JVM
JVM的组成
线程共享区
- 堆
- 方法区
线程私有区
- 虚拟机栈
- 本地方法栈
- 程序计数器
虚拟机栈
每个线程都会持有一块虚拟机栈, 是线程执行时的主要处理模块, 虚拟机栈包含栈帧
栈帧:由局部变量表,操作数栈,动态连接,方法出口组成
- 局部变量表: 存放着各种基本数据类型和对象的引用
- 操作数栈:用于执行各种计算指令
- 动态连接:Class中存在大量的符号引用, 类加载时会转化一部分为直接引用, 方法运行时也会转化一部分为直接引用, 这部分就为动态连接, 需要将符号引用转化为直接引用
- 方法出口:方法执行结束时的出口,返回结果或结束执行 包括异常, 返回, 和无返回
本地方法栈
由Native修饰的方法, 具体指向JVM底层的一个实现方法 , Hotspot虚拟机则是C++提供的实现, 与虚拟机栈的区别为, 虚拟机栈是由虚拟机执行Java方法(也就是字节码)服务, 本地方法栈则是本地方法服务
程序计数器
在JVM中唯一不会发生内存OOM的一块区域, 存储着当前JVM执行的字节码的行号, 主要用于线程执行时间片切换的时候, 记录线程具体执行到了哪一行
Java堆
堆主要存放对象, 根据使用的垃圾回收器不同, 堆空间的划分也不同, 大部分垃圾回收器都是基于分代划分, 所以经常会出现新生代,老年代, From区, To区, 而新的ZGC, Shenandoah则不是按照分代划分
方法区
方法区主要存已加载的类信息和字符串常量池, 基本书局类型常量池, 静态变量, 常量。是JDK6以前堆中的永久代
对象的创建
- 对象的创建首先会检查类是否已经被加载、解析和初始化过, 如果没有则会先执行类加载过程.
- 加载检查完成后, 会开始在堆中分配内存, 根据Java堆是否连续完整采用的分配策略也不同, 连续完整的则使用指针碰撞, 不连续的则使用空闲列表, Java堆是否连续完整取决于选择的垃圾回收器是否有空间整理功能,
对于分配时带来的并发安全问题, JVM采用CAS+失败重试机制和TLAB(本地线程分配缓冲)方式 - 内存分配完成后会开始零值的初始化工作, 如果使用了TLAB, 这一步可以提前至TLAB分配时直接执行。这一步保证了对象的实例字段在没有初始值时就能直接使用。
- 执行对象头的初始化工作
- 真正的对象初始化工作, 赋值变量, 构造方法执行等
对象的内存布局
对象主要由对象头、实例数据、对齐填充三个部分组成
- 对象头:主要由Mark word和类型指针组成, 如果对象是个数组,则还会包含数组的长度
- 实例数据:主要包含我们定义的各种字段内容
- 对齐填充:没有具体的实际意义, 主要起着占位符的作用, HotSpot虚拟机要求对象大小必须为8的整数倍, 如果定义的对象不是8的整数倍则会通过对齐填充来补全
对象头
- Mark Word:主要存储对象自身的一些基本数据,如:hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程的ID、偏向线程的时间戳等, 这部分数据长度在64位或32位
- 类型指针:主要指向对象的类型元数据的指针, 虚拟机通过这个指针来确定这个对象是哪个类的实例
GC-垃圾回收器
- Serial与Serial Old:
- Parallel Scavenge与Parallel Old:
- ParNew与CMS:
- G1:
- ZGC:
分代回收算法
- 引用计数法:
- 可达性分析算法:
- 标记-整理:
- 标记-清除:
- 标记-复制:
- 空间分配担保:
- 逃逸分析:
CMS、G1、ZGC的内存划分
CMS分代划分
- Eden区:
- From区:
- To区:
- Old区:
G1内存划分
- Region的概念:
- Humongous:
ZGC的内存划分
- 小对象区:
- 中对象区:
- 大对象区:
类加载过程
双亲委派机制
- 破坏双亲委派机制:

浙公网安备 33010602011771号