JVM内存区域由启动时划分,非Java代码控制;方法区Java8+用Metaspace替代PermGen,类元数据存Metaspace而字符串常量池在堆中;堆分新生代(Eden+S0+S1)和老年代,默认1:2;栈、本地方法栈、程序计数器为线程私有;直接内存属本机内存,不归JVM运行时数据区管。

Java 的 JVM 内存区域不是由 Java 代码直接控制的,而是由 JVM 启动时按规范划分的运行时数据区;搞不清这些区域,排查 OutOfMemoryError 或 StackOverflowError 就容易定位错方向。
方法区(Metaspace)和永久代(PermGen)的区别在哪
Java 8 及以后彻底移除了 PermGen,改用本地内存实现的 Metaspace。这意味着:
-
Metaspace默认无上限(受本机内存限制),而PermGen是堆内固定大小区域,容易因加载大量类(如热部署、反射、Groovy/Scala 动态生成类)触发java.lang.OutOfMemoryError: PermGen space - 配置方式变了:
-XX:MaxPermSize在 Java 8+ 已无效;改用-XX:MaxMetaspaceSize和-XX:MetaspaceSize - 类元数据(如 Class 对象、常量池、字段/方法信息)存在这里,但字符串常量池(
StringTable)在 Java 7+ 已移到堆中
堆内存(Heap)里新生代和老年代怎么分,为什么这么分
堆是 GC 主战场,逻辑上划分为新生代(Young Gen)和老年代(Old Gen),默认比例通常是 1:2(即新生代占 1/3),但可通过 -XX:NewRatio 调整。
- 新生代再细分为
Eden区和两个Survivor区(S0和S1),对象优先分配在Eden;一次 Minor GC 后存活对象进入Survivor,经历多次 GC 仍存活(由-XX:MaxTenuringThreshold控制,默认 15)才晋升到老年代 - 大对象(如大数组)可能直接进老年代(取决于
-XX:PretenureSizeThreshold设置,且仅对 Serial/Parallel 收集器有效) - 老年代空间不足触发 Full GC,代价远高于 Minor GC;所以频繁 Full GC 往往说明对象过早晋升,或老年代本身太小
栈、本地方法栈、程序计数器为什么是线程私有的
这三个区域生命周期与线程绑定,每个线程启动时 JVM 自动为其分配独立空间:
立即学习“Java免费学习笔记(深入)”;
-
Java 虚拟机栈存储局部变量、操作数栈、动态链接、方法出口等,每个方法调用对应一个栈帧;StackOverflowError多因递归过深或局部变量表过大 -
本地方法栈为 native 方法服务(如java.io.FileInputStream#read0()底层调用 C 函数),错误表现和 Java 栈类似 -
程序计数器记录当前线程执行字节码的行号,唯一不会 OOM 的区域;多线程环境下靠它保证线程切换后能恢复执行位置
直接内存(Direct Memory)算不算 JVM 内存
不算 JVM 运行时数据区的一部分,但它被 java.nio.ByteBuffer.allocateDirect() 分配,使用的是本机内存,不受 -Xmx 控制——这点极易被忽略。
- 常见于 NIO 框架(如 Netty)、高性能文件读写场景;泄漏表现为物理内存持续上涨,但
jstat显示堆内存正常 - 可通过
-XX:MaxDirectMemorySize限制,默认值等于-Xmx(注意:不是自动等于,是 JVM 实现的默认行为) - 释放依赖
Cleaner(Java 9+)或Finalizer(旧版),不及时 GC 可能导致OutOfMemoryError: Direct buffer memory
真正难的不是记住各区域名字,而是看到 OutOfMemoryError 日志时,能结合 GC 日志、堆转储(heap dump)和系统监控,快速判断是 Metaspace 不够、老年代碎片化、还是 DirectByteBuffer 泄漏——这些区域之间没有绝对隔离,但错误现象和排查路径差异极大。










