‌JVM 内存模型(JDK8+)

发布于:2025-03-28 ⋅ 阅读:(27) ⋅ 点赞:(0)
1. 内存模型结构图解
JVM 内存模型(JDK 8+)  
├── **线程私有区** 
│   ├── 程序计数器(Program Counter Register)‌ 
│   ├── 虚拟机栈(VM Stack)  
│   │   └── 栈帧(局部变量表、操作数栈、动态链接、方法出口)‌ 
│   └── 本地方法栈(Native Method Stack)‌
└── **线程共享区** 
    ├── 堆(Heap)  
    │   ├── 年轻代(Eden、Survivor S0/S1)‌
    │   └── 老年代(Old Generation)‌
    └──  元空间(Metaspace)

2. 各区域功能与代码示例

2.1 虚拟机栈(VM Stack)

功能‌:存储方法调用的栈帧,每个栈帧对应一个方法的执行状态‌。
代码示例‌:

public class StackExample {
    public static void main(String[] args) {
        int a = 1;                  // 局部变量a存入局部变量表‌
        int b = a + 2;              // 操作数栈 压入a和2,执行加法后存入b‌
        recursiveMethod(10000);     // 递归过深触发StackOverflowError‌
    }

    public static void recursiveMethod(int n) {
        if (n <= 0) return;
        recursiveMethod(n - 1);     // 栈帧持续入栈,超出栈容量时溢出‌
    }
}
  • 溢出场景‌:递归深度过大或局部变量占用过高。
  • 参数调优‌:
    -Xss256k  # 设置线程栈大小为256KB(默认1MB)‌
    

2.2 堆(Heap)

功能‌:存储所有对象实例数组对象。这是 JVM 中最大的一块内存区域。
代码示例‌:

public class HeapExample {
    public static void main(String[] args) {
        Object obj1 = new Object();  // 对象分配在堆的Eden区‌
        Object obj2 = new Object();
        obj1 = null;                // obj1引用断开,对象成为垃圾待回收‌
        System.gc();                // 触发Minor GC,存活对象晋升至Survivor区‌
    }
}
  • 堆内存划分‌:
    • 年轻代‌:新对象优先分配在 Eden 区,存活对象经 Minor GC 后进入 Survivor 区‌。
    • 老年代‌:长期存活对象或大对象直接进入老年代(如 new byte[10MB])‌。
  • 参数调优‌:
    -Xms512m -Xmx2g  # 设置堆初始大小512MB,最大2GB‌

2.3  元空间(Metaspace)

功能‌:存储类的元数据,如类结构信息(字段、方法、构造器)、常量池(Constant Pool)、静态变量(static 修饰的变量)以及 JIT 编译后的代码缓存。
代码示例‌:

public class MetaspaceExample {
    static final String CONSTANT = "JVM";  // 常量存入运行时常量池‌
    static String staticVar = "Static";     // 静态变量存储于元空间‌

    public static void main(String[] args) {
        Class<?> clazz = MetaspaceExample.class;  // 类元数据加载到元空间‌
    }
}
  • 参数调优‌:
    -XX:MaxMetaspaceSize=256m  # 限制元空间最大容量为256MB‌

2.4 程序计数器(Program Counter Register)

功能‌:记录当前线程执行的字节码指令地址(Native 方法执行时为空)‌。
代码示例‌:

public class CounterExample {
    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                // 程序计数器记录循环指令地址‌
                System.out.println(i);
            }
        }).start();
    }
}

2.5 本地方法栈(Native Method Stack)

功能‌:支持 JNI(Java Native Interface)方法的调用‌。
代码示例‌:

public class NativeExample {
    public native void nativeMethod();  // JNI方法调用使用本地方法栈‌
    static {
        System.loadLibrary("NativeLib");
    }
}

3. 内存交互与线程安全示例

多线程共享堆内存问题‌:

public class ThreadSafeExample {
    static int count = 0;  // 静态变量存储于元空间,线程共享‌

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count++;  // 非原子操作:读取→修改→写入‌
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);  // 结果可能非0(指令交错执行)‌
    }
}
  • 解决方案‌:
    • 使用 synchronized 同步代码块。
    • 使用 AtomicInteger 替代 int‌。

4. 总结

  1. 线程私有区‌:
    • 虚拟机栈管理方法调用栈帧,程序计数器记录指令地址‌。
    • 本地方法栈支持 JNI 方法调用‌。
  2. 线程共享区‌:
    • 堆存储对象实例,元空间存储类元数据(JDK 8+)‌。
  3. 参数调优‌:
    • 堆大小(-Xms-Xmx)、线程栈大小(-Xss)、元空间限制(-XX:MaxMetaspaceSize)‌。
  4. 线程安全‌:共享数据需同步或使用原子类‌。