内存布局设计
JVM的内存设计上是划分为两个大的区域的启动时向操作系统分配的程序内存区域、直接对系统内存进行操作的区域,后者不属于JVM管理,我们主要是分析前者
大体结构
- 程序计数器
-
程序计数器是用来记录当前线程所执行的字节码的行号指示器,字节码解释器在工作时就是通过改变这个计数器的值来记录当前执行的指令.
它是JVM层次的指令执行记录与硬件层次的指令地址寄存器(IR)的区别在于程序计数器只是一段内存区域,通过软件实现的记录虚拟机字节码的执行地址,当执行Native方法时,该区域值为空
-
为什么要有程序计数器?
- 在于jvm提交给CPU执行的指令不是一次性全部提交过去的,而是根据java的业务逻辑通过jvm编译在提交给CPU相应的指令,所以需要对IR进行抽象
-
- Java虚拟机栈
- 虚拟机栈描述的是Java方法执行时候的内存模型:线程在执行方法时都会创建相应独立的栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 虚拟机栈是由一个个栈帧组成的
- 局部变量表用于存储编译器可知的各种基本数据类型的值引用、对象引用、returnAddres类型(指向一个指令的类型,指的是方法返回的指令地址)
-
本地方法栈
本地方法栈是用于表示对本地方法(native)执行的’c stack’
-
java堆
java堆是java进程管理的最大的一块内存区域,这个区域允许所有线程访问,作用是在这个区域分配对象实例空间;堆所分配的内存空间不需要是连续的(物理/逻辑上都是一样),在HotSpot中堆空间在默认情况下是申请的连续分配
-
方法区
方法区用于虚拟机加载类信息、常量池、静态变量、即时编译器编译后的代码数据等
-
常量池
常量池用于存放在编译期产生的各种字符串、符号引用以及允许运行期动态添加
-
直接内存
直接内存指的是通过Native方法直接分配的堆外内存部分
堆上内存分配过程
堆上内存分配过程约等于对象的堆内存分配过程,堆上内存分配需要并发分配的问题,目前有两种处理方案:
- 通过CAS机制加上失败重试来保证操作的原子性
- 通过本地线程分配缓冲(TLAB)的方式来保证,核心思想就是通过预先划分线程独占的内存空间来尽量避免从堆上直接分配内存从而避免频繁的锁竞争
对象的内存布局
对象的内存布局可以划分为三个区域:对象头(Header)、实例数据、对齐填充部分
-
header
- Mark Word:通过标识来复用同一地址空间,列如对象哈希码/锁指针/偏向锁信息等
- 类型指针:用于标识对象的类元数据,其中数组类型要表示元素的类型以及元素的个数
-
实例数据
- 实例数据部分是用于存储业务数据的部分,在分配内存时会尽量遵守相同类型(宽度)的数据分配到一起
-
对齐部分
- 对齐部分不一定存在,由于jvm虚拟机分配内存是8个字节的整数倍,header部分设计是就遵守此规范,但是实例数据部分不一定能恰好遵循,因此需要进行数据填充来符合此规范
对象的访问定位
在栈帧中通过reference来表示对象的指针,然后通过refernce查找对象的方式可以分为两种实现方案:
- 直接通过指针来返回
- 通过句柄的方式来返回
HotSpot是通过第二种方式来进行访问的,优点是**快,**缺点是在GC时需要修改栈帧中的reference数据