You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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对象泄漏

  1. 检查DBCP2连接池配置:确认maxTotal、maxIdle、minIdle等参数是否合理,排查代码中是否存在未正确关闭PreparedStatement/Statement对象的情况,导致这些对象进入Finalizer队列无法及时回收
  2. 验证Finalizer线程执行状态:多次抓取jstack快照,确认FinalizerThread是否有处理队列的痕迹;若线程长期停滞,需排查JDBC对象的finalize方法是否存在阻塞IO等耗时操作
  3. 临时替换验证:将DBCP2替换为HikariCP(默认关闭Finalize机制),观察RSS增长情况是否缓解,以此确认是否为JDBC对象Finalize流程导致的问题

针对glibc malloc内存池问题

  1. 限制arena数量:设置环境变量MALLOC_ARENA_MAX=4(建议不超过CPU核心数),重启服务后观察RSS增长,验证是否因glibc arena过度分配导致内存碎片化
  2. 使用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分析内存分配热点
  3. 升级glibc版本:当前使用的glibc 2.17存在已知的内存泄漏或碎片化bug,尝试升级到2.23+版本,验证问题是否解决

其他排查方向

  1. 排查JNI调用:检查服务中自定义JNI代码或第三方依赖的JNI实现,确认是否存在内存未释放的情况;可使用valgrind(注意会影响Java进程性能)进行内存检测
  2. 对比NMT快照:执行jcmd pid VM.native_memory baseline建立基准,间隔一段时间后执行jcmd pid VM.native_memory diff,对比内存增长趋势,定位异常区域
  3. 系统层面内存分析:使用smem、ps aux等工具,详细分析进程内存组成,确认是否有共享内存、文件映射等区域异常增长

内容的提问来源于stack exchange,提问作者hehe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:53:20