Java应用问题定位系列——内存占用过高

定位Java程序内存使用过高或者内存泄漏的问题跟CPU也类似,一般可以分为以下3个步骤:

  1. 定位进程
  2. 定位线程
  3. 定位具体方法(代码部分)

一、定位进程

通过top -c(然后按Shift+M按内存排序),或者htop等工具定位到具体的高内存进程。假设定位到的进程ID为14279。

二、定位线程

2.1 通过top查看线程

top -H -p 14279(然后按Shift+M按内存排序)定位占内存的线程:

%Cpu(s):  0.5 us,  0.7 sy,  0.0 ni, 98.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8168236 total,   231696 free,  3660496 used,  4276044 buff/cache
KiB Swap:   969964 total,   969964 free,        0 used.  4197860 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
14293 weiping   20   0 4508772  97036  18112 S  10    12 152:35.42 java
14279 weiping   20   0 4508772  97036  18112 S  5.0  1.2   0:00.00 java
14282 weiping   20   0 4508772  97036  18112 S  0.0  1.2   0:00.37 java

2.2 通过ps统计下当前进程的线程数

ps p 14279 -L -o pcpu,pmem,pid,tid,time,tname,cmd |wc -l 

2.3 初步判断

通过以上二步确认是否线程开多了,还是单个线程内存占用过多导致。

  • 如果是线程过多,那么就要去排查具体原因。是服务器线程,还是业务代码中的多线程导致?
  • 如果是单线程内存占用,那么就要dump快照或者本地尝试模拟重现。

2.4 查看线程信息

通过thread tid直接查看指定线程的堆栈信息:

[arthas@42436]$ thread 91
"MQ-AsyncTraceDispatcher-Thread-ce0ebd2a-5807-4053-ae3c-7472fe4b5aef" Id=91 TIMED_WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@774405a1
    at sun.misc.Unsafe.park(Native Method)
    --  waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@774405a1
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
    at java.util.concurrent.ArrayBlockingQueue.poll(ArrayBlockingQueue.java:418)
    at org.apache.rocketmq.client.trace.AsyncTraceDispatcher$AsyncRunnable.run(AsyncTraceDispatcher.java:238)
    at java.lang.Thread.run(Thread.java:748)

Affect(row-cnt:0) cost in 2 ms.

更多官方详细文档:Arthas命令文档——thread

三、定位具体方法

3.1 通过jmap dump内存快照

如果是线上环境,注意dump之前必须先将流量切走,否则大内存dump是直接卡死服务。
```
# dump当前快照
jmap -dump:live,format=b,file=dump.hprof <pid>
# 触发full gc,然后再dump一次
jmap -dump:live,format=b,file=dump_gc.hprof <pid>
```

dump:live的作用是会触发Full GC,然后再dump数据,用作gc前后的数据做对比。

3.2 使用MAT分析

如果快照文件不大,可以下载到本地,然后通过MAT分析。

MAT快照文件分析结果示例

3.3 上传到fastthread.io分析

如果快照文件不大,也可以上传到https://fastthread.io/分析。

3.4 通过jhat分析

如果快照文件很大,可以在服务器上直接分析:

faceless@ttg12:~/tmp$ jhat dump.hprof
Reading from dump.hprof...
Dump file created Mon Jun 22 14:33:00 CST 2020
Snapshot read, resolving...
Resolving 36246 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

分析完成后,访问http://ttg12:7000,如下图:
image.png

点进入看实例数(图来自网络,这里只是示意):
image

找到数量大的业务类,比如上图中是Packet。然后一路点进去,跟踪引用路径,看到底是哪个类引用的。

3.5 通过Arthas分析

TO BE FINISHED——也可以通过服务器上的Arthas分析。
阅读 712

推荐阅读