Java进程内存问题分析

生产环境上个别服务出现内存占用过高的情况。

运行环境:jdk1.8.0_171、Linux 64bit

其中一个服务的最大堆内存设置为1g的情况下,但rss达到3.4g。该服务的具体vm参数如下:

-server -Xms1g -Xmx1g  -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:ErrorFile=$AFA_HOME/log/grp_err.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$AFA_HOME/log/heap_dump.hprof -Djava.security.egd=file:/dev/./urandom -Xloggc:"${AFA_HOME}"/log/grp_gc"${CLASS_ARGS}".log

针对此情况对该服务进行了检查。

1. 生产环境服务检查

1.1 堆内存(heap)检查

执行jmap -heap $pid命令,可以看到MaxHeapSize的值是1024M,且当前的使用率不足50%,且afa平台日志中并没有出现“java.lang.OutOfMemoryError”,故可排除堆内存原因。

1.2 直接内存检查(direct memory)

未指定直接内存的最大值(-XX:MaxDirectMemorySize)时,直接内存的默认最大值等于Xmx,即1024M,且afa平台日志中并没有出现“java.lang.OutOfMemoryError: Direct buffer memory”,故可以断定直接内存是正常的。

1.3 元空间(metaspace)检查

根据服务的gc日志,可以看到metaspace占用的内存空间稳定在68M左右。故可以判断metaspace是正常的。

1.4 线程数检查

使用jstack $pid|grep Thread.State | wc -l命令统计服务进程的线程数,可以看到当前线程数是140,未指定线程栈大小(-XX:ThreadStackSize)的情况下,Linux 64bit操作系统的每个线程栈的最大值为1M,140条线程即占用140M,故可判断内存问题并非线程数造成的。

2. NMT分析

由上面的生产服务检查可知,该服务的堆内存、直接内存、元空间、线程数均正常,且当前该服务已经无法通过java监控命令分析其它内存域的使用情况,需要开启NMT(native memory tracking)才能进一步地分析其它部分的内存使用情况。

2.1 NMT介绍

NMT是Java HotSpot VM在JDK1.8之后新增的一项功能,可跟踪Java HotSpot VM的内部内存使用情况。

2.2 使用NMT检查内存泄漏

  1. 在JVM启动参数中加入-XX:NativeMemoryTracking=summary或者-XX:NativeMemoryTracking=detail。注意,该参数最好紧跟java命令的后面(例如,java -XX:NativeMemoryTracking=detail xxx xxx),和-D参数混在一起时会开启NMT失败。

进程启动成功后,使用jcmd <pid> VM.native_memory summary即可观察该进程的各种内存占用情况,如下图所示:

上图中的各项参数的描述如下:

reserved:操作系统预留的内存大小,即可划分的内存大小

committed:已划分的内存大小

Java Heap:堆内存,需要注意的时,此处的committed并不是指堆内存区域内部(jmap -heap <pid>)的使用情况,而是堆内存域在系统中占用的内存大小。

Class:元空间

Thread:线程栈内存

Code:生成的代码

GC:GC使用的数据,例如卡表等等

Compiler:生成代码时编译器使用的内存

Symbol:Symbols

Native Memory Tracking:NMT使用的内存

Internal:命令行工具、JVMTI等使用的内部内存

Arena Chunk:Memory used by chunks in the arena chunk pool

2.3 测试环境进行NMT分析

为尝试重现生产环境上出现的内存问题,在测试环境中做了以下操作:

  1. 开启NMT
  2. 对服务进行压力测试
  3. 使用jcmd <pid> VM.native_memory summary命令进行内存分析

但经过4个小时的压力测试,服务进程的内存没有很明显增长,内存稳定在1g。

2.4 生产环境开启NMT

由于测试环境没能重现,所以决定在生产环境开启NMT,等待服务运行一段时间后,通过jcmd <pid> VM.native_memory summary命令观察其内存变化。

另外,生产环境中的服务,之前使用了crontab跑了一个会导致服务Full GC的脚本(脚本中含有两条jmap -histo:live <pid>命令,该命令会使得java进程进行Full GC),每5秒会执行1次该脚本,所以决定把此不合理的脚本停止掉。

3. 新发现

3.1 猜测

测试环境中并没有定时地去跑导致服务不断Full GC的脚本,所以猜测服务的内存问题是否与Full GC有关系。

3.2 验证猜测

1. 使用以下参数启动服务,该参数指定使用G1垃圾收集器:

-server -Xms1g -Xmx1g -XX:+UseG1GC

2. 服务启动后,使用jcmd <pid> VM.native_memory summary命令查看服务的内存分布,以及使用ps -p <pid> -o rss命令查看该服务占用的物理内存,并保存下来,以便和后面作对比。

3. 执行以下命令,使得服务每隔3秒执行一次Full GC:

# 根据实际将此处的pid修改为服务的进程号

pid=9484

while true

do

  jmap -histo:live $pid

  sleep 3s

done

  1. 待服务持续Full GC一段时间后,再使用jcmd <pid> VM.native_memory summary命令查看服务的内存分布,及使用ps -p <pid> -o rss命令查看该服务占用的物理内存。
  2. 对比Full GC前后的内存变化,可以发现整个服务占用的物理内存越来越大,其中在不断增大的部分是在GC部分。
  3. 另外,启动服务时使用以下参数不会出现内存问题,以下的参数没有指定垃圾收集器,JDK1.8默认会使用平行收集器。

-server -Xms1g -Xmx1g

3.3 结论

jdk1.8.0_171中,JVM使用G1垃圾收集器(-XX:+UseG1GC),Full GC时GC占用的内存会越来越大,且占用的内存不会释放(测试时达到了GC部分最高占用了2g,继续Full GC可能会更大);使用默认的平行回收器时,Full GC时GC占用的内存一直稳定,长时间Full GC也不会出现增长。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值