当前JVM GC行为异常的明确信号包括:Full GC频繁(如每分钟多次)、单次GC暂停超1秒、老年代使用率长期>90%且不下降、出现“GC overhead limit exceeded”错误;应先通过日志和jstat确认问题,再选对收集器而非盲目调参。

怎么看当前JVM的GC行为是否异常
别一上来就调参数,先确认真有问题。常见异常信号包括:Full GC 频繁(比如每分钟好几次)、单次 GC pause 超过 1 秒、老年代使用率长期 >90% 且不下降、java.lang.OutOfMemoryError: GC overhead limit exceeded 报错。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 加启动参数
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log(JDK 8)或-Xlog:gc*:file=/path/to/gc.log:time,uptime,level,tags(JDK 11+),让 GC 日志落盘 - 用
jdk.jstat实时看:运行jstat -gc观察1s OGCMN/OGCMX(老年代初始/最大)、OGC(当前老年代容量)、OC(老年代已用)变化趋势 - 避免只看
GC count—— 更关键的是GC time占应用总运行时间的比例,超 10% 就该警惕
选对垃圾收集器比狂调参数更有效
JDK 版本和应用特征决定收集器下限。盲目套用 G1 或 ZGC 参数,可能让吞吐量暴跌。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- JDK 8 默认是
Parallel GC(吞吐优先),适合批处理类服务;若响应延迟敏感(如 API 接口),改用-XX:+UseG1GC,但需配-XX:MaxGCPauseMillis=200(目标值,非保证值) - JDK 11+ 可直接试
-XX:+UseZGC,但要求 Linux x64 + 64GB 以上内存,小堆( - 禁用
-XX:+UseConcMarkSweepGC(CMS 已在 JDK 9 中废弃,JDK 14 彻底移除),继续用等于埋雷 - G1 的
-XX:G1HeapRegionSize别乱设 —— 它由堆大小自动推导,手动指定错误值会导致 JVM 启动失败
堆内存大小不是越大越好,要匹配回收器特性
堆设太大,G1 和 ZGC 的并发标记阶段耗时会显著上升;设太小,又触发频繁 Young GC。关键是让对象“自然死亡”在年轻代。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 年轻代不要硬写
-Xmn—— 改用-XX:G1NewSizePercent/-XX:G1MaxNewSizePercent(G1 下推荐 20–40%),让 G1 自适应 - 老年代预留空间很重要:G1 需要足够空间容纳从年轻代晋升的对象,否则触发
Evacuation Failure,退化成 Full GC;可观察日志中是否频繁出现to-space exhausted - 避免
-Xms和-Xmx差距过大(如-Xms2g -Xmx16g)—— 动态扩容过程本身会触发 GC,且 OS 内存碎片化后,大页分配(-XX:+UseLargePages)可能失败
容易被忽略的元空间与直接内存泄漏
很多人盯着堆看,结果 java.lang.OutOfMemoryError: Metaspace 或 OutOfMemoryError: Direct buffer memory 突然炸了。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 元空间泄漏常源于反复加载/卸载类(如热部署、OSGi、大量动态代理),加
-XX:MaxMetaspaceSize=512m限制上限,并用jstat -gcmetacapacity查看实际使用 - Netty 类框架默认用堆外内存,
-Dio.netty.maxDirectMemory=512m必须显式设置,否则走 JVM 源码里的Runtime.getRuntime().maxMemory()计算,默认值可能远超预期 -
VisualVM或JMC的“Native Memory Tracking”(启动加-XX:NativeMemoryTracking=detail)能定位直接内存谁在占——别只信top的 RES 值
DirectByteBuffer 就卡在老年代不动。动手前,先用 jmap -histo 或 jcmd VM.native_memory summary 看清内存里到底躺着什么。











