Java进程(Jetty WebSocket)驻留内存过高原因排查求助
为什么Java进程的RSS远高于你计算的内存总和?
这个问题我之前帮团队排查过类似的,你的计算思路没问题,但确实漏了很多容易被忽略的内存开销——不管是JVM内部的还是操作系统层面的,我来给你逐一梳理:
JVM堆外的直接内存(
Direct ByteBuffer)
这绝对是最常见的“隐形内存”,尤其是像Jetty WebSocket这种大量用NIO的应用。Direct ByteBuffer是直接在操作系统的堆外分配内存,完全不属于JVM堆的统计范畴(不会计入你说的231MB堆Committed值),但会实实在在占用物理内存,反映在RSS里。WebSocket处理网络数据时会频繁用到直接内存做缓冲区,这部分很可能占了不小的比例。JVM内部的其他隐藏开销
除了堆和非堆,JVM自身还有不少内存消耗:- JIT编译的代码缓存:比如HotSpot的Code Cache,用来存储编译后的native代码,这部分属于非堆但有时候没被完全统计到;
- 类加载器的辅助结构、GC的内部数据(比如标记栈、回收队列)、线程的本地存储(TLS)等,这些都是JVM运行必需的内存,但不会被堆或非堆的常规统计覆盖。
操作系统层面的进程开销
RSS是操作系统统计的进程总驻留内存,包含很多JVM之外的部分:- 共享库/动态链接库:JVM本身依赖的
libjvm.so、libc.so等系统库,还有你应用用到的native扩展(比如Jetty的native优化库、加密库),这些库加载后会占用物理内存,虽然部分是多进程共享的,但操作系统还是会把它们计入单个进程的RSS(不同OS统计逻辑略有差异); - 页表(Page Tables):每个线程都需要页表来映射虚拟内存到物理内存,151个线程的页表加起来也是一笔不小的开销,线程越多这部分占比越大;
- 文件缓存与内核态内存:文件描述符本身占用的内存很小,但如果通过这些fd打开了大文件并被操作系统缓存,或者进程的内核栈、信号处理结构等,都会被计入RSS。
- 共享库/动态链接库:JVM本身依赖的
第三方库的native内存分配
很多Java库底层会调用native代码分配堆外内存:- Jetty的WebSocket底层实现会用native缓冲处理网络IO;
- 日志框架(比如Logback)如果用了native的磁盘缓冲;
- 加密、压缩类库的native实现,都会在JVM堆外分配内存,这些都不会被你的堆/非堆统计覆盖。
内存对齐与碎片化
操作系统和JVM分配内存都是以页为单位(通常4KB),内存块需要对齐到页边界,这会产生一些碎片内存。比如一个实际占用101KB的对象,会被分配104KB的物理内存,大量这样的碎片累积起来,也会让RSS比你计算的总和高不少。
如果你想验证这些部分,可以用这些工具:
- 用
jmap -directmemory <pid>查看直接内存的使用量; - 用
pmap -x <pid>打印进程的内存映射详情,能看到各个共享库、堆外内存区域的具体占用; - 用
jcmd <pid> VM.native_memory(需要开启-XX:NativeMemoryTracking=summary)来查看JVM的native内存明细。
内容的提问来源于stack exchange,提问作者Abhishek Kumar
相关产品推荐
相关产品推荐

