JVM在执行java程序的过程中会把其管理的内存划分为多个数据区域,根据JVM规范中的规定,包括方法区、堆、虚拟机栈、程序计数器、本地方法栈(如下图)。(红色区域为线程共享,蓝色区域为线程私有)
方法区(Method Area)
线程共享的区域,生命周期和虚拟机相同,主要储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。该区域在逻辑上是堆的一部分,也会存在垃圾回收,主要是针对常量池的回收和类型的卸载。
当方法区无法满足内存分配需求时,抛OutOfMemoryError.
运行时常量池(Runtime Constant Pool)
主要存放两大类常量:字面量和符号引用。
运行时常量池的大部分数据取自编译期生成的Class类文件中的常量池(具体参看JVM系列之Class类文件的结构中的常量池),还有部分是动态生成的,即运行期间放入到常量池的,比如String类的intern()方法(具体参看String类系列之intern方法)。
堆(Heap)
线程共享的区域,生命周期和虚拟机相同,主要储存对象实例。该区域是JVM所管理的内存中最大的一块,也是垃圾回收的主要区域。具体参看JVM系列之垃圾回收(GC)机制
当堆中没有内存可以完成实例分配,并且堆无法再扩展时,抛OutOfMemoryError.
虚拟机栈(Java Virtual Machine Stacks)
线程私有的区域,生命周期和线程相同。用于描述java方法执行的过程,每个方法在调用时会创建一个栈帧(栈帧的具体结构参看JVM系列之栈帧结构),栈帧中储存着局部变量表、操作栈、动态链接、返回地址等信息,每个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程(如下图---省略了main方法的栈帧)。
public static void main(String[] args) {
new Thread(()->{
//TODO
method1();
//TODO
},"Thread1").run();
}
private static void method1(){
//TODO
method2();
//TODO
}
private static void method2(){
//TODO
}
如果线程请求的栈深度大于虚拟机允许的最大栈深度时(如无限递归),抛StackOverflowError.
如果栈可以动态扩展(一般的虚拟机栈都可以动态扩展),在扩展时无法申请到足够的内存,抛OutOfMemoryError.
程序计数器(Program Counter Register)
线程私有的区域,生命周期和线程相同。可以看做当前线程所执行的字节码的行号指示器,记录当前线程执行的位置。分支、循环、跳转、异常处理、线程恢复等功能都需要依赖程序计算器实现。
唯一一块JVM规范中没有规定OOM情况的区域。
本地方法栈(Native Method Stack)
功能上类似于虚拟机栈,只不过是为Native方法服务的,异常情况同虚拟机栈。
直接内存(Direct Memory)
直接内存(堆外内存),不是虚拟机运行时数据区的一部分,直接受操作系统管理(而不是虚拟机),但是也会导致OOM异常。
该区域主要用于NIO中DirectByteBuffer类的引用和操作。具体参看java中直接内存(堆外内存)详解
当忽略了直接内存的参数配置,直接内存在动态扩展时申请不到足够的内存空间,抛OutOfMemoryError.