Java相比C/C的一大优势就是自动内存管理也就是垃圾回收Garbage Collection简称GC。GC机制让开发者无需手动释放对象内存大大减少了内存泄漏和悬垂指针的风险。然而自动回收并不意味着我们可以高枕无忧——理解GC的工作原理是优化Java应用性能、排查内存问题的必备技能。本文将系统介绍JVM垃圾回收的方方面面包括内存管理、对象生死判定、垃圾回收算法、分代回收机制、四种引用类型以及主流垃圾收集器最后还会简要介绍G1中的三色标记算法。1. 内存管理概述1.1 从C/C的手动管理说起在C/C中程序员必须手动分配和释放内存Test* test new Test(); delete test; // 必须手动释放如果忘记释放不再使用的对象就会造成内存泄漏如果多次释放则可能导致程序崩溃。这种手动管理方式虽然灵活但极易出错。1.2 Java的自动垃圾回收Java引入了自动垃圾回收机制由JVM负责回收不再使用的对象。GC主要针对堆Heap进行回收因为堆中存放了绝大部分对象实例。方法区元空间也会回收但条件苛刻且很少发生。而线程私有的程序计数器、虚拟机栈、本地方法栈随着线程的消亡自然释放无需GC介入。2. 方法区的垃圾回收方法区JDK 8后为元空间主要存储类元数据、常量、静态变量等。回收条件比堆严格得多需要同时满足以下三个条件该类所有的实例都已被回收堆中不存在任何该类的实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有任何地方被引用。由于条件苛刻方法区的回收通常发生在Full GC时且回收效率较低。3. 判定对象已死引用计数法与可达性分析GC要回收对象首先需要判断哪些对象已经“死亡”不再被使用。3.1 引用计数法原理每个对象维护一个引用计数器每当有一个地方引用它时计数器1引用失效时计数器-1。计数器为0的对象即可回收。优点实现简单判定高效。缺点难以处理循环引用如A引用BB引用A外部再无引用时两者计数器均不为0无法回收。主流Java虚拟机已摒弃此法。3.2 可达性分析算法原理以一系列称为GC Roots的对象为起点从这些根向下搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连即不可达则证明此对象已死亡。GC Roots包括虚拟机栈栈帧中的局部变量表中引用的对象。方法区中静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI引用的对象。活跃线程Thread对象等。可达性分析解决了循环引用问题是Java、C#等现代语言的选择。4. 垃圾回收算法确定了哪些对象可回收后GC需要执行清理。以下是三种基础算法。4.1 标记-清除算法Mark-Sweep步骤标记从GC Roots出发标记所有可达对象。清除遍历堆将未标记的对象回收。优点简单不需要移动对象。缺点效率问题标记和清除都需要遍历所有对象效率较低。空间问题产生大量内存碎片导致后续分配大对象困难。4.2 复制算法Copying原理将内存分为两块如From和To每次只使用一块。当这块内存用完时将存活对象复制到另一块然后一次性清理原块。优点实现简单不会产生碎片。缺点内存利用率只有一半如果对象存活率高复制开销大。应用新生代采用复制算法因为对象“朝生夕死”存活率低。4.3 标记-整理算法Mark-Compact步骤标记所有存活对象。将存活对象向一端移动然后清理边界以外的内存。优点避免碎片内存利用率高。缺点移动对象需要更新引用开销较大。应用老年代常用此算法或与标记-清除混合使用。4.4 分代收集算法Generational Collection当前商业虚拟机的垃圾收集都采用分代收集即根据对象存活周期将堆划分为新生代和老年代分别采用不同算法。新生代对象存活率低使用复制算法只需复制少量存活对象效率高。老年代对象存活率高使用标记-清除或标记-整理算法避免复制开销。5. 四种引用类型Java的引用强度依次递减不同引用类型决定了对象在GC时的命运。5.1 强引用Strong Reference特点最普通的引用如Object obj new Object()。只要强引用存在GC永远不会回收被引用的对象。示例User user new User();5.2 软引用Soft Reference特点内存不足时才会回收。适合实现缓存如SoftReferenceUser。应用本地缓存、图片缓存等。5.3 弱引用Weak Reference特点只要发生GC无论内存是否充足都会被回收。适合实现规范化映射如WeakHashMap。应用ThreadLocal中的Entry就是弱引用。5.4 虚引用Phantom Reference特点最弱的引用无法通过它获取对象实例。唯一目的是在对象被回收时收到一个系统通知通过引用队列。应用对象回收跟踪、堆外内存释放。6. 垃圾收集器GC Collectors垃圾收集器是垃圾回收算法的具体实现。不同收集器适用于不同场景以下是HotSpot虚拟机中的主流收集器。6.1 Serial / Serial Old特点单线程收集进行垃圾回收时必须暂停所有用户线程Stop The WorldSTW。简单高效适合客户端模式或单核环境。新生代Serial复制算法老年代Serial Old标记-整理6.2 ParNew特点Serial的多线程版本与CMS配合使用。新生代并行复制老年代仍串行或配合CMS。6.3 Parallel Scavenge / Parallel Old特点关注吞吐量CPU用于用户代码的时间与总时间的比值。可动态调整参数适合后台计算型应用。新生代Parallel Scavenge复制算法老年代Parallel Old标记-整理6.4 CMSConcurrent Mark Sweep特点以最短回收停顿时间为目标适用于互联网服务端。基于“标记-清除”过程分为初始标记STW标记GC Roots直接关联对象并发标记与用户线程并发重新标记STW修正并发标记期间的变动并发清除优点并发、低停顿。缺点产生碎片、占用CPU资源、无法处理浮动垃圾。JDK 9后标记为废弃JDK 14正式移除。6.5 G1Garbage First特点区域化、分代化、可预测停顿。将堆划分为多个大小相等的Region默认2048个每个Region可扮演Eden、Survivor、Old或Humongous大对象。不再物理分代而是逻辑分代。工作步骤初始标记STW并发标记与用户线程并发最终标记STW筛选回收STW根据Region回收价值和成本排序优先回收收益高的Region优点避免全堆扫描使用Remembered Set管理跨Region引用可预测停顿时间通过-XX:MaxGCPauseMillis指定不会产生碎片采用复制算法。默认JDK 9默认收集器。6.5.1 三色标记算法G1并发标记的核心三色标记是并发标记的基础用于在不暂停用户线程的情况下标记存活对象。白色尚未访问的对象。灰色已被访问但其引用的对象尚未全部访问。黑色已被访问且其所有引用都已访问。问题并发标记期间用户线程可能修改引用关系导致两种异常浮动垃圾本应回收的对象被误标为存活留待下次GC处理。漏标错杀存活对象被误标为垃圾导致程序错误。解决方案CMS使用增量更新当黑色对象新增指向白色对象的引用时记录该黑色对象在重新标记阶段重新扫描。G1使用原始快照SATB当灰色对象要删除指向白色对象的引用时在写屏障中记录该引用保证该白色对象被当作存活即使实际已死也当作浮动垃圾。6.6 ZGC特点JDK 11引入专注于极低延迟几毫秒内完成GC可处理TB级堆。基于染色指针、读屏障等技术几乎全部并发STW时间极短。适用场景对延迟极度敏感的超大内存应用。7. GC触发机制与类型Minor GC / Young GC新生代空间Eden不足时触发。频率高速度快。Major GC / Old GC老年代空间不足时触发。通常比Minor GC慢10倍以上。Full GC回收整个堆和方法区。触发条件包括老年代空间不足。方法区空间不足。调用System.gc()建议不一定立即执行。大对象分配失败等。8. 垃圾收集器选择指南收集器组合适用场景Serial Serial Old单核、客户端、小内存应用Parallel Scavenge Parallel Old高吞吐量、后台计算型应用ParNew CMS低延迟、响应优先的应用JDK 8及以前G1平衡吞吐量与延迟大内存6GBJDK 9默认ZGC极低延迟、超大内存数百GB以上注意没有最好的收集器只有最适合的。建议在实际环境中进行压测根据吞吐量、停顿时间、内存占用等指标选择。9. 总结JVM垃圾回收是一个复杂而精妙的系统它通过可达性分析判定对象生死采用分代收集和多种算法平衡效率与内存利用率并提供多种收集器满足不同场景需求。理解GC原理有助于我们写出更高效的代码合理设置JVM参数以及在遇到内存溢出、频繁GC时快速定位问题。在实际开发中我们应当注意对象的生命周期避免内存泄漏。合理使用软引用、弱引用实现缓存。监控GC日志及时发现异常。根据应用特点选择并调优垃圾收集器。
JVM 整理(五) 垃圾回收(GC)
Java相比C/C的一大优势就是自动内存管理也就是垃圾回收Garbage Collection简称GC。GC机制让开发者无需手动释放对象内存大大减少了内存泄漏和悬垂指针的风险。然而自动回收并不意味着我们可以高枕无忧——理解GC的工作原理是优化Java应用性能、排查内存问题的必备技能。本文将系统介绍JVM垃圾回收的方方面面包括内存管理、对象生死判定、垃圾回收算法、分代回收机制、四种引用类型以及主流垃圾收集器最后还会简要介绍G1中的三色标记算法。1. 内存管理概述1.1 从C/C的手动管理说起在C/C中程序员必须手动分配和释放内存Test* test new Test(); delete test; // 必须手动释放如果忘记释放不再使用的对象就会造成内存泄漏如果多次释放则可能导致程序崩溃。这种手动管理方式虽然灵活但极易出错。1.2 Java的自动垃圾回收Java引入了自动垃圾回收机制由JVM负责回收不再使用的对象。GC主要针对堆Heap进行回收因为堆中存放了绝大部分对象实例。方法区元空间也会回收但条件苛刻且很少发生。而线程私有的程序计数器、虚拟机栈、本地方法栈随着线程的消亡自然释放无需GC介入。2. 方法区的垃圾回收方法区JDK 8后为元空间主要存储类元数据、常量、静态变量等。回收条件比堆严格得多需要同时满足以下三个条件该类所有的实例都已被回收堆中不存在任何该类的实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有任何地方被引用。由于条件苛刻方法区的回收通常发生在Full GC时且回收效率较低。3. 判定对象已死引用计数法与可达性分析GC要回收对象首先需要判断哪些对象已经“死亡”不再被使用。3.1 引用计数法原理每个对象维护一个引用计数器每当有一个地方引用它时计数器1引用失效时计数器-1。计数器为0的对象即可回收。优点实现简单判定高效。缺点难以处理循环引用如A引用BB引用A外部再无引用时两者计数器均不为0无法回收。主流Java虚拟机已摒弃此法。3.2 可达性分析算法原理以一系列称为GC Roots的对象为起点从这些根向下搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连即不可达则证明此对象已死亡。GC Roots包括虚拟机栈栈帧中的局部变量表中引用的对象。方法区中静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI引用的对象。活跃线程Thread对象等。可达性分析解决了循环引用问题是Java、C#等现代语言的选择。4. 垃圾回收算法确定了哪些对象可回收后GC需要执行清理。以下是三种基础算法。4.1 标记-清除算法Mark-Sweep步骤标记从GC Roots出发标记所有可达对象。清除遍历堆将未标记的对象回收。优点简单不需要移动对象。缺点效率问题标记和清除都需要遍历所有对象效率较低。空间问题产生大量内存碎片导致后续分配大对象困难。4.2 复制算法Copying原理将内存分为两块如From和To每次只使用一块。当这块内存用完时将存活对象复制到另一块然后一次性清理原块。优点实现简单不会产生碎片。缺点内存利用率只有一半如果对象存活率高复制开销大。应用新生代采用复制算法因为对象“朝生夕死”存活率低。4.3 标记-整理算法Mark-Compact步骤标记所有存活对象。将存活对象向一端移动然后清理边界以外的内存。优点避免碎片内存利用率高。缺点移动对象需要更新引用开销较大。应用老年代常用此算法或与标记-清除混合使用。4.4 分代收集算法Generational Collection当前商业虚拟机的垃圾收集都采用分代收集即根据对象存活周期将堆划分为新生代和老年代分别采用不同算法。新生代对象存活率低使用复制算法只需复制少量存活对象效率高。老年代对象存活率高使用标记-清除或标记-整理算法避免复制开销。5. 四种引用类型Java的引用强度依次递减不同引用类型决定了对象在GC时的命运。5.1 强引用Strong Reference特点最普通的引用如Object obj new Object()。只要强引用存在GC永远不会回收被引用的对象。示例User user new User();5.2 软引用Soft Reference特点内存不足时才会回收。适合实现缓存如SoftReferenceUser。应用本地缓存、图片缓存等。5.3 弱引用Weak Reference特点只要发生GC无论内存是否充足都会被回收。适合实现规范化映射如WeakHashMap。应用ThreadLocal中的Entry就是弱引用。5.4 虚引用Phantom Reference特点最弱的引用无法通过它获取对象实例。唯一目的是在对象被回收时收到一个系统通知通过引用队列。应用对象回收跟踪、堆外内存释放。6. 垃圾收集器GC Collectors垃圾收集器是垃圾回收算法的具体实现。不同收集器适用于不同场景以下是HotSpot虚拟机中的主流收集器。6.1 Serial / Serial Old特点单线程收集进行垃圾回收时必须暂停所有用户线程Stop The WorldSTW。简单高效适合客户端模式或单核环境。新生代Serial复制算法老年代Serial Old标记-整理6.2 ParNew特点Serial的多线程版本与CMS配合使用。新生代并行复制老年代仍串行或配合CMS。6.3 Parallel Scavenge / Parallel Old特点关注吞吐量CPU用于用户代码的时间与总时间的比值。可动态调整参数适合后台计算型应用。新生代Parallel Scavenge复制算法老年代Parallel Old标记-整理6.4 CMSConcurrent Mark Sweep特点以最短回收停顿时间为目标适用于互联网服务端。基于“标记-清除”过程分为初始标记STW标记GC Roots直接关联对象并发标记与用户线程并发重新标记STW修正并发标记期间的变动并发清除优点并发、低停顿。缺点产生碎片、占用CPU资源、无法处理浮动垃圾。JDK 9后标记为废弃JDK 14正式移除。6.5 G1Garbage First特点区域化、分代化、可预测停顿。将堆划分为多个大小相等的Region默认2048个每个Region可扮演Eden、Survivor、Old或Humongous大对象。不再物理分代而是逻辑分代。工作步骤初始标记STW并发标记与用户线程并发最终标记STW筛选回收STW根据Region回收价值和成本排序优先回收收益高的Region优点避免全堆扫描使用Remembered Set管理跨Region引用可预测停顿时间通过-XX:MaxGCPauseMillis指定不会产生碎片采用复制算法。默认JDK 9默认收集器。6.5.1 三色标记算法G1并发标记的核心三色标记是并发标记的基础用于在不暂停用户线程的情况下标记存活对象。白色尚未访问的对象。灰色已被访问但其引用的对象尚未全部访问。黑色已被访问且其所有引用都已访问。问题并发标记期间用户线程可能修改引用关系导致两种异常浮动垃圾本应回收的对象被误标为存活留待下次GC处理。漏标错杀存活对象被误标为垃圾导致程序错误。解决方案CMS使用增量更新当黑色对象新增指向白色对象的引用时记录该黑色对象在重新标记阶段重新扫描。G1使用原始快照SATB当灰色对象要删除指向白色对象的引用时在写屏障中记录该引用保证该白色对象被当作存活即使实际已死也当作浮动垃圾。6.6 ZGC特点JDK 11引入专注于极低延迟几毫秒内完成GC可处理TB级堆。基于染色指针、读屏障等技术几乎全部并发STW时间极短。适用场景对延迟极度敏感的超大内存应用。7. GC触发机制与类型Minor GC / Young GC新生代空间Eden不足时触发。频率高速度快。Major GC / Old GC老年代空间不足时触发。通常比Minor GC慢10倍以上。Full GC回收整个堆和方法区。触发条件包括老年代空间不足。方法区空间不足。调用System.gc()建议不一定立即执行。大对象分配失败等。8. 垃圾收集器选择指南收集器组合适用场景Serial Serial Old单核、客户端、小内存应用Parallel Scavenge Parallel Old高吞吐量、后台计算型应用ParNew CMS低延迟、响应优先的应用JDK 8及以前G1平衡吞吐量与延迟大内存6GBJDK 9默认ZGC极低延迟、超大内存数百GB以上注意没有最好的收集器只有最适合的。建议在实际环境中进行压测根据吞吐量、停顿时间、内存占用等指标选择。9. 总结JVM垃圾回收是一个复杂而精妙的系统它通过可达性分析判定对象生死采用分代收集和多种算法平衡效率与内存利用率并提供多种收集器满足不同场景需求。理解GC原理有助于我们写出更高效的代码合理设置JVM参数以及在遇到内存溢出、频繁GC时快速定位问题。在实际开发中我们应当注意对象的生命周期避免内存泄漏。合理使用软引用、弱引用实现缓存。监控GC日志及时发现异常。根据应用特点选择并调优垃圾收集器。