Java服务遭Linux OOM Killer杀死,求堆外内存泄漏排查方案
问题背景
运行约10天的Java服务,业务繁忙时被Linux OOM Killer杀死。服务堆内存配置为Xmx10G Xms6G,但OOM Killer日志显示RSS达24.5G:
[6252128.258453]Out of memory: Kill process 113627 (java) score 350 or sacrifice child
[6252128.258533]Killed process 113627 (java) total-vm:50134728kB, anon-rss:25062168kB, file-rss:0kB, shmem-rss:0kB
[7954659.707880][43116(bkmonitorbeat)]:gsch scan(inode=411811729,type=1,flags=0x0) -interrupted & wait(timeout=1000)
[7954659.709606][43116(bkmonitorbeat): gsch_scan(inode=411811729,type-1,flags-exe)-interrupted & waitdone
重启进程数天后,top显示RSS升至30G,怀疑存在堆外内存泄漏。
已执行排查操作
- 执行
jmap dump、jstack、pmap等命令,发现进程持有大量65508KB内存块;jmap中存在7206个java.lang.ref.Finalizer对象 - 通过OQL查询Finalizer的referents,发现主要为:
org.apache.commons.dbcp2.DelegatingPreparedStatement(2124个)org.apache.commons.dbcp2.DelegatingStatement(1940个)sun.security.ssl.SSLSessionImpl(460个)
- FinalizerThread状态:阻塞在
ReferenceQueue.remove(),线程栈如下:
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f96183e2800 nid=0x174a7 in Object.wait() [0x00007f95e4ecd000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x0000000540020d40> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
- 查看smaps文件,发现322个约64MB的内存块,累计超20GB;尝试用gdb导出内存文件,但因不熟悉内存结构无法进一步调试,怀疑泄漏与glibc的malloc内存池相关
- 环境依赖信息:
ldd /usr/lib64/jdk8/bin/java linux-vdso.so.1 => (0x00007ffd381f1000) libpthread.so.0 => /lib64/libpthread.so.0(0x00007fc9c1743000) libjli.so => /usr/lib64/idk8/bin/../lib/amd64/ili/libjli.so(0x00007fc9c152d000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fc9c1329000) libc.so.6 => /lib64/ibc.so.6 (0x00007fc9cof5b000) /lib64/ld-linux-x86-64.so.2 (0x00007fc9c195f000) ------- Installed Packages glibc.x86_64 2.17-317.el - 进程未配置
MALLOC_ARENA_MAX,重启时添加-XX:NativeMemoryTracking=detail参数,运行数天后执行jcmd pid VM.native_memory detail未发现异常
下一步排查方案
针对Finalizer队列堆积与JDBC对象泄漏
- 检查DBCP2连接池配置:确认
maxTotal、maxIdle、minIdle等参数是否合理,排查代码中是否存在未正确关闭PreparedStatement/Statement对象的情况,导致这些对象进入Finalizer队列无法及时回收 - 验证Finalizer线程执行状态:多次抓取jstack快照,确认FinalizerThread是否有处理队列的痕迹;若线程长期停滞,需排查JDBC对象的finalize方法是否存在阻塞IO等耗时操作
- 临时替换验证:将DBCP2替换为HikariCP(默认关闭Finalize机制),观察RSS增长情况是否缓解,以此确认是否为JDBC对象Finalize流程导致的问题
针对glibc malloc内存池问题
- 限制arena数量:设置环境变量
MALLOC_ARENA_MAX=4(建议不超过CPU核心数),重启服务后观察RSS增长,验证是否因glibc arena过度分配导致内存碎片化 - 使用malloc调试工具:
- 启用
MALLOC_TRACE:设置MALLOC_TRACE=/path/to/trace.log,运行服务后用mtrace工具分析内存分配与释放轨迹 - 替换为jemalloc:编译jemalloc并通过
LD_PRELOAD加载,启用内存统计(如MALLOC_CONF=prof:true,prof_prefix:/path/to/jemalloc.prof),生成profile后用jeprof分析内存分配热点
- 启用
- 升级glibc版本:当前使用的glibc 2.17存在已知的内存泄漏或碎片化bug,尝试升级到2.23+版本,验证问题是否解决
其他排查方向
- 排查JNI调用:检查服务中自定义JNI代码或第三方依赖的JNI实现,确认是否存在内存未释放的情况;可使用
valgrind(注意会影响Java进程性能)进行内存检测 - 对比NMT快照:执行
jcmd pid VM.native_memory baseline建立基准,间隔一段时间后执行jcmd pid VM.native_memory diff,对比内存增长趋势,定位异常区域 - 系统层面内存分析:使用
smem、ps aux等工具,详细分析进程内存组成,确认是否有共享内存、文件映射等区域异常增长
内容的提问来源于stack exchange,提问作者hehe
相关产品推荐
相关产品推荐

