【Java内存溢出排查】gc监测以及内存突增问题排查

本文详细记录了一次Java服务在测试环境中遭遇内存溢出问题的排查过程,通过分析内存泄漏、流量突增及特定代码行为,最终定位并解决了由不当ES查询引发的内存占用激增问题。

前情提要

文档:【Java内存溢出排查】测试环境服务器挂...

链接:http://note.youdao.com/noteshare?id=783e7ec89950f4167867ef3ef33470b6&sub=48AEFC6FDECB4C60869FAA5FABF57AB0

 

通过以下命令信息可以确定是内存溢出,且Full GC后内存无法得到回收

top -Hp 1 (发现JVM Thread占用 CPU200%)

jstat -gccause 1 3s (发现大量full gc,且full gc后内存没有得到回收)

jmap -heap 1 (发现新生代、老年代used占比均99.99%)

 

 

问题分析

 

新版本代码部署到预发布环境后,同样出现了频繁Full GC(1w+次),CPU飙高而堆内存回收不了的问题,在代码发布上线之前,必须排查出到底原因是什么?

之前针对测试环境的现场信息分析,基本可以确定不存在比较明显的内存泄漏(即使存在,也不会是导致服务挂掉的根本原因)

 

这里再次用新的方法说明为什么得出这个结论——

 

 

线索一:MAT(Memery Analysis Tools)指出的唯一内存泄漏可能

根据MAT对我们测试环境怪掉时的堆内存快照分析,给出的唯一的内存泄漏建议如下,换句话说,有一定量ParallelWebappClassLoader对象的引用出现了问题,导致GC无法正常回收这部分内存,那么一定量指的是多少?12.39%,也就是存在内存泄漏问题的话,对整个堆内存溢出问题的影响是非常小的。一般比较明显的内存溢出问题,占比会达到70%以上。

ParallelWebappClassLoader到底是不是内存泄漏?

如果不是ParallelWebappClassLoader对象引用造成的,服务器内存溢出的根因到底又是什么?

 

 

线索二:GC后的内存占用情况

 

下面是测试环境服务器正常情况下,执行jmap -histo:live pid | head -n 23 命令的截图,可以得到当前内存中排名前20的对象实例和class等信息。

 

这里说明下jmap -histo:live [pid]命令,执行后,将会触发一次Full GC,得到的执行结果是Full GC后的内存对象情况。

通过jstat -gccause [pid]命令也可以看到jmap执行后的GC原因,会显示 “Heap Inspection Initiated GC”

 

从instances列来看,[C、[I、[B排名靠前是正常的,毕竟是基础数据类型(char\int\byte)。

当时最先怀疑的是绿色框中的ConcurrentHashMap$Node对象,这可能是某些比较大的map造成的,一般内存泄漏也会是各种map或list持有引用导致的。

 

所以直接执行下面命令重点观察ConcurrentHashMap$Node对象的实例数(instances)

jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node

 

得到如下结果,简单总结一下结果,下面的执行信息太长可以跳过……

1)随着服务被持续调用,java.util.concurrent.ConcurrentHashMap$Node对象实例数会不断累加,但GC过后也得到了回收

2)一定时间段内,ConcurrentHashMap$Node对象的回收慢于其增长数量,回收效率比较低

num     #instances         #bytes  class name
----------------------------------------------
   1:        516515       77064424  [C
   2:         30478       39696288  [B
   3:        337998       29743824  java.lang.reflect.Method
   4:         16370       18795648  [I
   5:        510907       12261768  java.lang.String
   6:        374742       11991744  java.util.concurrent.ConcurrentHashMap$Node
   7:        222880       10698240  org.aspectj.weaver.reflect.ShadowMatchImpl
   8:        130860        7360464  [Ljava.lang.Object;
   9:        222880        7132160  org.aspectj.weaver.patterns.ExposedState
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374823       11994336  java.util.concurrent.ConcurrentHashMap$Node
  18:          5441        2834704  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7680         491520  java.util.concurrent.ConcurrentHashMap
 561:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 744:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 927:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1029:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3436:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374851       11995232  java.util.concurrent.ConcurrentHashMap$Node
  18:          5455        2835824  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7694         492416  java.util.concurrent.ConcurrentHashMap
 564:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 747:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 930:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1033:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1197:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375425       12013600  java.util.concurrent.ConcurrentHashMap$Node
  18:          5742        2858784  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7981         510784  java.util.concurrent.ConcurrentHashMap
 581:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 760:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 939:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1039:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1197:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374815       11994080  java.util.concurrent.ConcurrentHashMap$Node
  18:          5437        2834384  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7676         491264  java.util.concurrent.ConcurrentHashMap
 561:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 744:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 926:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1028:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374850       11995200  java.util.concurrent.ConcurrentHashMap$Node
  18:          5450        2835488  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7690         492160  java.util.concurrent.ConcurrentHashMap
 563:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 745:           150           2400  java.util.concurrent.ConcurrentHashMap$EntrySetView
 928:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1030:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1192:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1718:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375404       12012928  java.util.concurrent.ConcurrentHashMap$Node
  18:          5697        2855376  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7940         508160  java.util.concurrent.ConcurrentHashMap
 576:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 762:           152           2432  java.util.concurrent.ConcurrentHashMap$EntrySetView
 943:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1042:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1202:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1732:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3456:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374882       11996224  java.util.concurrent.ConcurrentHashMap$Node
  18:          5449        2835472  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7690         492160  java.util.concurrent.ConcurrentHashMap
 559:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 746:           151           2416  java.util.concurrent.ConcurrentHashMap$EntrySetView
 928:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1029:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1191:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1718:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375174       12005568  java.util.concurrent.ConcurrentHashMap$Node
  18:          5462        2838848  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7701         492864  java.util.concurrent.ConcurrentHashMap
 560:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 741:           153           2448  java.util.concurrent.ConcurrentHashMap$EntrySetView
 926:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1024:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1714:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        376732       12055424  java.util.concurrent.ConcurrentHashMap$Node
  18:          5818        2866688  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          8078         516992  java.util.concurrent.ConcurrentHashMap
 574:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 757:           154           2464  java.util.concurrent.ConcurrentHashMap$EntrySetView
 949:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1051:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1230:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1788:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3509:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;

 

所以分析ConcurrentHashMap$Node对象可能存在泄漏,在通过MAT查看该对象的引用情况(排除弱引用),根据shallow heap字段倒序排序,得到结果证实了MAT指出的内存泄漏问题对象ParallelWebappClassLoader,说明MAT分析是有道理的,所以可以确定这里存在比较“轻微”的内存泄漏问题。

 

为什么说是“轻微”的内存泄漏问题?接着就要分析下,服务挂掉的时候,这个ConcurrentHashMap$Node对象占用堆内存12307392b≈11.73M,而当时老年代大小为337M,所以不是它压垮的服务器。

这里也可以直接看下ParallelWebappClassLoader对象的Retained Heap(深堆内存)大小,这个值意味着如果该对象被回收,将获得多少的内存收益。

 

线索三:内存突增

 

当时只关注了内存泄漏这个点,忘记了流量突增或者是某些特定代码查询的数据量突增,也可能导致内存溢出,特别是在老年代设置得比较小的情况下。

而我们测试环境的流量不可能产生突增,基本就是测试同学在进行功能测试,QPS也比较低。从sky walking监控服务挂掉时的请求量也都还算正常

但看内存监控发现有内存的突增,14:53到14:54一分钟从402M突增到了462M,并且之后还在突增到483M(这时Full GC后老年代used占比依然是99.99%),测试环境堆的总大小500M,所以服务器内存溢出,应该是这个时间段的某段代码执行导致的

 

问题排查

 

思路一:排查接口

方向找到了,首先想到是分析关键时间段内的接口请求,能够一下子填满60+M的数据请求,接口耗时应该会挺长,因为在内存中遍历数据需要时间,再加上服务IO也需要时间。

不过由于测试环境日志被清理了,预发布环境有没有监控信息,从接口为入口的线索断掉了(况且接口响应慢也有可能是因为线程阻塞等其他资源竞争,没有说服力)

 

思路二:根据内存快照,对比突增内存对应的对象及其引用

没有接口日志也没关系,我对比了一下测试环境,服务器内存溢出时(下图1)的快照和服务器正常工作时(观察了比较久,数据稳定)的内存快照(下图2)

内存增加了47M(图3),还没算上其他的一些incoming引用对象的大小,那么这个突增的大小是对应得上前面分析得问题根因的。

(图1)

 

(图2)

(图3)

 

所以可以在服务挂掉时,且执行过GC后的堆内存快照中排查分析“[C”和“[B”这两块内存数据,可以更准确的找到这部分出问题的数据,最终找到问题代码。

先展开char[]对象和其引用,看看具体是什么东东导致比正常服务时多出来30M数据。

按照Shallow Heap(浅堆空间,即对象实际占用内存大小)倒叙排序,找一些问题对象看看(哪些是问题对象?1体积比较大的;2体积不大,但个数惊人的)

 

对比时突然发现当时测试环境怪掉时拿到的堆快照和服务正常时的快照数据几乎一致,暂时还不知道是不是文件下载错了,所以目前只能调整测试环境JVM配置,等测试环境再次出现问题时再dump一次堆快照,然后建立可用对照组进一步分析。


 

再次把服务器搞挂一次之后,拿到监控数据,频繁Full GC,各堆内存区域used 99%

此时的堆内存对象占用情况如下,基本和上一次挂掉的数据一致。

MAT建立对照组,分析问题数据,发现问题对象[C 的引用有一个ElasticSearchGroupUnitField,和客源匹配小组日记查询有关,是ES搜索服务提供的接口。

一个大char[]对象的实际内存占用了17141776b ≈ 16.34M,难以想象这个接口发布到线上,多线程请求执行时服务器能撑多久……

这就是导致测试环境服务器挂掉的根本原因!

 

 

问题代码分析

 

查询某个客户匹配的小组日记列表时,没有加入小组id条件,而设置的pageSize为1000条,在没有指定groupId的情况下,整个小组日记中搜索与之匹配的日记数据,那肯定是每次都是满载的1000条,而日记的字段又比较多,单个文档的数据量比较大。

这个业务方法在客源列表、客源详情、客源匹配日记接口等都有调用,所以将代码发布到测试环境后,针对性测试了几分钟服务就挂了,问题复现。

 

修复逻辑比较简单,就是加入需要查询的小组id作为查询条件,修复后,再次针对以上场景进行测试,GC回收正常,问题解决。

 

总结

 

1. 排查服务器内存性能问题应该从2个大方向获取信息进行分析:

          a. 内存泄漏

          b. 内存(流量)突增

   先确定是什么病,再对症下药,以免绕弯子…

 

2. 加强code review的执行力度,毕竟code review的成本比这样一顿排查和分析要小得多,反向的排查总是要复杂和困难的

 

最后,还是感谢 trouble maker,还有帮忙验证的测试同学!

 

<think>嗯,用户这次的问题是关于如何监控Tomcat内存使用以防止溢出,正好接续我上次关于内存溢出解决方案的讨论。用户可能是个运维工程师或Java开发者,正在生产环境遇到实际问题,情绪上有些焦虑——毕竟内存溢出会导致服务崩溃,SSH都连不上。 从引用内容看,用户特别关注三点:一是内存使用的动态监控(引用1提到并发量高时内存增长),二是长期运行后的累积泄漏(引用2描述内存从5%飙升到40%),三是不同启动方式下的监控差异(引用4提到Windows服务启动的特殊性)。需要覆盖命令行工具、图形工具和日志分析三种主流方式。 首先得强调监控的层次性:JVM自带工具最直接,但生产环境更依赖日志和远程监控。jstat和jconsole虽然强大,但很多服务器不允许装GUI,所以重点讲命令行和JMX远程连接。对于引用4提到的Windows服务启动问题,要单独说明注册表配置方式,避免用户踩坑。 用户没提具体Tomcat版本,但引用3提到Java 8的Metaspace,说明环境较新。监控方案需要兼容新旧版本,比如PermGen监控在Java 8+要调整为Metaspace。另外,用户可能没意识到线程泄漏也会导致内存问题,需要补充线程栈监控。 最后考虑给出实用建议:比如设置内存阈值报警(85%就预警),还有HeapDump参数配置技巧。用户上次提到问题持续一个月才解决,说明缺乏有效监控手段,这部分要详细展开。压力测试建议也很关键,引用1强调高并发场景,可以用JMeter模拟。 相关问题的设计要延续深度:内存分析工具对比、云环境监控差异、容器化部署的注意事项——这些都是用户后续可能遇到的延伸问题。</think>监控Tomcat内存使用是预防内存溢出的核心手段,以下是分层监控方案及实施步骤: --- ### 一、**基础监控工具** #### 1. **JVM内置工具** - **`jstat`(命令行实时监控)** 监控堆/非堆内存使用率及GC活动: ```bash jstat -gcutil <Tomcat进程PID> 1000 # 每秒刷新一次 ``` 输出示例: ``` S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 99.80 68.43 85.22 94.23 90.11 215 6.325 12 3.141 9.466 ``` - **关键列**: `O`:老年代使用率(>80%需警惕) `M`:Metaspace使用率(Java 8+) `FGC`:Full GC次数(突增可能泄漏)[^1][^3] - **`jconsole`/`VisualVM`(图形化监控)** 远程连接Tomcat(需启用JMX): ```bash # 在catalina.sh中添加 JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false" ``` 实时查看内存曲线、线程状态、类加载数。 --- ### 二、**日志分析与预警配置** #### 1. **GC日志监控** 启动参数添加GC日志记录: ```bash JAVA_OPTS="-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/tomcat/logs/gc.log" ``` 使用工具(如`GCViewer`)分析日志,关注: - **Full GC频率**:频繁Full GC可能内存不足 - **GC内存回收率**:回收率低说明存在无法释放的对象[^1][^4] #### 2. **内存溢出自动转储** 当OOM时自动生成堆转储文件: ```bash JAVA_OPTS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat/dumps/" ``` 用**Eclipse MAT**分析`heapdump.hprof`,定位泄漏对象(如占内存最大的`HashMap`)[^2][^3]。 --- ### 三、**生产环境监控体系** #### 1. **APM工具链** 工具 | 功能 | 关键指标 ---|---|--- **Prometheus + Grafana** | 时序数据可视化 | JVM内存使用率、GC时间、线程数 **Elastic APM** | 代码级追踪 | 慢方法检测、SQL查询分析 **Zabbix** | 阈值告警 | 内存>85%自动通知 #### 2. **Tomcat管理接口** 启用`manager-status`(需配置`conf/tomcat-users.xml`): ```xml <role rolename="manager-gui"/> <user username="admin" password="密码" roles="manager-gui"/> ``` 访问 `http://server:port/manager/status` 查看实时内存、会话数。 --- ### 四、**关键预防策略** 1. **阈值设置原则** - 堆内存(`-Xmx`):不超过物理内存70% - Metaspace(Java 8+):`-XX:MaxMetaspaceSize=256m`(默认无上限需手动约束)[^2][^4] 2. **定期压力测试** 用JMeter模拟高并发,观察内存增长斜率: ```bash jmeter -n -t test_plan.jmx -l result.jtl ``` 3. **容器化部署建议** Docker中限制容器内存,避免单个Tomcat耗尽宿主机资源: ```bash docker run -d --memory=2g --name=tomcat-app tomcat:9 ``` --- ### 五、**常见内存泄漏场景排查 场景 | 检测方法 | 修复方案 ---|---|--- **未关闭流** | `lsof -p <PID>`查打开文件数 | `try-with-resources`自动关闭 **静态集合累积** | MAT分析`java.util.*`对象 | 改用WeakReference或定期清理 **线程泄漏** | `jstack <PID>`检查线程名 | 使用线程池并设超时时间 **Session堆积** | `manager/status`查会话数 | 配置`web.xml`中`<session-timeout>`[^2][^3] --- > **最佳实践**:某电商平台通过`Prometheus`监控发现Metaspace持续增长,分析为热部署导致类加载残留。通过添加`-XX:+CMSClassUnloadingEnabled`参数,Metaspace使用率稳定下降40%[^1][^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值