
程序计数器
小块内存空间, 可看作是当前线程所执行的字节码的行号指示器.
线程独占
JVM 中唯一一个没有规定任何 OutOfMemoryError 情况的内存区
JVM 栈
Java 方法执行时的栈.
每个方法被执行的时候, JVM 都会同步创建一个栈帧用于存储局部变量表, 操作数栈, 动态连接, 方法出口等信息.
每一个方法被调用直至执行完毕的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.
线程独占
如果线程请求的栈深度大于虚拟机所允许的深度, 将抛出 StackOverflowError 异常.
如果Java虚拟机栈容量可以动态扩展, 当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常.
HotSpot 虚拟机的栈容量是不可以动态扩展的.
所以在 HotSpot 虚拟机上是不会由于虚拟机栈无法扩展而导致 OutOfMemoryError 异常 —— 只要线程申请栈空间成功了就不会有 OOM, 但是如果申请时就失败, 仍然是会出现 OOM 异常的.
本地方法栈
与 JVM 栈相似, 区别是 JVM 栈执行 Java 方法, 而本地方法栈执行 Native 方法.
线程独占
与虚拟机栈一样, 本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常.
Java 堆
大块内存空间, 几乎所有的对象实例以及数组都在这里.
垃圾收集器管理的主要区域.
线程共享
Java 堆既可以被实现成固定大小的, 也可以是可扩展的, 不过当前主流的 Java 虚拟机都是按照可扩展来实现的 (通过参数 -Xmx 和 -Xms 设定).
如果在 Java 堆中没有内存完成实例分配, 并且堆也无法再扩展时, Java 虚拟机将会抛出 OutOfMemoryError 异常.
方法区
用于存储已被虚拟机加载的类型信息, 常量, 静态变量, 即时编译器编译后的代码缓存等数据.
线程共享
如果方法区无法满足新的内存分配需求时, 将抛出 OutOfMemoryError 异常.
运行时常量池
方法区的一部分
当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常.
String::intern() 是一个本地方法, 它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串, 则返回代表池中这个字符串的String 对象的引用; 否则, 会将此 String 对象包含的字符串添加到常量池中, 并且返回此 String 对象的引用.
直接内存
不是虚拟机运行时数据区的一部分.
Java NIO 引入了一种基于通道 (Channel) 与缓冲区 (Buffer) 的 IO 方式.
它可以使用 Native 函数库直接分配堆外内存, 然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作.
这样能在一些场景中显著提高性能, 因为避免了在 Java 堆和 Native 堆中来回复制数据.
直接内存的分配不会受到 Java 堆大小的限制.
一般配置虚拟机参数时, 会根据实际内存去设置 -Xmx 等参数信息, 但经常忽略掉直接内存, 使得各个内存区域总和大于物理内存限制, 从而导致动态扩展时出现 OutOfMemoryError 异常.
由直接内存导致的内存溢出, 一个明显的特征是在 Heap Dump 文件中不会看见有什么明显的异常情况, 如果内存溢出之后产生的 Dump 文件很小, 而程序中又直接或间接使用了 DirectMemory (典型的间接使用就是 NIO), 那就可以考虑重点检查一下直接内存方面的原因了.
