文章内容收录到个人网站,方便阅读:hardyfish.top/

现象分析

监控 JVM GC 情况

通过 JVM 选项打开 GC 日志

可以通过 GC 日志 观察 GC 频率:

# JDK 8
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
​
# JDK 9+
-Xlog:gc*:file=gc.log:time,uptime,level,tags

然后执行:

tail -f gc.log

示例 GC 日志:

[GC (Allocation Failure) [PSYoungGen: 256M->64M(512M)] 512M->320M(1024M), 0.015s]
  • PSYoungGen: 256M->64M(512M) → 年轻代 GC,清理后剩 64M
  • 0.015s → GC 耗时 15ms
  • Allocation Failure → 由于分配失败触发 GC

如果 PSYoungGen 频繁触发,则表示年轻代回收频率过高!

通过 jstat 监控 GC

使用 jstat 观察年轻代的 分配速率 & GC 频率

jstat -gc <pid> 1000

示例输出:

 S0C    S1C   S0U   S1U   EC     EU       OC     OU     MC    MU     CCSC  CCSU   YGC     YGCT   FGC    FGCT     GCT   
 512.0  512.0  0.0   256.0 4096.0  1024.0  8192.0  4096.0 1024.0  512.0  512.0 256.0   14567  512.32  123  32.89  545.21 

使用 VisualVM 监控 GC

  1. 启动 VisualVM
  2. 连接 Java 进程
  3. 选择 监视(Monitor)-> GC 统计
  4. 观察 Eden & Survivor 区的变化

如果 Eden 区持续增长,并且 YGC 次数飙升,则说明年轻代 GC 过于频繁。

定位问题代码

使用 jmap 分析对象占用

jmap -histo:live <pid> | head -20

示例输出:

  #num   #instances   #bytes  class name
----------------------------------------------
    1:   150000       9600000  [C
    2:    50000       4800000  java.lang.String
    3:    40000       3200000  java.util.HashMap$Node
    4:    30000       2800000  java.lang.Integer
    ...

采样分析短生命周期对象

使用 jprofilerAsync-Profiler 进行对象分配分析:

# 运行 Async-Profiler 采样 30 秒
./profiler.sh -d 30 -f heap.svg <pid>

找到 GC 频繁回收的热点代码,如 ListMap 频繁创建后立即释放。

解决方案

调整 Eden 区大小

可以适当增大:

-XX:NewRatio=2  # 年轻代占堆内存的 1/3
-XX:SurvivorRatio=6  # Eden : Survivor = 6:1

减少 GC 触发频率,提高对象存活率。

预分配对象,减少短命对象

短生命周期对象会快速进入 Eden,导致频繁 GC:

// 优化前(大量创建临时对象)
for (int i = 0; i < 10000; i++) {
    String s = new String("hello");  // 每次都会创建新的 String
}

优化后,使用 String Pool 或对象重用

for (int i = 0; i < 10000; i++) {
    String s = "hello";  // 直接引用字符串常量池
}

使用对象池(Object Pool)

如果高频创建对象,如 ThreadConnection,可以使用 对象池

// 使用线程池代替频繁创建线程
ExecutorService executor = Executors.newFixedThreadPool(10);

避免频繁创建销毁对象,降低 GC 压力。

避免 SoftReference/WeakReference 过多

如果 SoftReferenceWeakReference 太多,可能导致频繁 GC 回收:

SoftReference<byte[]> ref = new SoftReference<>(new byte[1024 * 1024]);

尽量避免短时间大量创建 SoftReference,避免触发 GC。

降低 Survivor 区溢出

当 Survivor 区过小,新生代对象过快晋升到老年代,可能会导致 Full GC 过多

-XX:SurvivorRatio=8  # Eden:Survivor = 8:1

让对象在 Survivor 区存活更久,减少老年代晋升压力。

总结

优化方向方案
监控 GC 频率-XX:+PrintGCDetails / jstat -gc <pid>
分析对象分配jmap -histo / Async-Profiler
调整 Eden 大小-XX:NewRatio=2
减少临时对象使用对象池、缓存,避免 new String()
减少 Survivor 溢出-XX:SurvivorRatio=8,减少对象晋升老年代
优化 SoftReference避免过多软引用
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]