Java虚拟机运行时数据区详解


线程共享区域:方法区、堆内存 线程私有区域:虚拟机栈、本地方法栈、程序计数器

程序计数器
程序计数器是极小的内存空间,用于指示当前线程执行的字节码行号位置。
该区域是唯一不会发生OutOfMemoryError的内存区域。
虚拟机栈
开发中常说的"栈"即指虚拟机栈的局部变量表部分。 虚拟机栈描述Java方法执行的内存模型,存储以下线程私有数据:
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
方法调用到执行结束对应栈帧入栈到出栈过程。
可能出现的异常:
1. 栈深度超过虚拟机限制:StackOverflowError
(常见于无限递归调用)
2. 动态扩展时内存不足:OutOfMemoryError
本地方法栈
功能类似虚拟机栈,但服务于Native方法(如C/C++实现的方法)。
Java堆
Java虚拟机管理的最大内存区域,被所有线程共享,主要存储对象实例。 垃圾回收主要作用于该区域,因此性能调优多关注堆内存。

堆内存分区:
1. 新生代:存储新建对象,经过GC后存活对象年龄增加
2. 老年代:长期存活对象最终进入的区域
配置参数示例:
-Xms2G // 堆初始内存
-Xmx2G // 堆最大内存
-XX:NewSize=512m // 新生代初始大小
-XX:MaxNewSize=512m // 新生代最大大小
-XX:SurvivorRatio=8 // Eden与Survivor区比例
优化建议:
设置-Xms与-Xmx相同值,避免内存动态调整导致的性能波动。
方法区
线程共享区域,存储:
- 已加载类信息
- 运行时常量
- 静态变量
- JIT编译代码
内存不足时抛出OutOfMemoryError。
运行时常量池
方法区的子区域,存储编译期生成的:
- 字面量
- 符号引用

直接内存
非运行时数据区组成部分,但可能引发OOM。
传统IO与NIO对比:

NIO通过DirectByteBuffer操作堆外内存,忽略该区域可能引发OOM。
内存溢出场景分析
堆内存溢出
配置参数:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
使用MAT分析dump文件定位异常对象。
栈溢出
两种异常类型:
1. 栈深度超标:StackOverflowError
2. 扩展时内存不足:OutOfMemoryError
StackOverflowError示例:
public class StackDepthDemo {
public static void main(String[] args) {
recursiveCall(0);
}
static void recursiveCall(int count) {
System.out.println("调用深度: " + count++);
recursiveCall(count); // 无限递归
}
}
栈OOM示例:
public class ThreadOOM {
public static void main(String[] args) {
while(true) {
new Thread(() -> {
while(true); // 保持线程存活
}).start();
}
}
}
方法区溢出
JDK1.8前配置:
-XX:PermSize=10M -XX:MaxPermSize=10M
JDK1.8+元空间配置:
-XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m
常量池溢出示例:
public class ConstantPoolOOM {
public static void main(String[] args) {
List<String> pool = new ArrayList<>();
int i = 0;
while(true) {
pool.add(("数据项" + i++).intern());
}
}
}
直接内存溢出
配置参数:
-Xmx20m -XX:MaxDirectMemorySize=10m
示例代码:
public class DirectMemoryOOM {
static final int MB = 1024 * 1024;
public static void main(String[] args) {
ByteBuffer.allocateDirect(11 * MB); // 超出限制
}
}