前提:基于 java8 的 HotSpot
1. 程序计数器/PC 寄存器(Program Counter Register)
相当于程序执行的标记,字节码计时器就是通过计数器的值来决定下一条需要执行的命令,它控制着分支
,循环
,跳转
,异常处理
,线程恢复
等。
另外,由于 cpu 会在多个线程之前切换,当 cpu 切回原线程时,也要根据计数器的值来继续执行,不至于重新执行或迷失。
程序计数器是线程独占的,每个线程都有各自的程序计数器。它的生命周期和线程相同,线程结束后会自动释放,而且程序计数器占用的内存较小,通常不会发生内存溢出,也不是垃圾回收关注的对象。
如果线程在执行一个 java 方法,程序计数器的值就是正在执行的虚拟机字节码指令的地址;如果线程在执行一个本地方法,这个计数器值则为空(undefined)。
2. 本地方法栈
与 java 虚拟机栈类似,只不过它是针对本地方法,java 虚拟机栈针对的是 java 方法(也就是字节码)
*3. java虚拟机栈
通常所说的栈就是指这个区域,它也是线程独占,生命周期与线程相同。就是用来执行方法的。
虚拟机栈描述了 java 方法执行的线程内存模型:
每个方法被执行的时候,虚拟机都会创建一个栈帧(Stack Frame),用于存局部变量表
,动态链接
,方法出口
,操作数栈
,和一些附加信息
信息,每个方法被调用直到执行完毕,就对应着一个栈帧的入栈、出栈。
-
局部变量表 存放了编译期间可知的八种
基本数据类型
,对象引用
(reference类型,指向对象真实地址),returnAddress类型
。
局部变量表,通常是栈帧中最大的一部分,不仅影响栈的最大占帧数,而且由于它属于 GC Roots,所以也影响着 GC,是性能调优的重点关注对象。 - 操作数栈 用于存储计算过程中的中间结果,也作为计算过程中变量的临时存储
- 动态链接 用于将符号引用转换为对方法的直接引用
- 方法出口 就是指向程序计数器的值,也就是方法执行完成后的出口或者说下一条指令

*4. java堆
这是 jvm 中最大的一块区域,是线程共享的区域,随着虚拟机启动而创建。它是用来存放对象实例
的。
Java 堆主要分为2个区域-年轻代与老年代,年轻代包括 Eden 区和 Survivor 区,Survivor 区又分 From 区和 To 区。如下图:
堆区为什要分代?都放一起不好么?
根据工业统计,百分之九十的对象都是朝生夕死,只有较少的一部分对象需要长久保存,因此,把需要长久保存的对象放在一块(回收频率低),把刚创建的对象放在另一块(回收频率),方便回收,不用每次 GC 都扫描全堆
什么是 TLAB(Thread Local Allocation Buffer)?
堆区是线程共享的,这给线程通信带来便利,但是却给线程安全增加了负担。为了保证线程安全,不得不加锁,这就使得运行速度降低。TLAB 正是解决这个问题的,JVM 为每个线程在 Eden区 开辟一个线程独占的缓存空间,线程会优先使用 TLBA,在 TLAB 满了之后再使用 Eden 的空间。线程安全一定程度上得到了保障,同时也提升了内存分配的吞吐量,这种分配方式也称之为快速分配策略
。

*5. 方法区
方法区也是线程共享区,它用于存储虚拟机已加载的类型信息
,常量
,静态变量
,即时编译器编译后的代码缓存
等。
我们经常能听到“永久代”,它只是方法区的一种实现,由于“永久代”需要设置上限,而这个上限有比较难估算,所以内存溢出比较常见。HotSpot 虚拟机在 java 8,已经用“元空间”(用本地内存实现)代替“永久代”,这样,方法区不用设置上限,理论上本地内存上限才是对它的限制。
方法区的数据也并非是“永久”的,常量池的回收和类型的卸载会在这里进行。
运行时常量池:
它是方法区的一部分。Class 文件除了有类的版本信息、字段、方法、接口等描述信息外,还有一项信息——常量池表,用于存放编译器申城的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。
6. 直接内存
*注意:这部分不属于运行时数据区
随着 NIO 的加入,引入了一种基于通道和缓冲区的 I/O 方式,他可以使用 Native 函数库直接分配堆外内存
,然后通过一个存储在 java 堆
的 DirectByteBuffer 对象操作这块内存。这样避免了 java 堆和 Native 堆来回复制数据,可显著提高性能。
所以分配内存时,除了运行时数据区,还要留下冲粗的空间给直接内存,否则也会出现 OOM。
网友评论