
一、JVM内存分布全景图
JVM内存可以划分为以下四个核心部分(按逻辑分类):
JVM总内存 = 堆内存(Heap) + 非堆内存(Non-Heap) + 堆外内存(Off-Heap) + JVM Overhead
二、堆内存(Heap):G1的Region化管理
1. 堆内存定义
堆内存是 Java对象实例 的存储区域,由G1垃圾收集器统一管理。在JDK11中,堆内存通过 Region(区域) 划分,每个Region大小可配置(默认2MB~32MB)。
2. G1的堆内存结构
G1将堆划分为 年轻代(Young Generation) 和 老年代(Old Generation),两者均由Region组成:
- 年轻代(Young) :
- Eden Region:新对象分配的区域,GC后存活对象移动到Survivor。
- Survivor Region:存放年轻代GC后存活的对象,经历多次GC后晋升到老年代。
- 老年代(Old) :
- 存放长期存活的对象或大对象(Humongous Object,超过Region大小50%的对象)。
3. 关键参数
-Xms/-Xmx:设置堆内存初始值和最大值(如-Xms4g -Xmx8g)。-XX:G1HeapRegionSize:设置Region大小(默认2MB~32MB)。
三、非堆内存(Non-Heap):元空间与代码缓存
1. 元空间(Metaspace)
- 作用 :存储 类的元数据(Class Metadata),如类结构、方法信息、常量池等。
- JDK11特性 :
- 取代永久代(PermGen):JDK8及之后,元空间从堆内存移出,改为使用本地内存(Native Memory)。
- 动态扩容 :默认无上限,需通过
-XX:MaxMetaspaceSize显式限制(如-XX:MaxMetaspaceSize=512m)。
- 典型问题 :类加载过多(如动态代理、反射)可能导致
OutOfMemoryError: Metaspace。
2. 代码缓存(Code Cache)
- 作用 :存储 JIT编译器生成的本地机器码(Native Code)。
- 默认大小 :JDK11中默认为240MB(可通过
-XX:ReservedCodeCacheSize调整)。 - 典型问题 :代码缓存耗尽时,JVM会抛出
CodeCache is full错误,导致性能下降。
四、堆外内存(Off-Heap):脱离JVM管理的内存
1. 直接缓冲区(Direct Buffer)
- 作用 :通过
ByteBuffer.allocateDirect()分配,用于NIO高效I/O操作。 - 管理方式 :由
sun.nio.ch.DirectBuffer管理,通过Reference机制回收。 - 风险 :未正确释放可能导致内存泄漏(
OutOfMemoryError: Direct buffer memory)。
2. JNI本地内存
- 作用:Java通过JNI调用本地代码(C/C++)时分配的内存。
- 典型问题 :本地代码内存泄漏会导致进程内存耗尽(
OutOfMemoryError: native memory)。
3. 第三方库缓存
- 示例:Netty的堆外缓存池、数据库连接池的本地资源。
五、JVM Overhead:JVM自身运行的开销
1. 定义
JVM Overhead 是 JVM自身运行所需的内存开销,不包含堆内存和非堆内存中的显式区域。主要包括:
- 线程栈 :每个线程的私有内存(由
-Xss控制,默认1MB)。 - GC算法元数据:如G1的Region表、Remembered Set(RSet)、标记表。
- JIT编译器运行时开销:编译队列、临时数据结构。
- JVM内部缓存:如符号表、类加载器。
2. 线程栈的归属与影响
- 归属 :线程栈属于 JVM Overhead,而非非堆内存。
- 影响 :
- 高并发场景下 ,线程数 ×
-Xss会显著增加JVM Overhead。 - 示例:500线程 × 1MB = 500MB。
- 高并发场景下 ,线程数 ×
3. 典型问题
- 线程栈过大 :高并发场景下,线程数 ×
-Xss可能消耗大量内存(如500线程 × 1MB = 500MB)。 - GC元数据膨胀:G1的Region数量增加会导致RSet占用更多内存。
六、关键概念澄清:线程栈的归属
1. 错误表述修正
- 原错误:非堆内存中错误地将线程栈归类为非堆内存。
- 修正后 :
- 非堆内存:仅包含元空间、代码缓存。
- 线程栈 :属于 JVM Overhead,不计入堆或非堆内存。
2. 线程栈的归属逻辑
| 内存类型 | 是否包含线程栈 | 说明 |
|---|---|---|
| 堆内存 | ❌ | 存储对象实例,不包含线程栈。 |
| 非堆内存 | ❌ | 包含元空间、代码缓存,不包含线程栈。 |
| 堆外内存 | ❌ | 用于直接缓冲区、JNI内存,不包含线程栈。 |
| JVM Overhead | ✅ | 线程栈属于JVM Overhead,是JVM运行时的内部开销。 |
七、JVM内存分布的监控与调优
1. 监控工具
- jstat :监控堆内存使用(如
jstat -gc <pid>)。 - jcmd :查看内存分配(如
jcmd <pid> VM.native_memory summary)。 - jmap:生成堆转储(Heap Dump)分析内存泄漏。
- VisualVM / JConsole:可视化监控堆、非堆、线程栈。
2. 调优参数
| 参数 | 作用 |
|---|---|
-Xms / -Xmx |
设置堆内存初始值和最大值 |
-XX:MaxMetaspaceSize |
限制元空间最大值 |
-XX:MaxDirectMemorySize |
限制直接缓冲区最大值 |
-XX:ReservedCodeCacheSize |
调整代码缓存大小 |
-XX:NativeMemoryTracking=summary |
启用NMT监控堆外内存 |
八、实际案例:500线程应用的内存估算(基于指定参数)
1. 参数配置
bash
-Xms4g -Xmx4g \ # 堆内存固定为4GB
-XX:MaxMetaspaceSize=512m \ # 元空间最大512MB
-XX:MaxDirectMemorySize=1g \ # 直接缓冲区最大1GB
-Xss1m \ # 每个线程栈1MB
2. 内存分布计算
(1) 堆内存
- 大小:4GB(固定值,不可扩展)。
- 组成 :
- 年轻代(Young Generation):约1.5GB(默认G1的年轻代占比37.5%)。
- 老年代(Old Generation):约2.5GB。
(2) 非堆内存(Non-Heap)
- 元空间(Metaspace):512MB(显式限制)。
- 代码缓存(Code Cache):默认240MB(JDK11默认值)。
- 总计 :512MB + 240MB = 752MB。
(3) 堆外内存(Off-Heap)
- 直接缓冲区(Direct Buffer) :1GB(由
-XX:MaxDirectMemorySize=1g限制)。 - JNI内存:假设无显著泄漏,按需分配。
- 总计 :1GB。
(4) JVM Overhead
- 线程栈 :500线程 × 1MB = 500MB。
- GC元数据 (G1的Region表、RSet等):约 1.2GB(堆大小4GB时典型值)。
- JIT编译器运行时开销 :约 200MB。
- 其他JVM内部开销 (符号表、类加载器等):约 100MB。
- 总计 :500MB + 1.2GB + 200MB + 100MB = 2GB。
3. 总内存估算
总内存 = 堆内存 + 非堆内存 + 堆外内存 + JVM Overhead
= 4GB(堆) + 752MB(非堆) + 1GB(堆外) + 2GB(Overhead)
≈ **7.75GB**
注:这里计算出来的是值是jvm进程内存开销上限,因为上面这些参数MaxMetaspaceSize,MaxDirectMemorySize只是设置了上限,并不表示进程一启动就占用了这么多
九、调优策略
1. 线程栈优化
- 当前配置:500线程 × 1MB = 500MB。
- 优化建议 :
- 若线程数较多,可调低
-Xss(如-Xss512k),节省内存。 - 避免线程泄漏(如未关闭的线程池)。
- 若线程数较多,可调低
2. 直接缓冲区管理
-
监控命令 :
bashjcmd <pid> VM.native_memory summary -
优化建议 :
- 限制直接缓冲区大小(已配置为1GB)。
- 确保
ByteBuffer正确释放(调用ByteBuffer#free()或使用try-with-resources)。
3. 元空间监控
-
风险 :类加载过多可能导致
OutOfMemoryError: Metaspace。 -
监控命令 :
bashjstat -gc <pid>
4. 容器内存限制
- 建议 :若部署在容器中,需设置内存限制(如
--memory=8g),确保7.75GB < 8GB。
十、总结
| 内存类型 | 大小 | 说明 |
|---|---|---|
| 堆内存 | 4GB | 存储Java对象实例,固定值。 |
| 非堆内存 | 752MB | 元空间(512MB) + 代码缓存(240MB)。 |
| 堆外内存 | 1GB | 直接缓冲区(1GB),需监控NIO使用。 |
| JVM Overhead | 2GB | 线程栈(500MB) + GC元数据(1.2GB) + JIT开销(200MB) + 其他(100MB)。 |
十一、附录:关键参数说明
| 参数 | 作用 |
|---|---|
-Xms4g -Xmx4g |
堆内存固定为4GB,避免动态扩展。 |
-XX:MaxMetaspaceSize=512m |
限制元空间最大值为512MB,防止类加载导致OOM。 |
-XX:MaxDirectMemorySize=1g |
限制直接缓冲区为1GB,避免堆外内存溢出。 |
-Xss1m |
每个线程栈1MB,500线程共占用500MB。 |