Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域。
其中一些数据区域是在 Java 虚拟机启动时创建的,仅在Java虚拟机退出时销毁。
其他数据区域是每个线程。线程数据区域是在线程退出时创建和销毁线程时创建的。
1、The pc Register(PC 寄存器、程序计数器)
2、Java Virtual Machine Stacks(Java 虚拟机栈、Java 栈)
3、Native Method Stacks(本地方法栈,C栈)
4、Heap(堆)
5、Method Area(方法区,JDK8 中的实现叫元数据区(本地内存中),JDK7 中的实现叫永久代(JVM中))
6、Run-Time Constant Pool(运行时常量池,方法区的一部分)
每个 JVM 线程都有自己的 pc 寄存器(内存为线程私有,随着线程的创建而创建,线程的结束而销毁)。
在任何时候,每个 JVM 线程都在执行单个方法的代码,即该线程的当前方法(字节码解释器通过改变程序计数器来选取下一条需要执行指令,从而实现代码的流程控制,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成)。
如果该方法不是 native,则 pc 寄存器包含当前正在执行的 JVM 指令的地址(线程切换就知道上次线程执行到哪了)。
如果该方法是 native,则 pc 寄存器为 Undefined(不会 OutOfMemoryError)。
描述 Java 方法执行的内存模型。
每个 JVM 线程都有一个私有 JVM 栈,与线程同时创建(内存为线程私有,随着线程的创建而创建,线程的结束而销毁)。
JVM 栈存储 frames (栈帧)。方法调用和返回对应压栈和出栈(栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的方法,PC 寄存器也会指向这个地址,只有这个活动的栈帧的本地变量可以被操作数栈使用)。

由于除了压栈和出栈之外,永远不会直接操作 JVM 栈,JVM 栈的内存不需要是连续的。
JVM 规范允许 JVM 栈具有固定大小,也可以根据计算的需要动态扩展和收缩(通过 -Xss 控制)。
以下异常与 JVM 栈有关:
如果不可以动态扩展 Java 虚拟机栈,当线程中的方法调深度用超过 Java 虚拟机栈最大深度时,会抛出 StackOverflowError 异常(出现 StackOverFlowError 时,内存空间可能还有很多)。
如果可以动态扩展 Java 虚拟机栈,当线程尝试进行扩展但可使内存不足以实现扩展,或者可使内存不足以为新线程创建初始 Java 虚拟机堆栈时,会抛出 OutOfMemoryError 异常。
描述本地方法运行过程的内存模型。
JVM 可以使用常规栈来支持 native 方法(用 Java 编程语言以外的语言编写的方法,执行也会创建栈帧)。
无法加载 native 方法,并且本身不依赖于传统堆栈的 JVM, 不需要提供本地方法栈。如果提供,则通常在每个线程创建时分配本地方法栈。
本地方法栈具有固定大小,也可以根据计算的需要动态扩展和收缩。
以下异常与本地方法栈有关:
如果不可以动态扩展本地方法栈,当线程中的计算需要比允许的本地方法栈更大,则会抛出 StackOverflowError 异常。
如果可以动态扩展本地方法栈,当尝试进行本地方法栈扩展,但可使内存不足,或没有足够的内存可用于为当前前程创建初始本地方法栈,则会抛出 OutOfMemoryError 异常。
堆是运行时数据区,从中分配所有类实例和数组的内存(JVM 中内存最大的一块,被所有线程共享,需要注意同步问题)。
堆是在 JVM 启动时创建的。
堆中对象的存储由垃圾收集器(GC,自动存储管理系统)回收,对象永远不会被显式释放。
JVM 没有特定类型的 GC,可以根据实现者的系统要求选择存储管理技术。
堆可以具有固定大小,也可以根据计算的需要进行扩展(通过 -Xmx 和 -Xms 控制)。堆的内存不需要是连续的。
以下异常情况与堆有关:
如果计算需要的堆量超过自动存储管理系统可用的堆,则会抛出 OutOfMemoryError 异常。
方法区在所有 JVM 线程之间共享。方法区是在 JVM 启动时创建的。方法区在逻辑上是堆的一部分,但可选择不实现垃圾回收。
方法区存储类结构,如运行时常量(Run-Time Constant Pool),字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。
JVM 规范未规定方法区的位置或用于管理编译代码的策略。
方法区可以是固定大小的,也可以根据计算的需要进行扩展。方法区的内存不需要是连续的。
以下异常与方法区有关:
如果方法区域中的内存无法满足分配请求,会抛出 OutOfMemoryError 异常。
运行时常量池是方法区的一部分。
Class 文件中的常量池(constant_pool Table),用于存放编译期生成的各种字面量和符号引用,这部分在类加载后进入方法区的运行时常量池中。
在运行期间,也可以向常量池中添加新的常量。如 String 类的 intern() 方法。
每个运行时常量池都是从 JVM 的方法区中分配的。
以下异常与类或接口的运行时常量池的构造有关:
在创建类或接口时,如果运行时常量池的构造需要的内存比 JVM 方法区中可用的内存多,会抛出 OutOfMemoryError 异常。
不是 JVM 运行时数据区的一部分,但这部分内存也会被频繁的使用,也可能导致 OutOfMemoryError 异常。
JDK 1.4 中新加入了 NIO 类,通过调用本地方法直接分配 Java 虚拟机之外的内存,然后通过一个存储在堆中的 DirectByteBuffer 对象直接操作该内存。
避免了 Java 堆和 Native 堆来回交换数据的时间,更高效。

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
http://www.hollischuang.com/archives/2509
https://github.com/doocs/jvm/blob/master/docs/01-jvm-memory-structure.md
https://my.oschina.net/u/3471412/blog/3001024
JAVA-JVM 运行时内存结构(Run-Time Data Areas)
原文:https://www.cnblogs.com/jhxxb/p/10896386.html