3.垃圾回收
1. 什么时候触发 GC?
年轻代内存不够时触发 Young GC,老年代内存不够时触发 Old GC,两者都回收叫 Full GC。
可达性分析判断对象是否存活:从 GC Roots(局部变量、静态变量、常量)出发,能被引用链到达的存活,到达不了的是垃圾。内存区域划分见 2.JVM内存划分。
循环引用不会影响可达性分析。 A 引用 B、B 引用 A,但两者都没有被 GC Roots 引用,从 GC Roots 出发根本到不了它们,照样会被回收。
早期的引用计数法才会被循环引用坑到——A 和 B 互相引用,引用计数都是 1,永远不会降到 0,就算没人用它们也回收不掉。Java 没有用引用计数法,所以这个问题不存在。
Java 里真正的内存泄漏不是循环引用,而是对象一直被 GC Roots 引用链持有,但业务上已经不需要了:
- 静态集合(
static Map/List)不断往里塞对象,从不清理 - ThreadLocal 用完没调
remove(),Entry 留在线程的 ThreadLocalMap 里 - 监听器/回调注册了但没有反注册,持有外部对象引用
- 缓存只进不出,对象越堆越多
2. 回收算法
复制算法(年轻代):把存活对象复制到另一块区域,原区域整体清空。Eden + 两个 Survivor 就是复制算法的实现,只有 10% 内存闲置。
标记整理算法(老年代):标记存活对象,把它们移到一端,清理边界外的内存。没有内存碎片,但移动对象开销大。
3. 何时进入老年代?
-
年龄超过阈值(默认 15 次 GC):
-XX:MaxTenuringThreshold=15 -
动态年龄判断:Survivor 区中,某个年龄段对象总大小超过 Survivor 的 50%,该年龄及以上的对象直接晋升。这个比例阈值可以通过
-XX:TargetSurvivorRatio=50(默认值)调整,调大可以让对象在 Survivor 里多待几轮,减少过早晋升老年代
大对象直接进老年代:`-XX:PretenureSizeThreshold=1m
Young GC 后存活对象超过 Survivor 容量,直接溢出到老年代
# 年轻代 1G
-XX:SurvivorRatio=8 # Eden:S0:S1 = 8:1:1,Survivor 各 100M
-XX:MaxTenuringThreshold=15 # 最大年龄阈值
-XX:TargetSurvivorRatio=50
4. 垃圾回收器察晋升情况
加上 `-XX:+PrintTenuringDistribution` 后,GC 日志会输出类似:
Desired survivor size 52428800 bytes, new threshold 6 (max 15)
- age 1: 20971520 bytes, 20971520 total
- age 2: 15728640 bytes, 36700160 total
- age 3: 18874368 bytes, 55574528 total ← 累计超过 50M,3 龄及以上直接晋升
这里 age 3 累计超过了 50M 阈值,JVM 把 `new threshold` 调整为 3,下次 GC 后年龄 ≥ 3 的对象全部晋升老年代,不用等到 15 次
### 4.1 ParNew + CMS(JDK 8 常用)
ParNew 回收年轻代(多线程),CMS 回收老年代(并发,低停顿)。
CMS 四个阶段:
1. 初始标记(STW,很快)
2. 并发标记(耗时,不停顿业务)
3. 重新标记(STW,很快)
4. 并发清理(耗时,不停顿业务)
CMS 的问题:
- 并发标记占用 CPU,默认线程数 `(CPU核数+3)/4`
- 产生内存碎片,需要定期整理:`-XX:+UseCMSCompactAtFullCollection`
- `Concurrent Mode Failure`:老年代剩余空间不足以容纳并发期间新晋升的对象,触发 Serial Old 单线程 Full GC,停顿时间很长
CMS 常用配置:
```bash
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSScavengeBeforeRemark
4.2 G1(JDK 9+ 推荐)
把堆拆成多个等大的 Region,逻辑上区分年轻代和老年代,但不再是物理上连续的区域。可以设置 GC 停顿时间目标,6.G1垃圾回收器 根据每个 Region 的回收价值动态选择回收哪些。
关键参数:
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 停顿时间目标,默认 200ms
-XX:G1HeapRegionSize=2m # Region 大小,堆/2048
-XX:InitiatingHeapOccupancyPercent=45 # 老年代占比触发 Mixed GC 的阈值
G1 的优化核心是控制停顿时间,给足内存,合理设置 MaxGCPauseMillis。如果 G1 还是满足不了停顿要求,可以考虑升级到 5.ZGC学习。