JVM内存诊断

问题:后台omc系统,经过压力测试之后,进程占用的操作系统内存比例一直居高不下。

怀疑系统可能存在内存泄漏。




排查思路:
  1. 确定问题范围
  2. 收集问题相关情报
  3. 根据具体情况,猜测原因
  4. 猜测原因,并验证
  5. 得出结论


问题解决步骤:
  1. 使用top命令查看问题进程top - 17:27:42 up 6 days, 6:29, 17 users, load average: 0.18, 0.06, 0.04
Tasks: 503 total,   1 running, 502 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.3%us,  0.4%sy,  0.0%ni, 99.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32987452k total, 23453436k used,  9534016k free,   343260k buffers
Swap: 67108856k total,    27052k used, 67081804k free, 10424144k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                        
 2424 root      20   0 14.1g 6.8g  20m S  3.3 21.7 176:35.71 java                                                                                                                     
 2757 root      20   0 9305m 490m  13m S  2.0  1.5   4:06.79 java                                                            
 2673 root      20   0 9909m 1.0g  13m S  1.3  3.3   6:30.64 java
  1.  其中,问题进程pid是2424,其所占物理内存比例是21.7%。
  2. 查看该虚拟机启动配置信息JAVA_OPTS="$JAVA_OPTS -d64 -server -Xms128m -Xmx6g -XX:MaxPermSize=1g -XX:+UseParallelGC -XX:ParallelGCThreads=2 -XX:NewRatio=3 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0"其中,虚拟机运行在server模式,最大堆空间6G,永久代最大值1G。
  3. 查看进程内存使用情况#jmap -heap 2424
Attaching to process ID 2424, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6442450944 (6144.0MB)
   NewSize                  = 33554432 (32.0MB)
   MaxNewSize               = 1610612736 (1536.0MB)
   OldSize                  = 100663296 (96.0MB)
   NewRatio                 = 3
   SurvivorRatio            = 4
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1609564160 (1535.0MB)
   used     = 398756176 (380.2835235595703MB)
   free     = 1210807984 (1154.7164764404297MB)
   24.77417091593292% used
From Space:
   capacity = 524288 (0.5MB)
   used     = 0 (0.0MB)
   free     = 524288 (0.5MB)
   0.0% used
To Space:
   capacity = 524288 (0.5MB)
   used     = 0 (0.0MB)
   free     = 524288 (0.5MB)
   0.0% used
PS Old Generation
   capacity = 4268752896 (4071.0MB)
   used     = 618764872 (590.1001663208008MB)
   free     = 3649988024 (3480.899833679199MB)
   14.495214107609943% used

46452 interned Strings occupying 4758616 bytes.其中,堆最大值6G,新生代1536M,老年代4071M。
  1. 查看进程内存映像信息#pmap -x 2424
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  java
0000000000600000       4       4       4 rw---  java
0000000000e2c000     936     816     816 rw---    [ anon ]
0000000640000000 4718592 4718592 4718592 rw---    [ anon ]
0000000760000000 1572864 1572864 1572864 rw---    [ anon ]
00000007c0000000   11520   11376   11376 rw---    [ anon ]
00000007c0b40000 1037056       0       0 -----    [ anon ]
0000003784200000     128     108       0 r-x--  ld-2.12.so
000000378441f000       4       4       4 r----  ld-2.12.so
0000003784420000       4       4       4 rw---  ld-2.12.so
0000003784421000       4       4       4 rw---    [ anon ]
0000003784600000       8       8       0 r-x--  libdl-2.12.so
0000003784602000    2048       0       0 -----  libdl-2.12.so
0000003784802000       4       4       4 r----  libdl-2.12.so
0000003784803000       4       4       4 rw---  libdl-2.12.so
0000003784a00000    1576     668       0 r-x--  libc-2.12.so
0000003784b8a000    2044       0       0 -----  libc-2.12.so
0000003784d89000      16      16       8 r----  libc-2.12.so
0000003784d8d000       4       4       4 rw---  libc-2.12.so
0000003784d8e000      20      20      20 rw---    [ anon ]
.
.
.
00007f4511f94000       8       8       0 r--s-  bootstrap.jar
00007f4511f96000      32      32      12 rw-s-  2424
00007f4511f9e000       4       4       4 rw---    [ anon ]
00007f4511f9f000       4       0       0 r----    [ anon ]
00007f4511fa0000       4       4       4 rw---    [ anon ]
00007fff23cc5000      84      36      36 rw---    [ stack ]
00007fff23ddf000       4       4       0 r-x--    [ anon ]
ffffffffff600000       4       0       0 r-x--    [ anon ]
----------------  ------  ------  ------
  1. total kB       14781668 7171772 7150640其中,永久代4719M,年轻代1573M。
  2. 分析和结论进程所占物理内存:32768M * 0.21 = 6881M 永久代和年轻代 :4719M + 1573M = 6292M两者计算较为接近,可认为进程占用的内存结果合理。
    虚拟机申请到的堆栈空间,最终并不会释放给操作系统。


验证内存映像信息获取的堆信息正确性
验证步骤:
  1. 编写可指定新生代和老年代内存占用空间的测试代码
  2. 启动测试程序,启动时指定虚拟机启动参数,以方便验证问题
  3. 测试程序在新生代占用50M的条件下,记录内存映像
  4. 测试程序在新生代占用100M的条件下,记录内存映像
  5. 测试程序在新生代占用200M的条件下,记录内存映像
  6. 在内存映像中分别查找50M,100M,200M和300M近似大小的记录,并进行位置比对
  7. 得出结论
实验步骤:
  1. 测试程序import java.util.ArrayList;
1. import java.util.List;

public class Test {

        private static List<Object> list = new ArrayList<Object>();
        private static int M_N = 200;

        public static void main(String[] args) throws InterruptedException {
                list.add(new byte[1024*1024*M_N]);
                System.out.println(M_N + "M");
                while(true) {
                        Thread.sleep(1000);
                }
        }
}
  1. 50M情况下的内存映像9934:   java -Xmx512m -Xmn128m Test
1. Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  java
0000000000600000       4       4       4 rw---  java
0000000000999000     132      12      12 rw---    [ anon ]
00000000e0000000  385024       0       0 rw---    [ anon ]
00000000f7800000    8192       0       0 -----    [ anon ]
00000000f8000000  131584   55568   55568 rw---    [ anon ]
0000000100080000 1048064       0       0 -----    [ anon ]
0000003784200000     128     108       0 r-x--  ld-2.12.so
000000378441f000       4       4       4 r----  ld-2.12.so
0000003784420000       4       4       4 rw---  ld-2.12.so
0000003784421000       4       4       4 rw---    [ anon ]
  1. 100M情况下的内存映像Address           Kbytes     RSS   Dirty Mode   Mapping
1. 0000000000400000       4       4       0 r-x--  java
0000000000600000       4       4       4 rw---  java
0000000000f36000     132      12      12 rw---    [ anon ]
00000000e0000000  385024  104448  104448 rw---    [ anon ]
00000000f7800000    8192       0       0 -----    [ anon ]
00000000f8000000  131584    2320    2320 rw---    [ anon ]
0000000100080000 1048064       0       0 -----    [ anon ]
0000003784200000     128     108       0 r-x--  ld-2.12.so
000000378441f000       4       4       4 r----  ld-2.12.so
0000003784420000       4       4       4 rw---  ld-2.12.so
0000003784421000       4       4       4 rw---    [ anon ]
  1. 200M情况下的内存映像16130:   java -Xmx512m -Xmn128m Test
1. Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  java
0000000000600000       4       4       4 rw---  java
0000000001832000     132      12      12 rw---    [ anon ]
00000000e0000000  385024  206848  206848 rw---    [ anon ]
00000000f7800000    8192       0       0 -----    [ anon ]
00000000f8000000  131584    2320    2320 rw---    [ anon ]
0000000100080000 1048064       0       0 -----    [ anon ]
0000003784200000     128     108       0 r-x--  ld-2.12.so
000000378441f000       4       4       4 r----  ld-2.12.so
0000003784420000       4       4       4 rw---  ld-2.12.so
0000003784421000       4       4       4 rw---    [ anon ]
  1. 分析并得出结论
    虚拟机堆内存512M,且新生代是128M,如果创建50M或者100M对象,则虚拟机会在新生代中创建;如果创建200M大小对象,因为新生代空间不够,虚拟机会选择在老年代直接创建。
    根据步骤2,55568kb与50M接近;步骤3中,104448kb与100M接近;步骤4中,206848kb与200M接近。依此可推知,pmap命令输出的第5行信息是指老年代,第7行是指年轻代。