java运行时内存模型
1. 程序计数器(Program Counter Register)
线程私有
是一块较小的内存空间,它可以看作是当前线程所执行的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 为什么它是线程私有?
因为java虚拟机多线程是通过线程轮流切换并分配处理前执行时间的方式来实现的,任何时刻,一个CPU内核都只可执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器。 - 如果正在执行的是Native方法,这个计数器的值为空(Undefined)
- 此区域是JVM中唯一一个没有规定任何OOM(OutOfMemoryError)的区域
2. 虚拟机栈(Virtual Machine Stack)
线程私有
虚拟机栈是JAVA方法执行的内存模型,每个方法执行的时候会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法的调用开始到结束的过程就对应着一个栈帧入栈到出栈的过程
- 局部变量表
存放了编译期可知的各种基本数据类型、对象引用,其中64位的long和double会占用2个局部变量空间(Slot),其余的会占用1个。该空间所需内存再编译期间已完成分配,方法运行期间不会改变。 - 如果线程请求的栈深度大于虚拟机栈所允许的最大深度,将抛出StackOverflowError异常
- 如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存空间,就会抛出OOM异常
3. 本地方法栈(Native Method Stack)
线程私有
与虚拟机栈作用类似,使用到的是native方法服务,也会抛出StackOverflowError、OOM异常
4. 堆(Heap)
线程共享
堆式JVM中最大的一块。所有线程共享该区域,在虚拟机启动时创建;
该区域唯一目的就是存放对象实例,堆是GC管理的主要区域,通常也称为GC堆,
还可细分为:新生代(Eden、From Survivor、To Survivor)和老年代;
从内存分配的角度来看,堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
通过-Xms/-Xmx来配置最大值和最小值;
如果堆中没有足够的内存来完成实例分配,且无法再扩展时,将会抛出OOM异常
5. 方法区(Method Area)
线程共享
用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
Hotspot把GC分代收集扩展至方法区,所以这块也被称为永久代
当方法区无法满足内存分配需求时也将抛出OOM
6. 运行时常量池
方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符合引用。
运行期间也可能将新的常量放入池中,String.intern()方法
当常量池无法再申请到内存时抛出OOM异常
7. 直接内存(Direct Memory)
NIO中使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作;
动态扩展JVM堆内存时容易造成与直接内存的总和超过物理内存大小,从而导致OOM