# 系统莫名卡顿,JVM参数一行搞定——System.gc()的隐蔽陷阱
系统莫名卡顿JVM参数一行搞定——System.gc()的隐蔽陷阱背景线上系统突然开始间歇性卡顿。不是崩溃不是报错就是每隔一段时间咯噔一下——页面转几秒然后恢复。业务人员在群里反馈又卡了运维去查数据库和应用服务器一切正常。排查过程第一步排除数据库AWR报告拉出来缓存命中率正常慢SQL没有新增锁表没有IO正常。数据库没问题。第二步排除应用服务器硬件CPU使用率正常内存充足磁盘IO不高网络无延迟。硬件没问题。第三步盯JVM数据库和硬件都正常那问题大概率在JVM。用jstat监控GC情况jstat-gcutilpid1000每秒输出一次GC统计。盯了几分钟发现异常S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 45.23 67.82 12.56 93.12 89.45 156 1.234 3 2.876 4.110 0.00 45.23 67.82 12.56 93.12 89.45 156 1.234 3 2.876 4.110 0.00 45.23 78.45 12.58 93.12 89.45 156 1.234 3 2.876 4.110 0.00 45.23 89.12 12.61 93.12 89.45 156 1.234 3 2.876 4.110 0.00 45.23 5.34 13.02 93.12 89.45 157 1.256 3 2.876 4.134 0.00 45.23 12.67 13.05 93.12 89.45 157 1.256 4 4.123 5.381关键发现O老年代使用率只有12%~13%——老年代还很空FGCFull GC次数从3跳到4——触发了Full GCFGCTFull GC总耗时从2.876跳到4.123——这次Full GC耗时约1.2秒老年代才用了12%远没到阈值默认92%为什么触发Full GC第四步分析原因老年代很空却触发Full GC常见原因就那么几个原因可能性排除依据老年代自然填满排除使用率才12%Metaspace满排除CCS使用率稳定在89%没涨显式调用System.gc()嫌疑最大唯一能绕过阈值触发Full GC的手段JVM内部诊断命令可能但少见没有开启相关JMX操作System.gc()的调用来源有两种可能业务代码里有人写了System.gc()——想帮JVM回收内存实际是帮倒忙第三方库调用了System.gc()——有些库在做了大量反射操作或NIO Buffer分配后会调用System.gc()试图释放DirectByteBuffer不管是哪种结论一样有人好心办坏事。第五步应急处理不需要找到是谁调的生产环境排查调用栈需要重启加参数直接禁止System.gc()生效-XX:DisableExplicitGC加上这个JVM参数所有代码里的System.gc()调用都会被JVM忽略。重启后监控FGC不再无故触发系统恢复平滑。System.gc()为什么会卡顿System.gc()不是建议JVM做垃圾回收——在大多数JVM实现中它会触发一次Full GC全堆扫描压缩。Full GC要暂停所有应用线程Stop-The-World这段时间所有请求都卡着。小堆1~2GFull GC通常几百毫秒用户感知不明显大堆8~16GFull GC可能几秒甚至十几秒用户明显感到卡顿政务系统堆一般在4~8G一次Full GC停顿1~3秒正好是页面转了几秒然后恢复的体验。哪些库会调用System.gc()常见的几个库/场景为什么调System.gc()NIO DirectByteBuffer分配直接内存时如果堆外内存不够调用System.gc()试图释放无用的DirectByteBufferRMI分布式垃圾回收定期调用默认每小时一次反射工具库部分版本大量生成反射类后Metaspace膨胀试图用GC回收业务代码开发人员以为手动回收能提高性能JVM参数建议生产环境建议加上-XX:DisableExplicitGC加上之后NIO的DirectByteBuffer回收可能受影响。如果系统用了大量NIO比如Netty需要配合限制堆外内存-XX:MaxDirectMemorySize512m如果用了RMI需要缩短RMI的GC间隔默认太长容易积累大量垃圾-Dsun.rmi.dgc.server.gcInterval3600000 -Dsun.rmi.dgc.client.gcInterval3600000排查命令速查# 查看GC概况每1秒刷新jstat-gcutilpid1000# 查看GC原因需要开启GC日志# JDK 8:-XX:PrintGCDetails-XX:PrintGCDateStamps-Xloggc:/path/to/gc.log# JDK 11:-Xlog:gc*:file/path/to/gc.log:time,uptime:filecount5,filesize10m# 查看是谁调用了System.gc()jstackpid|grep-A5System.gc总结系统卡顿数据库和硬件都正常先盯JVM的GC情况老年代很空却Full GC第一反应就是System.gc()生产环境加上-XX:DisableExplicitGC禁止显式GC不要在业务代码里写System.gc()——你比JVM更不懂什么时候该回收