JVM内存模型详解:运行时数据区
JVM内存模型详解
JVM运行时数据区
JVM在执行Java程序时会把内存划分为多个数据区域,每个区域有特定的用途。
┌─────────────────────────────────────────┐
│ JVM运行时数据区 │
├──────────────┬──────────────────────────┤
│ 线程私有 │ 线程共享 │
├──────────────┼──────────────────────────┤
│ 程序计数器 │ 堆(Heap) │
│ 虚拟机栈 │ 方法区/元空间 │
│ 本地方法栈 │ 运行时常量池 │
└──────────────┴──────────────────────────┘
程序计数器
线程私有的内存区域,记录当前线程执行的字节码行号。
public class Counter {
public static void main(String[] args) {
int a = 10; // 行号 0
int b = 20; // 行号 1
int c = a + b; // 行号 2
System.out.println(c); // 行号 3
}
}
// 程序计数器记录当前执行到哪一行字节码
- 唯一不会发生OOM的区域
- 如果执行native方法,计数器值为空(Undefined)
虚拟机栈
线程私有,每个方法执行时创建一个栈帧。
public class StackDemo {
public static void main(String[] args) {
methodA();
}
public static void methodA() {
int x = 10;
methodB(x);
}
public static void methodB(int param) {
String msg = "hello";
System.out.println(msg);
}
}
// 栈帧结构:
// methodA的栈帧:[x=10]
// methodB的栈帧:[param=10, msg="hello"]
栈帧结构
┌─────────────────┐
│ 局部变量表 │ 存放方法参数和局部变量
├─────────────────┤
│ 操作数栈 │ 方法执行的工作区
├─────────────────┤
│ 动态链接 │ 指向运行时常量池
├─────────────────┤
│ 方法返回地址 │ 方法退出后回到调用处
└─────────────────┘
栈溢出(StackOverflowError)
public class StackOverflowDemo {
public static void main(String[] args) {
recursiveMethod();
}
public static void recursiveMethod() {
recursiveMethod(); // 无限递归
}
}
// Exception in thread "main" java.lang.StackOverflowError
堆(Heap)
线程共享,存放对象实例和数组。
public class HeapDemo {
public static void main(String[] args) {
// 对象在堆上分配
User user = new User("张三");
// 数组在堆上分配
int[] arr = new int[100];
}
}
class User {
private String name; // name引用在栈上,String对象在堆上
public User(String name) {
this.name = name;
}
}
堆内存分代
┌────────────────────────────────────────┐
│ 堆内存 │
├───────────────────┬────────────────────┤
│ 新生代(Young) │ 老年代(Old) │
├─────┬──────┬──────┤ │
│Eden │ S0 │ S1 │ │
│ 8 │ 1 │ 1 │ │
└─────┴──────┴──────┴────────────────────┘
# 查看堆内存配置
java -XX:+PrintFlagsFinal -version | grep -i heap
# 设置堆大小
java -Xms512m -Xmx1024m MyApp
方法区/元空间
存放类信息、常量、静态变量。
public class MethodAreaDemo {
// 静态变量存储在方法区
public static final String CONSTANT = "常量";
public static int count = 0;
public static void main(String[] args) {
// 类信息存储在方法区
Class<?> clazz = MethodAreaDemo.class;
System.out.println(clazz.getName());
}
}
永久代 vs 元空间
| 特性 | 永久代(PermGen) | 元空间(Metaspace) |
|---|---|---|
| 位置 | JVM内存 | 本地内存 |
| 大小限制 | 固定大小 | 可动态扩展 |
| JVM版本 | Java 7及以前 | Java 8+ |
# 设置元空间大小
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m MyApp
运行时常量池
存放编译期生成的字面量和符号引用。
public class ConstantPoolDemo {
public static void main(String[] args) {
String s1 = "hello"; // 字面量,存入常量池
String s2 = "hello"; // 引用常量池中的"hello"
String s3 = new String("hello"); // 新建对象
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s3.intern()); // true
}
}
直接内存
NIO使用直接内存,绕过JVM堆,提高IO性能。
public class DirectMemoryDemo {
public static void main(String[] args) {
// 分配直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);
buffer.put((byte) 'A');
buffer.flip();
System.out.println((char) buffer.get());
}
}
内存溢出场景
// 堆溢出
public class HeapOOM {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 不断创建大对象
}
}
}
// 栈溢出
public class StackOOM {
public static void main(String[] args) {
stackOverflow();
}
private static void stackOverflow() {
stackOverflow();
}
}
总结
理解JVM内存模型是进行性能调优和问题排查的基础。重点关注堆和栈的区别,以及GC对堆内存的管理。