Java 垃圾回收(GC)
第 1 章 垃圾回收GC基础1.1 什么是垃圾回收Garbage CollectionGC 是 JVM 自动回收不再使用的对象内存避免内存泄漏和内存溢出的自动化机制。不需要像 C/C 一样malloc/freeJava 由GC 线程自动管理内存。1.2 GC 要解决的 3 个核心问题哪些内存需要回收判断对象存活什么时候回收GC 触发时机如何回收回收算法 收集器这也是 JVM 设计 GC 的核心三问。1.3 GC 管理哪些内存只管理堆Heap 方法区Metaspace栈随方法进出栈自动释放程序计数器不涉及 GC本地方法栈同栈只有堆和元空间需要 GC第 2 章 对象存活判断GC 如何找到 “垃圾”2.1 引用计数法古老、JVM 不使用原理对象被引用一次计数器 1引用失效计数器 -1计数器 0 → 判定死亡致命缺陷无法解决循环引用plaintextA a new A(); B b new B(); a.b b; b.a a; a null; b null; // 两个对象都为null但计数器不为0 → 永远无法回收所以 JVM 不使用引用计数2.2 可达性分析算法JVM 唯一使用核心原理以GC Roots为起点向下搜索路径引用链。如果一个对象到 GC Roots 没有任何引用链 → 不可达 → 判定为垃圾。GC Roots 包含哪些必须背虚拟机栈中引用的对象局部变量本地方法栈引用的对象方法区静态属性引用的对象方法区常量引用的对象同步锁synchronized持有的对象只有这些能作为 GC 根节点。对象死亡的完整过程可达性分析 → 标记为不可达判断是否有必要执行 finalize ()若没重写 / 已执行 → 判定死亡否则放入 F-Queue 队列等待执行 finalize ()finalize () 中对象可自救重新与 GC Roots 关联第二次标记 → 仍不可达 → 真正回收finalize () 是对象最后一次自救机会但不推荐使用2.3 Java 四大引用决定 GC 何时回收对象1. 强引用StrongplaintextObject obj new Object();永不回收即使 OOM 也不回收我们平时写的 99% 都是强引用2. 软引用Soft内存不足时才回收适合缓存plaintextSoftReferenceObject sr new SoftReference(obj);3. 弱引用Weak下次 GC 必回收适合短生命周期对象plaintextWeakReferenceObject wr new WeakReference(obj);4. 虚引用Phantom等同于没有引用唯一作用对象被回收时收到通知用于堆外内存释放Netty第 3 章 垃圾回收算法底层核心3.1 标记 - 清除算法Mark-Sweep步骤标记标记所有需要回收的对象清除统一回收标记对象缺点效率低标记和清除都慢内存碎片严重大量不连续空间 → 大对象无法分配老年代早期使用现在几乎不用。3.2 复制算法Copying原理内存分为大小相等的两块每次只使用一块。GC 时 → 存活对象复制到另一块 → 清空当前块。优点速度极快无内存碎片适合存活对象少的场景缺点内存利用率只有 50%JVM 实际使用年轻代Eden : S0 : S1 8:1:1不是 1:1浪费极小。3.3 标记 - 整理算法Mark-Compact原理标记可回收对象让所有存活对象向一端移动清理端以外的内存优点无内存碎片内存利用率 100%缺点效率低需要移动对象适合老年代存活对象多3.4 分代收集算法JVM 实际使用的综合算法核心思想不同对象生命周期不同 → 不同区域使用不同算法年轻代存活对象少使用复制算法老年代存活对象多使用标记 - 整理 / 标记 - 清除这就是JVM 分代回收的理论基础。第 4 章 JVM 堆内存分代模型GC 工作的舞台4.1 为什么要分代绝大多数对象朝生夕死少部分对象长期存活不分代 → 每次 GC 都要扫描整个堆 → 效率极低4.2 完整分代结构plaintextJava 堆 ├── 年轻代 Young Gen1/3 │ ├── Eden 区 新对象诞生 │ ├── Survivor 0 存活对象轮换 │ └── Survivor 1 └── 老年代 Old Gen2/3 长期存活对象JDK8元空间Metaspace不属于堆使用本地内存。第 5 章 对象从创建到回收的完整 GC 生命周期超级详细5.1 对象内存分配规则新对象优先在 Eden 分配Eden 满 → Minor GC存活对象 → Survivor 空区年龄 1对象在 S0 ↔ S1 不断轮换年龄达标 → 晋升老年代大对象直接进入老年代老年代满 → Full GC5.2 对象晋升老年代的 4 个条件年龄达到阈值默认 15 岁每熬过一次 GC 年龄 1-XX:MaxTenuringThreshold15大对象直接进入老年代长字符串、大数组-XX:PretenureSizeThreshold...Survivor 相同年龄对象总大小 Survivor 一半动态年龄判断Minor GC 后存活对象太多无法放入 Survivor直接分配担保进入老年代5.3 GC 类型详细区分1. Minor GC / Young GC发生在年轻代触发条件Eden 满速度极快毫秒级STW短使用算法复制算法2. Major GC发生在老年代速度慢3. Full GC发生在整堆 元空间速度最慢比 Minor GC 慢 10 倍以上STW长线上卡顿、CPU 高、服务雪崩 90% 来源于 Full GCFull GC 触发条件必须背老年代空间不足元空间不足System.gc()Minor GC 后进入老年代对象太多堆内存分配担保失败使用 CMS 收集器出现 promotion failure /concurrent mode failure第 6 章 垃圾收集器GC 的具体执行者6.1 垃圾收集器总览年轻代收集器SerialParNewParallel Scavenge老年代收集器Serial OldParallel OldCMS全堆收集器G1ZGCShenandoah6.2 逐一代详细解析1. Serial 收集器单线程简单高效客户端模式默认适用于小内存应用2. ParNew 收集器Serial 多线程版本常与 CMS 配合3. Parallel Scavenge多线程关注吞吐量TPS后台计算类型应用4. CMS 收集器Concurrent Mark SweepJVM 历史里程碑第一款并发低延迟收集器运作过程初始标记STW并发标记和用户线程一起重新标记STW并发清除优点低延迟用户线程几乎不卡顿缺点内存碎片占用 CPU无法处理浮动垃圾容易触发 Full GC5. G1 收集器JDK 9 默认面向服务端、低延迟、高吞吐、区域化分代特点把堆分成多个 Region可指定最大停顿时间年轻代 老年代混合收集无内存碎片6. ZGC / ShenandoahJDK11超低延迟亚毫秒级几乎无 STW未来主流第 7 章 内存分配与回收策略深度原理7.1 对象优先在 Eden 分配新对象 → EdenEden 满 → Minor GC7.2 大对象直接进入老年代避免大对象在 Eden 和 Survivor 之间复制7.3 长期存活对象进入老年代年龄计数器7.4 动态对象年龄判断Survivor 中相同年龄对象大小 Survivor 一半直接进入老年代7.5 空间分配担保Minor GC 前JVM 检查老年代最大连续空间 年轻代对象总大小允许 Minor GC否则 Full GC第 8 章 GC 日志分析实战核心8.1 开启 GC 日志参数plaintext-XX:PrintGCDetails -XX:PrintGCTimeStamps -XX:PrintHeapAtGC -Xloggc:gc.log8.2 Young GC 日志plaintext[GC (Allocation Failure) [PSYoungGen: 8192K-512K(9216K)] 8192K-640K(30720K), 0.002 sec]含义Allocation FailureEden 满年轻代 8M → 512K总堆 8M → 640K耗时 0.002 秒8.3 Full GC 日志plaintext[Full GC (Ergonomics) [PSYoungGen: 512K-0K(9216K)] [ParOldGen: 20400K-20400K(20480K)] 20912K-20400K(29696K), [Metaspace: 3630K-3630K(1056768K)], 0.005 sec]老年代无法回收 → 内存泄漏 / 内存溢出第 9 章 GC 性能指标调优依据吞吐量用户线程时间 / (用户线程 GC 线程)停顿时间STW 时间GC 频率Minor GC / Full GC 次数内存使用率低延迟服务优先停顿时间大数据 / 计算服务优先吞吐量第 10 章 Java GC 调优实战步骤10.1 调优前提没有瓶颈不调优GC 调优是最后手段不是第一步10.2 调优目标减少 Full GC 次数缩短 Minor GC 停顿避免内存泄漏10.3 调优步骤监控 GC 日志分析 GC 频率、暂停时间判断是年轻代问题还是老年代问题调整堆大小、分代比例选择合适垃圾收集器压测验证10.4 生产环境推荐配置通用标准配置plaintext-Xms4g -Xmx4g -Xmn2g -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/tmp/ -Xloggc:gc.log低延迟服务plaintext-XX:UseZGC第 11 章 内存泄漏与 GC 关系深度11.1 内存泄漏现象对象存活但无用GC 无法回收老年代不断上涨Full GC 越来越频繁最终 OOM11.2 内存泄漏 8 大原因static 集合List/Map只增不减ThreadLocal 未 remove ()IO / 连接未关闭内部类持有外部类引用单例持有外部对象引用缓存未设置过期监听器未注销静态常量持有大对象11.3 排查工具jstatjmapMATArthasVisualVM第 12 章 GC 深度面试题能答完就是大神1. JVM 如何判断对象可回收可达性分析算法 GC Roots2. 为什么不用引用计数循环引用无法解决3. 年轻代为什么用复制算法存活对象少复制速度快无碎片4. 老年代为什么用标记整理存活对象多不适合复制无碎片5. 对象如何进入老年代年龄、大对象、动态判断、分配担保6. Minor GC 和 Full GC 区别年轻代 / 整堆、速度、STW、频率7. CMS 工作原理初始标记 → 并发标记 → 重新标记 → 并发清除8. G1 特点Region 化、可预测停顿、无碎片9. 什么是 STWStop The WorldGC 时暂停所有用户线程10. 内存泄漏如何排查jstat 看 GC → jmap dump → MAT 分析 → 找 GC Roots第 13 章 终极总结GC 完整知识体系GC 负责回收堆和元空间可达性分析判断对象死亡引用级别决定回收时机分代回收是 JVM GC 核心年轻代复制算法老年代标记整理对象生命周期Eden → Survivor → Old垃圾收集器Serial → Parallel → CMS → G1 → ZGCGC 调优核心减少 Full GC降低 STW线上故障 90% 来自 Full GC 或内存泄漏GC 日志 MAT 是排查 OOM 唯一利器