进程内存全景
进程实际占用的内存(RSS)远不止堆:堆(-Xmx)+ 元空间(-XX:MaxMetaspaceSize)+ 每线程栈(-Xss × 线程数)+ 直接内存(-XX:MaxDirectMemorySize)+ JIT 编译缓存(Code Cache)+ GC 开销 + JVM 自身。容器里配额只按 -Xmx 算是经典事故——堆没满,容器先 OOM,OOM Killer 杀进程时连个 Java 异常都来不及打印。JDK 8u191+ 对容器有了感知(UseContainerSupport),但仍建议显式设置各块上限。
元空间:类装到哪里了
元空间装类元数据,位于本地内存。正常服务类数量固定,元空间平稳;动态生成类是变数——CGLIB 代理、反射 inflate、Groovy/JSP 编译、字节码增强框架,每个都往里塞类。泄漏信号:MAT 里同名类一堆(不同 ClassLoader 各一份)、jstat -gcutil 的 M 列只涨不跌。防法:MaxMetaspaceSize 设上限,超了宁可 OOM 也好过吃穿容器。
直接内存:NIO 的地盘
// 直接内存:绕过 JVM 堆,操作系统直接管理
ByteBuffer buf = ByteBuffer.allocateDirect(1024 * 1024);
// 为什么用它:网络 IO 时省一次"堆内存 → 内核缓冲区"的拷贝
// 代价:分配/释放慢(靠 Cleaner 专职线程回收,时机不可控)
// 上限:-XX:MaxDirectMemorySize(默认≈-Xmx)
//
// Netty 的池化堆外内存(PooledDirectByteBuf)是集大成者:
// 按 ByteBuf 引用计数管理,用完必须 release()
// 忘 release 的代价:堆外只涨不跌,最终 Direct buffer memory OOM
// 开启 -Dio.netty.leakDetection.level=PARANOID 能抓到泄漏点排查工具:NMT 是主力
堆外问题 heap dump 无能为力,要用 Native Memory Tracking:启动加 -XX:NativeMemoryTracking=summary(detail 更细但 overhead 略高),运行时 jcmd PID VM.native_memory summary 看各区域占用,diff 前后快照看谁在涨。NMT 只统计 JVM 自己的账(堆、元空间、线程、Code Cache、GC),RSS 减去 NMT 总账的差额,多半就是 DirectBuffer 这类"自由发挥"的部分——对不上账时用 pmap 按地址段挨个认领。
一次 Netty 泄漏复盘
网关服务 RSS 每天涨 300M,heap dump 干干净净。NMT 显示 JVM 内部各区平稳——差额在堆外。开 Netty 泄漏检测 PARANOID 级,日志抓到 LEAK: ByteBuf.release() was not called,定位到一处异常分支提前 return 没释放 buffer。修复后 RSS 稳定。结论记两条:堆外问题先看 RSS 与 NMT 的差额;Netty 项目上线前把泄漏检测开成 SIMPLE 级做一次压测。下一篇收官,把二十篇串成一张地图。
评论 (0)