JVM内存模型
- 程序计数器:线程私有,用于表示当前执行的字节码地址。
- 虚拟机栈:线程私有,方法执行时创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等,服务于字节码。
- 本地方法栈:同虚拟机栈,但为本地方法服务。
本地方法:由Java调用非Java代码接口。
- 堆:存放对象实例。
- 方法区:已加载类的信息、常量、静态变量、即时编译后的代码。其中包括运行常量池。
对象
创建对象步骤
- 检查类的符号引用,若没有则进行类加载过程。
- 分配内存。
- 内存空间初始化(不含对象头)。
- 配置对象头。
- 执行构造函数。
对象的内存分配
包括对象头,实例数据,对齐填充
对象头
- 存储自身运行时数据,例如HashCode、GC分代年龄、锁状态等。
- 类型指针。
- 如果为数组,则还要有用于记录数组长度等块。
对象等访问定位
通过栈 -> reference -> 堆来操作对象,可以通过句柄或者直接指针访问。
Java GC
存活对象判断
引用计数算法
- 为对象添加引用计数器,每次产生引用加1,引用失效减1。
- 缺点:难以解决对象之间循环引用的问题。
可达性分析算法
- 从GC Root节点沿引用链向下搜索,当从GC Root到某对象不可达时则该对象应该被回收。
垃圾收集算法
- 标记 - 清除算法:先通过可达性分析标记需要回收的对象,完成后统一回收所用被标记的对象。
- 复制算法:将内存划分为两块,每次只用其中一块。用尽时,将存活的对象复制到另外一块上,然后将该块内存全部清理。主要用于新生代的内存回收。缺点是会浪费部分内存。
- 标记 - 整理算法:标记步骤同标记 - 清除算法,整理则是将存活对象向内存的一端移动,然后清掉边界外的内存。
- 分代收集:根据对象存活周期的不同,将村村划分为几块。一般是把Java堆分成新生代和老年代。
垃圾收集器
Serial收集器
- 单线程,STW 100ms以内。作用于新生代时采用复制算法,作用于老年代时使用标记 - 整理算法。
ParNew收集器
- Serial收集器的多线程版
Parallel Scavenge收集器
- 同ParNew收集器,回收新生代,目标为达到可控制的吞吐量。
CMS收集器
- 采用标记 - 清除算法。
- 步骤:
- 初始标记,STW
- 并发标记,与其他线程同步运行
- 重新标记,STW
- 并发清除
- 缺点:
- 并发阶段会占用CPU资源而导致吞吐量下降
- 无法处理浮动垃圾(并发清除时产生的垃圾)
- 标记 - 清除带来的内存碎片问题
G1收集器
- 采用标记 - 整理算法。
- 优势:
- 利用多CPU、多核,缩短STW
- 分代收集
- 使用复制算法减少碎片产生
- 可预测停顿
- G1收集器将堆划分成大小相同堆多个Region,新生代和老年代不再物理隔离。跟踪Region中垃圾价值大小,维护优先列表,优先回收价值最大的Region。
- 步骤:
- 初始标记,STW
- 并发标记
- 最终标记,STW
- 筛选回收,STW
内存分配与回收策略
- 对象优先在Eden分配,若Eden区没有足够空间则出发minor GC
- 大对象直接进入老年代
- 长期存活对象进入老年代
- 动态的对象年龄判定:如果Survivor区相同年龄所有对象大小的总和大于Survivor区的一半,则年龄大于或等于该年龄的对象就可以直接进入老年代
- 空间分配担保
- 老年代最大可用连续空间是否大于新生代所有对象空间,是则进行minor GC安全。
- 老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则minor GC有风险,可能会再次出发full GC,反之若小于,或者设置不允许冒险,则直接进行full GC。