定位基于Java的PostgreSQL CDC变更同步应用内存泄漏问题
定位Java进程RSS远大于堆内存的问题步骤
1. 拆解进程内存的具体构成
先通过系统命令明确内存占用的细分项,避免只看htop的整体数值:
- 执行
pmap -x <进程PID>,查看内存段的类型(匿名内存、文件映射、共享内存)和大小,重点关注anon段(堆外内存、JNI分配的内存都归类于此) - 用
smem -t -k -u -p <进程PID>,更直观地展示进程的内存占用(包括RSS、PSS、USS等指标)
如果有JDK环境,直接用jcmd <进程PID> VM.native_memory summary查看JVM的堆外内存分配详情(若未开启Native Memory Tracking,可临时执行jcmd <进程PID> VM.native_memory tracking=detail开启),该命令会列出直接内存、线程栈、元空间、JNI内存、代码缓存等非堆内存的占用情况。
2. 排查堆外内存(Direct ByteBuffer)
Java的ByteBuffer.allocateDirect()分配的内存不在堆中,默认不会被常规Java分析工具统计:
- 在VisualVM的Profiler面板勾选“Direct Buffers”,查看这类对象的实例数和占用大小
- 执行
jmap -histo:live <进程PID>,搜索java.nio.DirectByteBuffer,确认是否存在大量未回收的实例——如果是,说明代码或依赖库(如JDBC驱动)未正确释放直接内存
3. 验证JNI/C端内存情况
即便怀疑不是libpq,也需排除C端内存泄漏可能:
- 检查libpq调用逻辑:确认每次调用后都执行了
PQclear、PQfreemem等释放函数,避免C端内存未回收 - 测试环境可使用
valgrind --tool=massif <启动Java应用的命令>,跟踪C端内存分配轨迹;生产环境无法用valgrind的话,用perf record -g -p <进程PID>采样JNI调用热点,排查是否存在重复分配不释放的逻辑
4. 检查线程栈内存占用
每个Java线程默认栈大小为1M(可通过-Xss调整),线程数量过多会累积大量内存:
- 执行
jstack <进程PID>统计线程总数,或在htop中按H键查看进程的线程数 - 若线程数异常偏高,排查线程池配置是否合理、异步任务是否存在线程泄漏(如未正确关闭线程池)
5. 排查内存映射与第三方库
查看进程的内存映射文件,定位大内存区域的来源:
- 执行
ls -l /proc/<进程PID>/maps,查找大尺寸的匿名映射或文件映射,结合业务代码判断是否为第三方库(如JDBC、libpq)的内存映射未释放
6. 验证GC对RSS的影响
强制触发Full GC,观察RSS变化:
- 执行
jcmd <进程PID> GC.run,之后查看htop中的RSS数值- 若RSS明显下降,说明是堆外内存(如Direct ByteBuffer)未被及时回收
- 若RSS无变化,大概率是JNI/C端内存泄漏、线程栈或元空间这类不会被GC回收的内存导致
内容的提问来源于stack exchange,提问作者Muhammad Iqbal
相关产品推荐
相关产品推荐

