JVM垃圾回收机制
如何判断一个对象可以被回收
-
引用计数法
每个对象维护一个引用计数器
有一处引用指向该对象,引用计数器+1
引用失效,则引用计数器-1
引用计数器为0,判断无任何引用,可以回收 -
可达性分析法(jvm的实际使用)
-
选定一系列起点gc roots
-
从gc roots遍历所有的引用链路,能遍历到的对象全部存活
-
没有走到的对象,没有任何引用,可以被回收
引用计数法的缺陷
- 循环引用(致命问题)
两个/多个对象互相引用,没有外部引用指向它们,导致内存泄漏,拥有不会被回收 - 额外开销大
对象创建、赋值、消耗都需要修改计数器,高并发场景下有大量原子操作损耗性能
常见gc roots清单
- 虚拟机栈(栈帧本地变量表)
线程每调用一个方法就创建一块栈帧,栈帧的本地变量表里存着引用变量,从这些变量往下找对象,能找到,就认为存活 - 本地方法栈
专门给native本地方法服务,C/C++通过JNI持有java对象引用,C代码拿着java对象的指针,这个指针存在本地方法栈,gc扫描本地方法栈,顺着C的引用找到java对象,没有的话判定为不可达 - JDK8 元空间(原方法区)的静态变量、常量引用
静态变量属于类,类存在元空间,生命周期和类一致,只要类没被卸载,静态变量永远是 GC Roots。静态变量属于类,类存在元空间,生命周期和类一致,只要类没被卸载,静态变量永远是 GC Roots。
运行时常量池里的引用同理:常量引用指向堆内对象,这条链路让对象可达。
为什么大部分JVM都采用分代收集?
分代收集的根本原因:
- 绝大多数对象朝生夕灭
- 熬过多次 GC 的老对象,很难再被回收
Minor GC / Mixed GC / Full GC 触发条件
- Minor GC
Eden区内存耗尽,只回收新生代 - Mixed Gc
仅限G1
老年代占用率达到 IHOP 默认 45%,并发标记结束后触发;回收全部新生代 + 收益最高的部分老年代分区。 - Full GC(全堆 + 元空间回收)
老年代空间不足,放不下晋升对象;
元空间触及最大限制
主动调用 System.gc
......
从 Serial → Parallel → CMS → G1 → ZGC,每一代的演进解决了什么问题
Serial
- 架构
单线程gc,全程STW,新生代复制、老年代标记整理 - 解决
最基础的垃圾回收,适配早年单核小内存机器 - 缺点
多核cpu完全闲置,堆越大STW停顿越长,无法用于线上服务
Parallel
- 改进
多线程并行gc,多核cpu所有gc线程一起干活 - 解决
利用多核减少stw的耗时,主打高吞吐量,降低gc耗时 - 定位
吞吐量优先 - 遗留问题
依旧全局stw,业务全程卡死,大堆停顿依然很高,无法做低延迟业务
CMS
- 核心
gc大部分阶段核用户线程并发执行,不再全程卡死,追求低停顿 - 解决Parallel 最大痛点
解决长时间stw,只有初始标记、重新标记两段短暂暂停 - 底层
增量更新解决三色标记漏标 - 遗留问题
4.1. 标记清除算法,产生大量内存碎片;分配大对象容易 Full GC;
4.2. 并发模式失败:并发期间老年代被占满,直接退化为单线程 Full GC;
4.3. 无法管控停顿时长,堆越大风险越高。
CMS垃圾回收流程
- 初始标记
标记核gc root直接关联的对象,有stw - 并发标记
gc线程和业务线程同时运行 - 重新标记
在并发标记中业务线程可以修改了一些对象的引用关系,因此重新标记。
是通过增量更新,即,在黑色对象指向白色对象的情况,将黑色对象标记为灰色对象 - 并发清除
gc回收所有的白色对象,业务线程正常跑
G1(Garbage-First)
解决:CMS三大痛点
- 内存碎片不可控
- 停顿不可预测
- 老年代回收一刀切
底层内存布局
将java堆切分成上千个等大的Region,逻辑区分新生代、老年代,无物理连续分区
核心优势
- 可预测停顿,可设置
-XX:MaxGCPauseMillis,GC 优先回收垃圾占比最高的 Region,把停顿卡死在预期范围 - 解决碎片:回收 Region 时使用标记复制 / 整理,回收完该 Region 整块空闲,无内存碎片
- 支持 Mixed GC,不用整堆回收老年代。
G1 缺点
超大堆(32G+)场景,STW 依旧会拉长;并发整理耗时高,极致低延迟不如 ZGC。
gc过程
-
初始标记
标记所有 GC Roots 直接引用的对象 -
根区扫描(STW 结束,用户线程运行)
顺着 Root Region,遍历老年代指向新生代的引用。 -
并发标记(GC 线程与业务线程并发执行,无 STW)
GC 顺着引用链遍历全堆对象 -
最终重新标记(STW)
修正并发标记期间引用变动产生的标记误差;SATB 把所有删除的旧引用全部补标记,保证快照内存活对象全部标记到位。 -
筛选回收(可带轻微 STW)
原创
JVM常见疑难点
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法