深入理解Java垃圾回收(GC)
1. 概述我自己对GC的理解为 : new 的新对象会先进入eden_space里面 然后eden_space满了以后会触发yongGC将eden_space里面的对象 S0(初次可能是空的) 的所有存活对象全部复制到S1,这个时候会有判断如果S1没有满,则会清空S0 和eden_space里面所有的对象,满了则会将存活时间长的优先晋升到老年代,如果还是满的则将剩下的存活时间一般或者小的也开始晋升到老年代如果还是不行就全部晋升到老年代,之后再去清空S0和eden_space的所有对象,下次如果在发生yongGC的时候S0和S1角色就会发生互换了就是复制到空的S0区域了逻辑还是和上面的一样,元空间是为了保存class/method/filed等等Java的垃圾回收Garbage Collection, GC是JVM自动管理内存的核心机制它负责回收程序中不再使用的对象所占用的内存。其中新生代Young Generation的回收Young GC/Minor GC是最频繁发生的GC事件理解其工作原理对于性能调优至关重要。2. 堆内存结构在HotSpot JVM中堆内存通常被划分为以下几个区域新生代 (Young Generation)存放新创建的对象。生命周期短GC频繁。Eden区 (Eden Space)新对象诞生的地方。幸存者区 (Survivor Space)分为两个大小相等的区域S0 (From Survivor) 和 S1 (To Survivor)。用于存放经过一次GC后存活下来的对象。老年代 (Old Generation/Tenured Generation)存放经过多次GC后依然存活的对象即“长寿”对象。GC频率较低但每次回收耗时更长。元空间 (Metaspace)在JDK 8及以后取代了永久代PermGen。主要用于存储类的元数据如类结构、方法、字段、常量池等信息。其内存不在堆内由本地内存管理。graph TD A[Java Heap] -- B[新生代 Young Generation] A -- C[老年代 Old Generation] A -- D[元空间 Metaspacebr/非堆内存] B -- E[Eden区 Eden Space] B -- F[幸存者区 Survivor Space] F -- F0[S0 (From)] F -- F1[S1 (To)]3. 新生代GCYoung GC详细流程3.1 初始状态与触发对象分配绝大多数通过new关键字创建的对象会首先被分配在Eden区。触发条件当Eden区被填满时便会触发一次Young GC也称为 Minor GC。3.2 复制与清除Copying Scavenging这是Young GC的核心采用“复制-清除”算法。存活对象标记GC线程会暂停所有应用线程Stop-The-World然后标记出Eden区和当前活动的Survivor区假设是S0中所有仍然存活的对象。初次GC时S0通常是空的。复制到空Survivor区将所有存活的对象一次性复制到另一个空闲的Survivor区假设是S1。年龄计数每经历一次Young GC并存活下来对象的“年龄”就会增加1岁。清空原区域将Eden区和刚才使用的S0区全部清空。此时S1区存放着本次GC后所有存活的新生代对象S0变为空闲。flowchart TD Start[Young GC 触发] -- A{Eden S0 (From) 已满?} A -- 是 -- B[标记 Eden S0 中的存活对象] B -- C[将所有存活对象复制到 S1 (To)] C -- D{S1 空间充足?} D -- 是 -- E[清空 Eden 和 S0] E -- F[交换 S0/S1 角色br/S1成为新的From, S0成为新的To] F -- End[Young GC 结束] D -- 否 -- G[触发对象晋升判断]3.3 对象晋升Promotion机制当存活对象过多目标Survivor区S1空间不足时便会触发对象向老年代的晋升。晋升规则是保障堆内存稳定的关键年龄阈值晋升JVM有一个阈值-XX:MaxTenuringThreshold默认15。如果存活对象的年龄达到或超过此阈值则优先将其晋升到老年代。分配担保晋升如果晋升了“老年”对象后S1区仍然空间不足JVM会尝试将剩余对象中年龄较大的也晋升到老年代。强制晋升如果经过上述步骤S1区依然无法容纳剩余对象则本次GC中所有存活的新生代对象都将被直接晋升到老年代。这是一种“兜底”策略。执行清空完成晋升后最后清空Eden区和S0区。简单来说晋升顺序是老的 - 半老的 - 如果还不行全部晋升。3.4 角色交换一次Young GC结束后两个Survivor区的角色会发生交换刚才存放存活对象的S1区在下次GC时将作为“From Survivor”。被清空的S0区在下次GC时将作为“To Survivor”准备接收新的存活对象。如此循环往复使得总有一个Survivor区是空的为下一次复制算法做好准备。4. 元空间Metaspace的作用元空间用于保存class/method/field等信息它与GC的关系主要体现在存储内容类的元数据、方法代码、常量池、注解等。内存回收当对应的类加载器ClassLoader被垃圾回收时其加载的类在元空间中的元数据才会被回收。因此元空间的GC通常发生在Full GC期间且与堆内存的Young/Old GC是相对独立的。好处避免了永久代时代的java.lang.OutOfMemoryError: PermGen space错误内存分配更灵活。5. 老年代GC与Full GC5.1 老年代GCMajor GC/Old GC当对象从新生代晋升到老年代或大对象直接分配在老年代时老年代空间会逐渐被占用。老年代GC的触发条件与新生代不同触发条件空间不足老年代自身空间不足时触发。晋升失败Young GC时存活对象需要晋升到老年代但老年代剩余空间不足。显式调用程序调用System.gc()建议JVM进行Full GC不保证立即执行。元空间/永久代空间不足JDK 7及之前。回收算法标记-清除Mark-Sweep先标记所有存活对象然后清除未标记的垃圾对象。会产生内存碎片。标记-整理Mark-Compact标记存活对象后将所有存活对象向一端移动然后清理边界外的内存。避免碎片但耗时更长。分代收集GenerationalHotSpot JVM默认采用分代收集策略针对老年代使用“标记-清除”或“标记-整理”算法。特点停顿时间长Stop-The-World老年代GC通常涉及全堆扫描暂停时间远长于Young GC。频率低对象在老年代存活时间长GC频率较低。吞吐量与延迟的权衡不同的GC器如Parallel GC、CMS、G1、ZGC对老年代回收策略不同需要在吞吐量和延迟之间取舍。5.2 Full GC整堆回收Full GC是指同时回收新生代、老年代以及方法区/元空间的全局垃圾回收。它是JVM中最耗时的GC操作。触发条件老年代空间不足且无法通过Young GC释放足够空间。方法区/元空间空间不足。调用System.gc()可能触发Full GC。某些GC器的自身策略如CMS并发失败后的后备方案。执行过程暂停所有应用线程Stop-The-World。标记整个堆包括新生代、老年代以及方法区/元空间中的存活对象。清除所有未标记的垃圾对象。整理内存取决于GC器以减少碎片。恢复应用线程。影响与优化长时间停顿Full GC可能导致应用停顿数秒甚至数十秒对延迟敏感的应用是灾难性的。优化方向减少对象晋升通过调整-XX:MaxTenuringThreshold、增大Survivor区避免过早晋升。增大堆内存提供更多缓冲空间。选择合适的GC器如G1、ZGC、Shenandoah等低延迟GC器能大幅减少Full GC的频率和停顿时间。5.3 常见GC器对老年代/Full GC的处理GC器老年代回收方式Full GC触发条件特点Serial GC单线程标记-整理老年代满、晋升失败等简单停顿长适合客户端应用Parallel GC吞吐量优先多线程标记-整理同Serial GC但并行处理高吞吐量停顿仍较长CMSConcurrent Mark Sweep并发标记-清除并发模式失败、晋升失败等低停顿但会产生碎片可能触发Full GC整理G1Garbage-First分区回收优先回收垃圾最多区域并发回收跟不上分配速度时可预测停顿兼顾吞吐与延迟ZGC并发标记-整理几乎无Full GC极少数情况亚毫秒级停顿适用于大内存5.4 监控与调优建议监控指标GC频率Young GC、Full GC的次数/频率。GC耗时各阶段停顿时间。内存占用各代使用率、晋升速率。常见参数-XX:UseConcMarkSweepGC启用CMS。-XX:UseG1GC启用G1。-XX:MaxGCPauseMillis目标最大停顿时间G1/ZGC。-XX:InitiatingHeapOccupancyPercent触发并发GC的堆占用阈值。调优思路避免频繁Full GC增大堆、调整新生代/老年代比例、选择低延迟GC器。减少晋升优化对象生命周期让短期对象在Young GC时就被回收。分析GC日志使用-Xlog:gc*或-XX:PrintGCDetails输出详细日志借助工具如GCeasy、GCE Viewer可视化分析。6. 总结与要点回顾更新关键点说明对象出生地Eden区Young GC触发Eden区满Young GC算法复制算法Eden From Survivor → To Survivor对象年龄每熬过一次Young GC年龄1晋升条件1. 年龄超阈值2. To Survivor区空间不足3. 分配担保失败Survivor区两个永远一存一空角色每次GC后互换老年代GC触发老年代空间不足、晋升失败等老年代GC算法标记-清除、标记-整理取决于GC器Full GC整堆回收停顿最长应尽量避免元空间存类元数据GC独立于堆GC器选择Serial/Parallel/CMS/G1/ZGC根据吞吐、延迟需求选择理解完整的GC流程Young GC → 对象晋升 → 老年代GC/Full GC能帮助我们优化代码减少短命大对象创建避免内存泄漏。合理配置JVM参数根据应用特点选择GC器、调整各代大小、设置停顿目标。分析GC日志定位是频繁Young GC、过早晋升还是老年代瓶颈从而针对性调优。关键点说明对象出生地Eden区GC触发Eden区满核心算法复制算法Eden From Survivor → To Survivor对象年龄每熬过一次Young GC年龄1晋升条件1. 年龄超阈值2. To Survivor区空间不足3. 分配担保失败Survivor区两个永远一存一空角色每次GC后互换元空间存类元数据GC独立于堆理解Young GC的流程有助于我们优化代码减少短命大对象的创建减轻GC压力。合理配置JVM参数如-Xmn(新生代大小)、-XX:SurvivorRatio(Eden与Survivor比例)、-XX:MaxTenuringThreshold(晋升年龄)。分析GC日志能快速定位是频繁Young GC还是晋升导致的老年代问题。您的理解抓住了核心流程本文在此基础上进行了系统化的梳理、补充了细节如年龄阈值、分配担保和可视化说明使其成为一篇完整的入门指南。