
1. JVM垃圾回收器概述在Java虚拟机(JVM)中垃圾回收器(GC)负责自动管理内存回收不再使用的对象所占用的内存空间。作为Java开发者理解不同垃圾回收器的工作原理和适用场景对于优化应用性能、减少停顿时间至关重要。现代JVM主要采用分代收集策略将堆内存划分为新生代(Young Generation)和老年代(Old Generation)。新生代存放生命周期较短的对象老年代存放长期存活的对象。这种划分基于弱分代假说(Weak Generational Hypothesis)绝大多数对象都是朝生夕死的。2. 垃圾回收算法基础2.1 标记-清除算法(Mark-Sweep)标记-清除是最基础的垃圾回收算法分为两个阶段标记阶段从GC Roots开始遍历标记所有可达对象清除阶段回收未被标记的对象所占用的内存注意标记-清除算法会产生内存碎片可能导致后续大对象无法分配2.2 复制算法(Copying)复制算法将内存分为大小相等的两块每次只使用其中一块。当这块内存用完时将存活对象复制到另一块然后清除已使用的内存块。优点实现简单不会产生内存碎片适合对象存活率低的场景如新生代缺点内存利用率只有50%复制大量存活对象时效率低2.3 标记-整理算法(Mark-Compact)标记-整理算法在标记完成后将所有存活对象向一端移动然后直接清理掉边界以外的内存。特点解决了内存碎片问题适合对象存活率高的场景如老年代移动对象需要额外时间开销3. 分代收集与垃圾回收器3.1 新生代收集器Serial收集器单线程收集器采用复制算法工作时会暂停所有应用线程(Stop The World)适合客户端应用和小内存环境ParNew收集器Serial收集器的多线程版本采用复制算法需要与CMS收集器配合使用在单核CPU上性能可能不如Serial收集器Parallel Scavenge收集器关注吞吐量(Throughput)而非停顿时间采用复制算法适合后台运算而不需要太多交互的任务提供自适应调节策略(-XX:UseAdaptiveSizePolicy)3.2 老年代收集器Serial Old收集器Serial收集器的老年代版本采用标记-整理算法主要用于客户端应用和CMS的后备方案Parallel Old收集器Parallel Scavenge的老年代版本采用标记-整理算法JDK6开始提供完善了Parallel Scavenge的搭配CMS(Concurrent Mark Sweep)收集器以获取最短回收停顿时间为目标采用标记-清除算法分为四个阶段初始标记(Initial Mark)并发标记(Concurrent Mark)重新标记(Remark)并发清除(Concurrent Sweep)注意CMS会产生内存碎片在JDK9中已被标记为废弃JDK14中移除3.3 G1收集器(Garbage-First)G1是面向服务端应用的垃圾收集器主要特点将堆划分为多个大小相等的Region可预测的停顿时间模型整体基于标记-整理局部基于复制算法从JDK9开始成为默认收集器G1的工作阶段初始标记(Initial Mark)并发标记(Concurrent Mark)最终标记(Final Mark)筛选回收(Live Data Counting and Evacuation)3.4 ZGC收集器ZGC是JDK11引入的低延迟垃圾收集器停顿时间不超过10ms支持TB级堆内存基于Region的内存布局使用染色指针(Colored Pointers)技术JDK15成为生产可用版本4. 垃圾回收器选择与调优4.1 常见组合方式组合方式新生代老年代特点SerialSerial OldSerialSerial Old单线程适合客户端应用ParNewCMSParNewCMS注重响应时间适合Web应用Parallel ScavengeParallel OldParallel ScavengeParallel Old注重吞吐量适合后台计算4.2 关键参数配置常用JVM参数示例# 使用G1收集器 -XX:UseG1GC # 设置堆大小 -Xms4g -Xmx4g # 设置新生代大小 -Xmn1g # 设置G1最大停顿时间目标 -XX:MaxGCPauseMillis200 # 打印GC日志 -XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:gc.log4.3 调优建议新生代调优合理设置新生代大小(-Xmn)调整Eden和Survivor比例(-XX:SurvivorRatio)设置晋升老年代的年龄阈值(-XX:MaxTenuringThreshold)老年代调优对于CMS合理设置触发GC的阈值(-XX:CMSInitiatingOccupancyFraction)对于G1设置期望最大停顿时间(-XX:MaxGCPauseMillis)通用建议避免频繁Full GC监控GC日志分析GC频率和耗时根据应用特点选择合适的收集器组合5. 实战经验与问题排查5.1 常见GC问题频繁Full GC可能原因内存泄漏、老年代空间不足、大对象分配排查方法分析堆转储、检查对象引用链长时间GC停顿可能原因堆过大、收集器选择不当、系统资源不足解决方案考虑使用G1或ZGC、增加内存、优化代码内存碎片常见于CMS收集器解决方案定期Full GC(-XX:UseCMSCompactAtFullCollection)5.2 GC日志分析示例GC日志片段[GC (Allocation Failure) [PSYoungGen: 65536K-10752K(76288K)] 65536K-22016K(251392K), 0.0123456 secs] [Times: user0.03 sys0.01, real0.01 secs]关键信息解读GC原因Allocation Failure分配失败年轻代回收PSYoungGenParallel Scavenge回收前后内存变化65536K-10752K耗时0.0123456秒5.3 工具推荐命令行工具jstat监控GC统计信息jmap生成堆转储jstack获取线程栈信息可视化工具VisualVMJConsoleEclipse MAT内存分析工具线上监控Prometheus GrafanaArthasSkyWalking在实际项目中我遇到过因不合理对象分配导致的频繁GC问题。通过分析发现是缓存实现不当缓存了大量短期对象。解决方案是改用更合适的缓存策略并调整新生代大小最终将GC频率降低了80%。这个案例让我深刻体会到理解GC原理对于性能优化至关重要。