Java服务堆外内存问题排查

一、简介:

  1. 堆内存排查

Java服务堆内存溢出导致oom解决方法一般都是生成dump文件然后使用Mat,jvisualvm这类工具打开,看哪个对象占用过大,这种请求比较好解决。

  • 生产dump文件有两种常用方案:
    • 可以启动服务时添加-XX:+HeapDumpOnOutOfMemoryError参数,当服务产生oom错误时会生产dump文件(推荐这种方案)
    • 使用jmap,jcmd下载当时的dump文件

  1. 非堆内存排查

而Java服务非堆内存溢出导致的oom或者由于进程内存占用过大被Linux系统自动kill掉,这种情况就无法使用dump文件这种方案了,下面主要介绍下非堆内存溢出的解决思路。

二、示例代码

使用ByteBuffer.allocateDirect分配直接内存,此内存属于非堆内存,所以不会被jvm的GC管理回收

public class DumpTest {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("程序启动成功!!");
        Thread.sleep(20000);
        // 在此打印之前执行strace命令
        System.out.println("循环创建ByteBuffer开始");
        for (int i = 0; i < 3; i++) {
            ByteBuffer bb = ByteBuffer.allocateDirect(1024 * 1024 * 10);
            bb.put((i + "AAaaa测试ByteBuffer内存").getBytes());
            Thread.sleep(10000);
            System.out.println("ByteBuffer对象:" + i + "--" + bb.getChar(0));
        }
        // 在此打印之后执行pmap、jcmd pid ...、cat /prop/pid/..命令
        System.out.println("循环创建ByteBuffer结束");
        Thread.sleep(10000000);
    }
}

三、命令执行

1、启动服务
]$ java -XX:NativeMemoryTracking=detail -jar -Xms32m -Xmx32m -XX:MaxDirectMemorySize=64m dump_test.jar
程序启动成功!!
循环创建ByteBuffer开始
ByteBuffer对象:0--ぁ
ByteBuffer对象:1--ㅁ
ByteBuffer对象:2--㉁
循环创建ByteBuffer结束
2、查看进程内存分配情况
]$ strace -f -e "brk,mmap,munmap" -p 5665
strace: Process 5665 attached with 17 threads
[pid  5668] mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdfa11d1000
[pid  5668] mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdfa07d0000
[pid  5668] mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdf7d0bd000
[pid  5665] --- SIGQUIT {si_signo=SIGQUIT, si_code=SI_USER, si_pid=8932, si_uid=1000} ---
[pid  5722] mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fdfa8019000

此处可以看到进程申请了三次10489856字节的内存区域(mmap(*)),和代码相呼应
mmap、mumap我的理解是申请和释放内存的意思,具体可以参考系统调用与内存管理(sbrk、brk、mmap、munmap)这篇文章

3、查看进程当前内存

按照常驻内存集RSS(表示该进程分配的内存大小)倒序排序

pmap -x 5665 | sort -k 3 -n -r > 5665_pmap.txt

结果:

total kB         2661600   50016   39468
00007fdfa07d0000   51208   20492   20492 rw---   [ anon ]
00007fdf7d0bd000   10244   10244   10244 rw---   [ anon ]
00007fdfc436b000   13840    7316       0 r-x-- libjvm.so
00007fdfa8cf5000    4352    2616    2616 rw---   [ anon ]
00007fdfa8b24000    1860    1860       0 r--s- rt.jar
00007fdfc0000000    1600    1516    1516 rw---   [ anon ]
00000000fe000000   33280     764     764 rw---   [ anon ]
00007fdfc5403000    1808     656       0 r-x-- libc-2.17.so
00007fdfc52ee000     652     652     652 r---- libjvm.so
00007fdfb0069000     472     472     472 rw---   [ anon ]
00007fdfc6057000     824     468     468 rw---   [ anon ]
00007fdf70000000     380     348     348 rw---   [ anon ]

解释:
第1列:Address: start address of map 映像起始地址
第2列:Kbytes: size of map in kilobytes 映像大小
第3列:RSS: resident set size in kilobytes 驻留集大小

通过strace、pmap内存地址对应可以确定进程占用了30m的内存。不过一直不太理解strace结果显示分配了三次内存同时生成了三次地址,但是pmap结果只有两个地址,把前两次的内存放在一起展示了。

4、查找内存地址

cat /proc/5665/smaps > 5665_smaps.txt

结果:

7fdf7d0bd000-7fdf7dabe000 rw-p 00000000 00:00 0 
Size:              10244 kB
Rss:               10244 kB
Pss:               10244 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:     10244 kB
Referenced:        10244 kB
Anonymous:         10244 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mp me ac sd
7fdfa07d0000-7fdfa39d2000 rw-p 00000000 00:00 0 
Size:              51208 kB
Rss:               20492 kB
Pss:               20492 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:     20492 kB
Referenced:        20492 kB
Anonymous:         20492 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mp me ac sd 

将pmap和strace命令展示的内存地址在smaps文件中找到内存的开始结束地址,供后续使用

5、查看内存内容

5.1、将内存内容dump下来
gdb attach 5665
// 此处有结果展示省略
(gdb) dump memory /home/用户名/5665_dump.mem 0x7fdf7d0bd000 0x7fdf7dabe000
(gdb) dump memory /home/用户名/5665_dump1.mem 0x7fdfa07d0000 0x7fdfa39d2000

注意:

  • 执行gdb命令前需要停止strace,否则会失败
  • dump memory后的0x…地址即为上面在smaps文件中找到内存的开始结束地址
5.2、查看dump文件
]$ strings 5665_dump.mem
2AAaaa
ByteBuffer
]$ strings 5665_dump1.mem 
1AAaaa
ByteBuffer
0AAaaa
ByteBuffer

可以看到打印内容确实包含了代码中的一些文字,但是中文没有展示。

四、Java的NativeMemoryTracking

使用此功能需要启动服务时添加-XX:NativeMemoryTracking=detail参数,执行命令:

]$ jcmd 5665 VM.native_memory detail > 6646_native.txt

结果:

5665:

Native Memory Tracking:

Total: reserved=1431356KB, committed=132388KB
-                 Java Heap (reserved=32768KB, committed=32768KB)
                            (mmap: reserved=32768KB, committed=32768KB) 
 
-                     Class (reserved=1056895KB, committed=4991KB)
                            (classes #442)
                            (malloc=127KB #154) 
                            (mmap: reserved=1056768KB, committed=4864KB) 
 
-                    Thread (reserved=17508KB, committed=17508KB)
                            (thread #18)
                            (stack: reserved=17432KB, committed=17432KB)
                            (malloc=57KB #92) 
                            (arena=20KB #31)
            ///此处有省略
            ///此处有省略
            ///此处有省略
[0x00007fdfc4dd8d72] Unsafe_AllocateMemory+0x82
[0x00007fdfb05b8427]
                             (malloc=30720KB type=Internal #3)

只能看到分配了30m的内存,信息比较少。没有内存的开始结束地址,因此不能看到内存的内容。不助于解决这次讨论问题,不过应该有其他应用场景。

五、总结

此篇文章主要讨论了解决使用ByteBuffer.allocateDirect申请内存导致堆外内存溢出的思路,遇到具体问题还是要具体分析。不过可以借鉴此篇文章,不会出现用不Mat,jvisualvm等工具就无从下手了。


参考文章:一次寻常的堆外内存泄漏排查

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值