学习内容:第2章 - Java 内存区域与内存溢出异常
实战代码均在我的 Git 仓库中:https://github.com/nx-xn2002/JVM-Learn.git
环境准备
书中使用的是 Eclipse,我使用的是我个人更常用的的 IDEA,JDK 版本是 JDK 19。实战中通过以下方式来设置调试过程中的虚拟机启动参数(VM options):
- 在程序调试启动处选择
Edit...
- 在窗口中
VM options
处填入启动参数
实战
Java 堆溢出
Java 堆用于存储对象实例,所以只要我们不断创建新对象,并保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,随着对象数量增加,总容量触及到堆的最大容量限制后,就会产生内存溢出异常。
/**
* Java 堆溢出
* VM Options:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
* @author Ni Xiang
*/
public class HeapOutOfMemory {
static class OutOfMemoryObject {
}
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new OutOfMemoryObject());
}
}
}
以下代码中,我使用死循环不断创建 OutOfMemoryObject
对象,同时,在启动时,设置了参数-Xms20m -Xmx20m
限制 Java 堆的大小为 20 MB,且不可扩展(将堆的最小值 -Xms
参数与最大值 -Xmx
参数设置为一样可以避免堆自动扩展),然后通过参数 -XX:+HeapDumpOnOutOfMemoryError
让虚拟机在出现溢出异常时 Dump 出当前内存堆转储快照来进行事后分析。
运行结果如下图:
出现 Java 堆内存溢出时,异常堆栈信息 java.lang.OutOfMemoryError
会进一步提示 Java heap space
。要解决这个问题,最常规的处理方法就是对 Dump 出来的堆转储快照进行分析。
如下图就是用 IDEA 打开快照文件:
展开可以发现,就是海量的 OutOfMemoryObject
占据了大量的 Java 堆内存
分析的第一步是确认内存中导致 OOM 的对象是否是必要的,也就是要先分清楚是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
内存泄漏(Memory Leak)
是指在程序运行过程中,动态分配的堆内存未被正确释放,导致系统内存浪费,进而可能引起程序运行速度减慢甚至系统崩溃的问题。如果内存中导致 OOM 的对象是非必要的,则可以初步判断是内存泄漏问题
内存溢出(Memory Overflow)
存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。如果内存中导致 OOM 的对象是必要的,则可以初步判断是内存溢出问题
作者针对这两种情况,给出了进一步的分析解决思路:
- 内存泄漏:
如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们,根据泄漏对象的类型信息以及它到GC Roots引用链的信息,一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。
- 内存溢出:
如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机的堆参数(-Xmx与-Xms)设置,与机器的内存对比,看看是否还有向上调整的空间。再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。