VisualVM 分析full GC问题记录

本文介绍了一款Java应用遭遇的内存溢出(OOM)问题及其解决方案。通过对GC日志的分析,发现老年代内存持续增长,最终定位到异步批量插入数据库时,由于LinkedBlockingQueue容量设置过大导致内存泄露。通过调整队列容量并优化插入机制,有效解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:JAVA APP,主要功能是处理日志并存入db

现象:运行一段时间就出现OOM问题,查看GC log发现运行没多久就一直Full GC,并且抛出OOM的异常。

[Full GC (Ergonomics) [PSYoungGen: 529920K->525999K(614912K)] [ParOldGen: 1398052K->1397869K(1398272K)] 1927972K->1923868K(2013184K), [Metaspace: 33827K->33827K(1079296K)], 4.1812153 secs] [Times: user=32.38 sys=0.07, real=4.19 secs]
[Full GC (Ergonomics) [PSYoungGen: 529920K->525483K(614912K)] [ParOldGen: 1397869K->1397848K(1398272K)] 1927789K->1923331K(2013184K), [Metaspace: 33832K->33832K(1079296K)], 5.6714054 secs] [Times: user=43.50 sys=0.09, real=5.67 secs]

GC日志中老年代非常大,而且回收效果也不明显。遇到这种问题,第一感觉还是有内存泄露,虽然Java能自己回收内存,但是不能保证我自己写的程序没有问题,比如曾经犯过一个错误往list一直add object,却没有remove,并且list也不释放,导致list越来越大,最后内存OOM。本着大胆假设,小心求证的理念,开始排查问题。

遇到内存问题,最好是希望能够直观的看到Java程序堆中现在有哪些对象,有哪些对象数目一直在递增而没有被回收。为此需要借助工具来排查了,visualVM是非常好的能满足需求的一个工具。

启动visualVM,启动程序,通过Visual GC查看内存情况。

从上图中,我们很明显发现程序的Old Generation占用空间是在不断地上涨,并且没有明显下跌,说明生成的对象都是存在内存里面的,并没有被释放掉。接下来查看一下内存中有哪些对象

使用Sampler标签页中的Memory 采集功能,查看内存中当前是哪些对象,从采样结果来看Request对象是非常多的,而且一直在递增。Request对象在程序中使用较多,接下来看下是哪个线程一直在积压Request

查看Per thread allocations,很容易看出insertRequestList线程分配的内存最多。

对照程序我恍然大悟,发现问题就出在插入数据上。程序中插入数据库采用异步批量插入方式,当生成一个Request对象就存入一个LinkedBlockingQueue,集满一定量进行批量插入。而我设置的LinkedBlockingQueue初始化了一个非常大的capacity,这就导致来不及插入的Request全部都堆积在LinkedBlockingQueue中了。

临时解决方案:将LinkedBlockingQueue的capacity设置小一些,并且采用put方式插入Request到LinkedBlockingQueue,当插入来不及的时候就等待。

修改代码后再来看下visualVM结果:

old Generation每次GC能回收大量的Object,不会出现Full GC了。

Request对象数量也并不是一直在增长了。但是InsertRequestThread线程仍然是分配了很多的内存,可以得知该线程必然还是积压了大量的Request对象的。因为只采用了临时解决方案,mysql插入数据速度上不去,积压在所难免。

 

附录:

visualVM远程监控

在执行Java 应用程序的服务器上先生成一个jstatd.all.policy

grant codebase "file:${JAVA_HOME}/lib/tools.jar" {
   permission java.security.AllPermission;
};

然后执行命令

nohup ${JAVA_HOME}/bin/jstatd -J-Djava.rmi.server.hostname=10.111.123.234 -J-Djava.security.policy=jstatd.all.policy -J-Dcom.sun.management.jmxremote.authenticate=false -J-Dcom.sun.management.jmxremote.ssl=false -J-Dcom.sun.management.jmxremote.port=8888 &  

然后在本地启动visualVM,Remote添加10.111.123.234,默认端口1099就可以监控服务器上的程序运行状况了,非常方便。

如果出现access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")报警,则建议将$JAVA_HOME和jstatd.all.policy都使用绝对路径。

05-14
<think>好的,用户现在想了解Java中的Full GCFull Garbage Collection)的原因及优化方法。我需要结合之前的对话历史和提供的引用内容来构建回答。首先,回顾用户之前的问题,他们询问了MyBatis的一对多映射配置,现在转向了JVM的垃圾回收机制,特别是Full GC。这可能意味着用户正在处理性能调优的问题,可能在他们的应用中遇到了Full GC频繁发生的情况,导致应用停顿或性能下降。 首先,我需要明确Full GC的定义和触发原因。根据引用[1]、[3]和[4],Full GC涉及整个堆内存(包括年轻代和老年代)以及方法区的回收,是重量级操作,会导致Stop-The-World停顿。用户可能已经知道这些基本概念,但需要深入的原因分析和优化策略。 接下来,我需要列举Full GC的常见原因。引用中提到内存泄漏、对象晋升过快、System.gc()调用、堆内存设置不当、大对象分配、元空间不足、GC策略不合理等。需要详细解释每个原因,并结合例子帮助用户理解。例如,内存泄漏可能导致老年代逐渐填满,最终触发Full GC,但回收效果差,频繁触发。 然后,优化策略部分需要涵盖内存分析、堆大小调整、避免手动触发GC、分代优化、选择合适GC算法、大对象处理、元空间监控、代码优化等。引用[2]提到G1垃圾回收器的使用,可以建议用户根据应用场景选择合适的GC算法,比如G1或ZGC来减少停顿时间。 还需要注意用户可能遇到的常见问题,如如何监控Full GC、如何分析内存泄漏等。引用[4]提到监控GC日志和堆内存使用情况,可以推荐工具如VisualVM、MAT等,帮助用户实际操作。 此外,用户可能对如何具体设置JVM参数感兴趣,比如调整-Xmx、-Xms,或者使用-XX:+UseG1GC等。需要给出具体的参数示例,并解释每个参数的作用,如避免显式GC调用使用-XX:+DisableExplicitGC。 最后,确保回答结构清晰,分点列出原因、优化策略和常见问题,使用用户提供的引用内容作为支持,比如引用[1]说明手动触发Full GC的方法,引用[4]强调监控的重要性。同时,用户可能需要实际案例或步骤指导,因此在优化策略中加入分析步骤,如生成Heap Dump,使用工具分析等。 总结,用户的需求是全面了解Full GC的原因及优化方法,可能实际应用中遇到了性能问题,需要具体的解决方案和调优建议。因此,回答需要详细、结构化,并结合实际案例和工具推荐,帮助用户有效解决问题。</think>### Java Full GC 原因分析及优化策略 --- #### **一、Full GC 核心机制** Full GC(完全垃圾回收)是 JVM 对**整个堆内存(年轻代+老年代)及方法区(元空间)**的全局回收操作,具有以下特点: - **Stop-The-World(STW)**:暂停所有应用线程,导致服务延迟[^1][^3] - **触发条件复杂**:老年代空间不足、方法区溢出、显式调用`System.gc()`等[^1][^3] - **性能影响显著**:回收时间与堆内存大小成正比,通常超过 1 秒即需优化[^4] --- #### **二、Full GC 常见原因** | **原因分类** | **典型场景** | **关联引用** | |----------------------|-----------------------------------------------------------------------------|------------| | **内存泄漏** | 对象长期被错误引用(如静态集合、未关闭的连接池)导致老年代无法回收[^3] | [^1][^3] | | **对象晋升过快** | 年轻代空间过小或 Survivor 区配置不合理,对象过早进入老年代 | [^4] | | **显式调用 System.gc** | 代码中直接调用`System.gc()`或通过 RMI 等框架间接触发 | [^1] | | **堆内存分配不合理** | 老年代空间不足(如`-Xmx`设置过小)或元空间(`-XX:MetaspaceSize`)溢出 | [^2][^4] | | **大对象分配** | 直接进入老年代的大对象(如未压缩的图片、缓存数据)占用过多空间 | [^3] | | **GC 策略不匹配** | 选择低效的垃圾回收器(如 Parallel GC 处理大堆) | [^2] | --- #### **三、优化策略与实战方案** ##### **1. 内存分析与泄漏排查** - **步骤 1:生成 Heap Dump** ```bash # 通过 jmap 导出堆内存快照 jmap -dump:format=b,file=heapdump.hprof <pid> ``` - **步骤 2:使用工具分析** 推荐工具:Eclipse MAT、VisualVM - **Dominator Tree**:定位内存占用最高的对象 - **Leak Suspects Report**:自动检测潜在泄漏点[^3] ##### **2. 堆内存配置优化** - **调整年轻代与老年代比例**(适用于 CMS/G1) ```bash # 年轻代占比(默认 1/3) -XX:NewRatio=2 # Eden 与 Survivor 区比例(默认 8:1:1) -XX:SurvivorRatio=8 ``` - **避免元空间动态扩展** ```bash # 固定元空间大小 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M ``` ##### **3. 分代回收策略优化** - **年轻代调优** ```bash # 增大年轻代空间(降低对象晋升频率) -Xmn512m # 设置晋升阈值(默认 15) -XX:MaxTenuringThreshold=10 ``` - **大对象直接进入老年代** ```bash # 对象超过 4MB 直接分配至老年代(默认 0,表示由 G1 自动管理) -XX:PretenureSizeThreshold=4194304 ``` ##### **4. 选择合适的垃圾回收器** | **GC 类型** | **适用场景** | **配置示例** | |------------|-------------------------------|---------------------------------------| | **G1** | 大堆(>4GB)、低延迟需求 | `-XX:+UseG1GC -XX:MaxGCPauseMillis=200` | | **ZGC** | 超大堆(TB 级)、极低停顿 | `-XX:+UseZGC -Xmx16g` | | **Shenandoah** | 平衡吞吐量与延迟 | `-XX:+UseShenandoahGC` | ##### **5. 其他关键配置** - **禁止显式触发 Full GC** ```bash -XX:+DisableExplicitGC # 屏蔽 System.gc() ``` - **Full GC 日志监控** ```bash -XX:+PrintGCDetails -Xloggc:/path/to/gc.log ``` --- #### **四、高频问题解答** 1. **如何判断 Full GC 是否频繁?** - 监控`jstat -gcutil <pid>`中的 **FGCT(Full GC 总耗时)** - 日志中搜索关键字 **`Full GC`** 统计频率 2. **Full GC 后内存未释放怎么办?** - 检查内存泄漏(参考 MAT 分析) - 增大堆内存或优化对象生命周期管理 3. **G1 如何减少 Full GC?** - 启用并发标记:`-XX:+G1ConcurrentMark` - 避免 Mixed GC 失败:调整`-XX:InitiatingHeapOccupancyPercent`(默认 45%) --- #### **五、应用场景与案例** - **电商大促**:通过增大年轻代和调整 G1 的`MaxGCPauseMillis`,将 Full GC 频率从 10 次/分钟降至 0.5 次/小时[^4] - **实时交易系统**:使用 ZGCFull GC 停顿时间从 2 秒缩短至 10ms 以内 --- ### 相关问题 1. 如何通过 JVM 参数限制年轻代对象晋升到老年代的速度?[^1][^4] 2. G1 垃圾回收器的 Mixed GCFull GC 有什么区别?[^2] 3. 哪些工具可以实时监控 Java 应用的 Full GC 状态?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值