注意区别于 Java 内存模型

JVM 内存结构,和 Java 虚拟机的运行时区域有关

Java 内存模型,和 Java 的并发编程有关

结构

JVM 定义了五种运行时数据区,分别是虚拟机栈、本地方法栈、程序计数器、Java 堆、方法区

线程隔离的:虚拟机栈、本地方法栈、程序计数器

线程共享的:堆、方法区

程序计数器

用作当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。在多线程的情况下,多个线程轮流切换执行,所以为了线程切换后能够恢复到正确的执行位置,需要程序计数器来告诉线程接下来该执行哪条指令

虚拟机栈

每个 Java 虚拟机线程都有自己私有的 Java 虚拟机栈,跟线程同时创建,跟线程有相同生命周期

Java 虚拟机栈描述的是 Java 方法执行的内存模型:每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中的入栈到出栈的过程

可抛出的异常:

线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,常见于递归调用

虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时,不太常见

本地方法栈

本地方法栈区别于虚拟机栈,当调用的方法是本地方法(例如 C 语言实现的方法)时,会用到本地方法栈

同样会抛出 StackOverflowErrorOutOfMemoryError 异常

堆是线程共享的,分为新生代和老年代

新生代又分为 Eden 区和两个 Survivor 区 S0(Survivor From) 和 S1(Survivor To),而且 Survivor 区总有一个是空的

可抛出的异常:

当在堆上申请不到足够的空间

方法区

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据

Java 7

JDK 1.7 中,习惯将方法区称为“永久代”,因为 HotSpot 虚拟机用永久代来实现方法区

方法区是 JVM 规范中定义的一个区域,但规范只做理论指导,而永久代是 HotSpot 虚拟机在此规范上的具体实现方式

可抛出的异常:

当永久代被占满时

Java 8

到了 JDK 1.8,HotSpot 虚拟机已经完全移除永久代,也就是说不使用永久代的方式来实现方法区,也就是 JDK1.8 中不存在永久代的概念了,取而代之的是 Metaspace(元空间),它也是对方法区的一个实现方式,相比于永久代方式,元空间方式解决了永久代方式存在的一些问题

元空间数据分配在本地内存中,也就是系统可用内存,所以默认情况下,不会发生 OOM 问题

举🌰

public class Test {

    /**
     * 类的变量
     */
    private static int a;

    /**
     * 类成员变量
     */
    private int b;

    /**
     * 局部变量
     */
    public void test(int c){
        int d;
    }
}

变量 a 是类变量,存放在 方法区,线程共享

变量 b 是成员变量,随着对象一起存放在 内存,线程共享

变量 c 和 d 是局部变量,存放在 内存,线程独享