在 Java 虚拟机规范的描述中,除了程序计数器外,虚拟机的其他几个运行时区域都有发生 OutOfMemoryError 异常的可能。
前言
一本好书,每读一遍都会有不同的感受。写读书笔记,一来是便于平时查阅,毕竟技术书籍都比较厚,不方便随时携带;二来是督促自己多读书,理论与实践结合才能不断提升自己。
【深入理解Java虚拟机】阅读笔记: 2.4 OutOfMemoryError 异常
Java 堆溢出
Java 堆用于存储对象实例,只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径,避免垃圾回收机制清除这些对象,那么在达到最大堆的容量限制后就会产生内存溢出异常
1 | /** |
虚拟机栈和本地方法栈溢出
Java 虚拟机规范描述的两种异常:本质上是堆同一件事情的两种描述
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常
- 如果虚拟机在扩展栈时无法申请到足够的内存空间, 则抛出 OutOfMemoryError 异常
实验情况:
- 使用 -Xss 参数减少栈内存的容量。结果:抛出 StackOverflowError 异常
- 定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果:抛出 StackOverflowError 异常
如下为第 1 点测试程序
1 | /** |
创建线程导致内存溢出异常:通过不断地建立线程产生内存溢出异常,这样产生的内存溢出异常与栈空间是否足够大不存在联系。相反,为每个线程的栈分配的内存越大,越容易产生内存溢出异常
1 | /** |
方法区和运行时常量池溢出
运行时常量池是方法区的一部分
JDK 1.6 及之前的版本中,常量池分配在永久代,可通过-XX:PermSize 和 -XX:MaxPermSize 直接限制其容量
1 | /** |
- 在 JDK 1.6 中,intern() 方法会把首次遇到的字符串实例复制到永久代中,返回其引用
- 在 JDK 1.7 中,intern() 不会复制实例,只是在常量池中记录首次出现的实例引用
1 | public class RuntimeConstantPoolOOM { |
方法区用于存放 Class 的相关信息,测试的基本思路是运行时产生大量的类去填满方法区,直到溢出。
1 | /** |
本机直接内存溢出
- 使用 DirectByteBuffer 分配内存导致抛出内存溢出异常时,实际并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,手动抛出异常
- 直接通过反射获取 Unsafe 实例,调用 unsafe.allocateMemory() 可直接向系统进行内存分配
1 | /** |
参考
周志明. 深入理解Java虚拟机