外观
Java 垃圾回收?
⭐ 题目日期:
美团 - 2024/4/12,字节 - 2024/12/10
📝 题解:
一、核心概念
堆内存结构
• 年轻代(Young Generation):存放新创建的对象,分为 Eden区 和两个 Survivor区(S0/S1)。
• 老年代(Old Generation):存放长期存活的对象(经过多次Young GC未被回收的对象)。
• 元空间(Metaspace):JDK8+替代永久代(PermGen),存放类元数据、常量池等。垃圾回收目标
• 自动回收:释放无引用对象占用的内存,防止内存泄漏。
• 内存整理:减少内存碎片,提高内存利用率。Stop-The-World(STW)
• 垃圾回收过程中会暂停所有应用线程,影响响应时间。
• 低延迟垃圾收集器(如G1、ZGC)通过并发标记、增量回收减少STW时间。
二、垃圾判定算法
引用计数法(Reference Counting)
• 每个对象维护一个引用计数器,引用增减时更新计数器。
• 缺点:无法解决循环引用问题(如A→B,B→A,但无外部引用)。可达性分析(Reachability Analysis)
• 从 GC Roots 出发,遍历对象引用链,未被引用的对象判定为垃圾。
• GC Roots 包括:
◦ 虚拟机栈(栈帧中的局部变量)引用的对象。
◦ 方法区中静态变量、常量引用的对象。
◦ JNI(本地方法)引用的对象。
三、垃圾回收算法
标记-清除(Mark-Sweep)
• 步骤:标记所有存活对象 → 清除未标记对象。
• 缺点:内存碎片化,可能触发频繁Full GC。复制算法(Copying)
• 步骤:将存活对象从Eden和Survivor区复制到另一块Survivor区,清空原空间。
• 优点:无碎片,适用于对象存活率低的场景(如年轻代)。
• 缺点:空间利用率低(需预留50%内存)。标记-整理(Mark-Compact)
• 步骤:标记存活对象 → 将对象向内存一端移动 → 清理边界外内存。
• 优点:避免碎片,适用于老年代。
• 缺点:移动对象耗时,STW时间较长。分代收集(Generational Collection)
• 核心思想:根据对象存活周期划分内存区域,采用不同算法。
◦ 年轻代:使用复制算法(Minor GC)。
◦ 老年代:使用标记-清除或标记-整理算法(Full GC)。
四、垃圾收集器
Serial 收集器
• 单线程,通过复制算法处理年轻代,标记-整理处理老年代。
• 适用场景:单核客户端应用(如Java桌面程序)。Parallel Scavenge / Parallel Old
• 多线程并行回收,关注吞吐量(吞吐量 = 用户代码时间 / (用户代码时间 + GC时间)
)。
• 参数控制:-XX:MaxGCPauseMillis
(最大GC停顿时间),-XX:GCTimeRatio
(吞吐量目标)。CMS(Concurrent Mark-Sweep)
• 目标:减少老年代回收的STW时间,通过并发标记实现低停顿。
• 回收流程:
1. 初始标记(STW):标记GC Roots直接关联的对象。
2. 并发标记:遍历对象图(与应用线程并发执行)。
3. 重新标记(STW):修正并发标记期间变动的引用。
4. 并发清除:清理垃圾(并发执行)。
• 缺点:内存碎片、CPU敏感、并发失败时退化为Serial Old。G1(Garbage-First)
• 内存划分:将堆划分为多个 Region,每个Region可以是Eden、Survivor或Old类型。
• 核心流程:
1. 并发全局标记(Global Marking):识别各Region存活对象比例。
2. 混合回收(Mixed GC):优先回收垃圾比例高的Region。
• 优点:可预测的停顿时间(-XX:MaxGCPauseMillis
),高吞吐与低延迟平衡。
• 适用场景:大堆(>4GB)、响应时间敏感的应用。ZGC(Z Garbage Collector)
• 目标:亚毫秒级STW停顿(<10ms),支持TB级堆内存。
• 核心技术:
◦ 染色指针(Colored Pointers):在指针中存储元数据,避免对象移动时的STW。
◦ 并发压缩:在应用线程运行时整理内存。
• 参数启用:-XX:+UseZGC
(JDK15+生产可用)。Shenandoah
• 类似ZGC的低延迟收集器,通过 Brooks指针 和并发压缩实现低停顿。
• 参数启用:-XX:+UseShenandoahGC
(需额外安装)。
五、内存分配与回收策略
对象分配规则
• 优先在Eden区分配:多数对象“朝生夕死”。
• 大对象直接进入老年代:避免在Eden区反复复制(通过-XX:PretenureSizeThreshold
设置阈值)。
• 长期存活对象进入老年代:对象年龄计数器(默认15次Minor GC后晋升,通过-XX:MaxTenuringThreshold
调整)。Minor GC 与 Full GC
• Minor GC:回收年轻代,触发条件为Eden区满。
• Full GC:回收整个堆(包括老年代和元空间),触发条件:
◦ 老年代空间不足。
◦ 元空间不足。
◦System.gc()
显式调用(可通过-XX:+DisableExplicitGC
禁用)。空间分配担保
• Minor GC前检查老年代剩余空间是否足够容纳年轻代所有对象。
• 若不足,触发Full GC(通过-XX:-HandlePromotionFailure
控制策略)。
六、调优与实践
常见调优参数
• 堆大小:-Xms
(初始堆)、-Xmx
(最大堆)。
• 年轻代大小:-Xmn
。
• 元空间:-XX:MetaspaceSize
、-XX:MaxMetaspaceSize
。
• 收集器选择:-XX:+UseG1GC
、-XX:+UseConcMarkSweepGC
。监控工具
• 命令行:jstat -gc <pid>
(实时GC统计)、jmap -heap <pid>
(堆内存快照)。
• 图形化工具:VisualVM、MAT(Memory Analyzer Tool)分析堆转储(Heap Dump)。避免内存泄漏
• 排查长生命周期集合类(如静态Map缓存未清理)。
• 避免未关闭的资源(数据库连接、文件流)。
• 使用弱引用(WeakReference
)管理缓存。
七、高级话题
三色标记算法
• 白色:未访问对象。
• 灰色:已访问但子节点未完全处理。
• 黑色:已访问且子节点处理完成。
• 并发标记问题:漏标(应用线程修改引用导致存活对象被误删),通过 增量更新(CMS) 或 原始快照(G1) 解决。安全点(Safepoint)与安全区域(Safe Region)
• 安全点:线程执行到特定位置(如方法调用、循环跳转)时才能暂停进行GC。
• 安全区域:线程在一段代码中引用关系不会变化,可随时响应GC。跨代引用(Inter-Generational References)
• 老年代对象引用年轻代对象时,需避免每次Minor GC扫描整个老年代。
• 解决方案:记忆集(Remembered Set)记录跨代引用(G1中每个Region维护一个RSet)。ZGC 的 NUMA 感知
• 针对非统一内存访问(NUMA)架构优化,优先在本地内存分配对象。