深入JVM9 运行时数据区
1. 运行时数据区
包括:PC 寄存器、Java虚拟机栈、Java堆、方法区、运行时常量池、本地方法栈
1.1 PC(Program Counter)寄存器(线程私有)
程序计数器(线程私有):就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码)由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
1:每个线程拥有一个PC寄存器,是线程私有的,用来存储指向下一条指令的地址
2:在创建线程的时候,创建相应的PC寄存器
3:执行本地方法时,PC寄存器的值为underfined
4:是一块较小的内存空间,是唯一一个在JVM规范中没有规定OutOfMemoryError的内存区域
1.2 Java虚拟机栈(线程私有)
Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息)不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致
- 局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个slot存放32位的数据,long、double占两个槽位
- 栈的优点: 存取速度比堆快,仅次于寄存器
- 栈的缺点:存在栈中的数据大小、生存期是在编译期决定的,缺乏灵活性
1.3本地方法栈(线程私有)
登记native方法,在Execution Engine执行时加载本地方法库
1.4. Java堆内存(线程共享)
Java堆用来存放应用系统创建的对象和数组,所有线程共享Java堆
Java堆是在运行期动态分配内存的大小,自动进行垃圾回收
GC主要就管理堆空间,堆分代GC来说,堆也是分代的
堆的优点: 运行期动态分配内存大小,自动进行垃圾回收
堆的缺点: 效率相对较慢
1.5 方法区(线程共享)
类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区中,虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
- 通常和元空间关联在一起,但具体的跟JVM实现和版本有关
1.6运行时常量池:
是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通常包括:类的版本、字段、方法、接口等信息
- 在方法区中分配
- 通常在加载类和接口到JVM后,就创建相应的运行时常量池
2. 栈、堆、方法区的交互关系
2.1对象的访问定位
image.png在JVM规范中只规定了reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位、访问堆中对象的具体位置
因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄或使用指针两种方式
HotSpot是使用指针的方式来访问对象
Java堆中会存放访问类元数据的地址
reference存储的就直接是对象的地址
image.png
3 Java堆的结构
- 新生代用来放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
- 老年代存储对象比新生代存储对象的年龄大得多
- 老年代存储一些大对象
- 整个堆大小 = 新生代 + 老年代
- 新生代 = Eden + 存货区
- 从前的持久代,用来存放Class、Method等元信息的区域,从JDK8开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地缓存
4 对象的内存布局
-
对象在内存中存储的布局(这里以HotSpot虚拟机为例来说明),分为:对象头、实例数据和对齐填充
-
对象头,包括两个部分:
(1)Mark Word:存储对象自身的运行数据,如:HashCode、GC分代存储、锁状态标志等。
(2)类型指针:对象指向它的类元数据的指针 -
实例数据
真正存放对象实例数据的地方 -
对齐填充
这部分不一定存在,也没有什么特别含义,仅仅是占位符。
因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就对齐,