Java--JVM 内存结构
一、JVM 的运行时数据的五个区域
1.虚拟机栈(VM Stack)
虚拟机栈为线程所有,生命周期贯穿整个线程,描述的是 Java 方法执行的内存模型,存放 8 大基本类型,对象引用,实例的方法。每个方法执行都会创建一个栈帧。
栈帧
每个方法的执行到执行完成,都是对应着一个栈帧再 JVM 中的入栈到出栈的过程。栈最顶部的栈帧一定是当前任务执行方法的栈帧,由 PC 寄存器指向该地址,具体内容放置在堆中,当执行完该方法后者出栈。 栈帧包含 局部变量表,操作数栈,动态连接,方法返回地址等。
2.本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈作用相似,但是虚拟机栈是执行 java 方法服务,而本地方法栈是为虚拟机使用到的 native 方法服务,也就是调用本地方法。native 一开始设计是由于当时都是 c,c++的时代,java 想要发展就必须兼容其他语言的代码来执行,所以就研发出 native,可以调用底层 c,c++代码,目前也可以用来调用其他语言如 python 等代码块。
3.程序计数器(PC)
PC 也是为线程私有,每个线程有单独的程序计数器,在线程中内存占用非常的小。程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变计数器的值来选取下一条字节码指令。其中,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。
4.方法区(Method Area)
方法区又称为非堆(Non-Heap) ,在逻辑上属于堆,物理上不属于堆的。方法区存储的是已经被 JVM 加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
方法区是线程共享、内存不连续、可扩展、可垃圾回收。
5.堆(Heap)
堆事线程共享的,几乎所有的对象实例都存在在此区间。因此堆的内存最大,也往往是我们 JVM 调优的最主要方向,于是就会有 GC 算法。
为了保证并发的安全,在堆空间中会为每个线程创建一个 TLAB(本地线程分配缓冲区)区域,所以虽然堆内存是线程共享的,但是在内存分配的角度来看,它又能够划分出多个线程私有的 TLAB 区域。
1.新生代
新生代分为伊甸园区和幸存区,新生的对象会放置在伊甸园区,当伊甸园区满了之后,会触发一次轻 GC,此时会讲伊甸园区存活的对象复制到幸存区 from,第二次触发轻 GC 时会把存活的对象从伊甸园区和 幸存区 from 对象复制到幸存区 to,这时 幸存区 from 和幸存区 to 动态交互,因此存活对象又放置在 from 区,所以新生代中,只会 Eden 区和 Survivor 区域其中的一块被同时使用,另一块 Survivor 区域始终为空的。
划分两个幸存区的原因是:
Survivor 区作为新生代与老年代之间的缓冲区,可以降低将对象送往老年代的频率,老年代也就没那么快就被对象堆满而导致发生垃圾回收
Survivor 中采用的复制算法,复制算法能有效的降低内存的碎片化
2.老年代
当被轻 GC 标记 16 次仍然存活的对象就会被送往老年代,老年代的对象会通过 full GC 进行垃圾回收。
3.GC 何时触发
轻 GC:当新对象生成,并且在 Eden 申请空间失败时,就触发轻 GC。
重 GC:对整个堆进行整理,包括新生区,老年代和元空间。下面几种情况会触发:1⃣️老年代被写满;2⃣️持久区被写满;3⃣️应用显示调用 System.gc();4⃣️上一次 GC 之后 Heap 的各区域分配策略动态变化。
版权声明: 本文为 InfoQ 作者【是老郭啊】的原创文章。
原文链接:【http://xie.infoq.cn/article/2571ced28b3b638406170bbb8】。文章转载请联系作者。
评论