总览
1. 程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行字节码的行号指示器。
工作时就是通过改变这个计数器的值来选取下一条需要执行的指令。
此区域是唯一一个没有OOM情况的区域。
2. Java虚拟机栈
和程序计数器一样也是线程私有的,生命周期与线程相同。
每个方法被执行的时候,JVM都会同步创建出一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每个方法被调用到执行结束的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
当线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError;若虚拟机栈可以动态扩展,当栈扩展时无法申请到内存时,就会抛出OOM。
3. 本地方法栈
本地方法栈和虚拟机栈相似,区别只是虚拟机栈是为虚拟机执行字节码服务的,而本地方法栈是为执行本地方法服务的。
4. 堆
“几乎”所有的对象实例都在堆上分配内存。(逃逸分析会使对象分配在栈上)
在G1出现之前,堆可以分为新生代、老年代、永久代(jdk8为元数据区)、Eden、Survivor(Survivor To、Survivor From)等。
当堆中已经没有内存可以分配对象了,并且堆无法扩展了,会抛出OOM。
5. 方法区
方法区和堆一样都是线程都是线程共享区域。
用于存储被JVM加载的类信息、常量、静态变量、编译后的代码缓存等数据。
当方法区中已经无法满足新的内存分配要求时,会抛出OOM。
6. 运行时常量池
常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。运行期间的常量也可以放在常量池中,如String的intern()方法。
当常量池无法申请到内存时也会抛出OOM异常。
7. 直接内存
直接内存不属于虚拟机运行时数据区的一部分,但是也被频繁使用,如NIO可以使用Native函数直接分配堆外内存,然后通过堆中的一个DirectByteBuffer对象作为这块内存的引用进行操作。
在配置虚拟机参数时,根据实际内存去设置-Xmx等参数信息时,经常会忽略掉直接内存,使得各个内存区域总和超出物理内存限制,从而导致动态扩展时出现OOM。
参考
《深入理解Java虚拟机 第三版》周志明