1.总体上,JVM所管理的内存将会包括以下几个部分。

那么接下来对他们详细的讲述,以备今后之需😄。

2.1程序计数器

程序计数器可以看作是当前线程所执行的字节码的行号计数器,而计数器本身的值通过被字节码解释器改变,以此来选取下一条需要执行的字节码指令。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式实现的,在任何一个确定的时间点上,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了能够在多线程切换中能够恢复到上一次正确的位置,每个线程都会有属于自己的独有的程序计数器,并且各线程的PC互不影响,我们称这类内存为“线程私有”的内存。

2.2Java虚拟机栈

虚拟机栈描述的是Java方法执行的线程内存模型。每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用到执行结束的过程中,都伴随着栈帧在虚拟机栈的出栈,入栈。其生命周期和线程一样。
对于程序员来说,比较关心的是“堆”和“栈”,一般来说栈指的是这里的虚拟机栈,更多情况下是虚拟机栈中局部变量表部分。
局部变量表中存放了编译器可知的Java虚拟机基本数据类型(char, int, boolen...)、对象引用(reference类型,它不同于对象本身,可能是指向对象起始地址的指针,也可能是代表对象的句柄) 和 returnAddress类型(指向一条字节码指令的地址)。而这些数据在局部标量表中存储空间以局部变量槽(Slot)来表示,并且数据本身的大小决定了Slot占据的数量。进入一个方法后,在方法需要在栈帧中占多大的局部变量空间在编译期间就可以确定,并且在运行时不会改变局部变量表的大小(这里的大小指变量槽的数量)。
对于此内存区域的两类异常状况:

  • StackOverflowError: 如果线程请求的栈深度大于虚拟机允许的最大深度。
  • OutOfMenoryError(OOM): 如果虚拟机允许栈容量动态扩展,那么当栈扩展无法申请到足够的内存则抛出。

2.3本地方法栈

本地方法栈和虚拟机栈的作用跟相似,虚拟机栈为虚拟机执行的Java方法服务,而本地方法栈为虚拟机使用到本地方法服务。根据不同的虚拟机实现,可能将本地方法栈和虚拟机栈合并(如Spot),本地方法栈也会在响应的时候抛出和虚拟机栈一样类型的错误。

2.4Java堆

如图中所示的,Java堆是所有线程共享的内存区域,在虚拟机启动时创建。此内存区域的唯一目的是存放对象实例,几乎所有的对象实例在这里分配内存。(当然,随着即时编译技术的进步,比如逃逸分析技术的日渐强大,栈上分配、标量替换使得Java对象必须在Heap上分配内存变得不是那么绝对。)
Java堆存储内容的共性是,无论哪个区域,存储的都是对象的实例,有些将Java堆进行例如“新生代、老年代、永久代......”这样的划分,只是为了更好的回收内存,或者更快速得分配内存,这里暂不讨论GC或者分配的细节。

2.5方法区

作为各个线程共享的内存区域,其用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。当然《Java虚拟机规范》对方法区的约束比较宽松,和Java堆一样不需要连续的内存空间和可以选择固定大小或者可扩展。对于这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载;同样,如果在方法区无法满足新的分配内存需求,将会抛出OOM异常。

2.6运行时常量池

运行时常量池是方法区的一部分。Class文件除了包含类的版本、字段、方法、接口等描述信息,还有常量池表(Constant Pool Table),用于存放编译期间各种生成的字面量于符号引用,而这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池也具备动态性,也就是说,并非预置入Class文件的常量池的内容才能进入方法区运行时常量池,运行时也可以将新的常量放入池中。
同样,如果收到方法去内存的限制,抛出OOM异常。

——本内容来自于《Java深入理解》-Java内存区域,以上为个人总结。