K8s容器内存与JVM内存占用差异过大问题咨询
排查容器内存超出JVM内存的常见原因及方法
以下是针对容器内存(3.5GB)远大于JVM内存(1.5GB)的具体排查步骤:
1. 精确核算JVM的实际总内存占用
很多时候我们只关注了JVM堆内存,但JVM的内存消耗是多部分的总和:
- 用
jstat -gc <JVM进程PID>查看堆内存(Eden/Survivor/Old)和元空间(Metaspace)的占用; - 开启
-XX:NativeMemoryTracking=summary参数后,执行jcmd <JVM进程PID> VM.native_memory summary,可以看到直接内存、线程栈、JNI内存、JVM本身的进程开销等所有非堆内存的占用情况,确认JVM实际总内存是否真的只有1.5GB。
2. 检查容器内的其他进程
容器中可能存在除SpringBoot应用外的其他进程,消耗额外内存:
- 执行
docker exec <容器ID> ps aux,列出容器内所有进程,查看每个进程的RSS(常驻内存)占用,将所有进程的RSS相加,对比容器总内存。常见的额外进程包括日志收集sidecar、初始化脚本残留进程等。
3. 排查Kafka Streams的RocksDB内存占用
Kafka Streams依赖RocksDB做状态存储,RocksDB的缓存是独立于JVM堆的本地内存,很容易被忽略:
- 检查Kafka Streams配置:
cache.max.bytes.buffering(默认100MB)控制内存中缓存的流数据,rocksdb.block.cache.size控制RocksDB的块缓存大小(默认会占用系统可用内存的一定比例); - 如果状态存储数据量较大,RocksDB的缓存可能占用几百MB甚至上GB内存,可通过应用监控(如Micrometer)查看RocksDB的缓存命中率、内存使用指标,或使用RocksDB自带工具分析其内存占用。
4. 区分容器内存中的页面缓存
Docker的内存统计包含Linux内核的页面缓存(Page Cache),这部分内存用于缓存磁盘文件(如应用日志、RocksDB的状态文件),属于可回收内存:
- 执行
docker exec <容器ID> free -h,查看cache列的数值,这部分是被内核用来缓存文件的内存,会被算进容器总内存,但不会导致OOM(内存不足时内核会自动回收)。
5. 检查JVM直接内存与JNI内存
- SpringBoot和Kafka客户端大量使用NIO,会占用直接内存(Direct Memory),这部分不在JVM堆中;
- Kafka客户端的底层实现可能用到JNI调用,也会占用本地内存,这些都可以通过
jcmd <PID> VM.native_memory summary中的Direct Memory和JNI部分查看。
6. 线程栈的累计内存消耗
每个JVM线程默认分配1MB栈内存(可通过-Xss调整),如果应用存在大量线程(如Kafka Streams的处理线程、HTTP线程池、定时任务线程等),累计起来也是不小的开销:
- 执行
jstack <PID>统计线程总数,总栈内存=线程数×单线程栈大小,这部分内存也属于JVM的非堆内存消耗。
内容的提问来源于stack exchange,提问作者axatrikx
相关产品推荐
相关产品推荐

