一、前言
OutOfMemoryError是未检测异常的一个子类。在java程序运行时,JVM内存区域内除了程序计数器之外,其他区域都有可能抛出此异常。
JVM内存区域相关知识,见文章:《JVM虚拟机内存区域思维导图-------《深入理解Java虚拟机》2020第三版》
程序员往往需要根据OutOfMemoryError去定位具体是哪个内存区域出现了问题?是内存溢出还是内存泄漏? 只有问题定位准确,才能更好的修改、维护程序。因此,分析OutOfMemoryError很有实际意义。
二、思维导图
笔者根据经典书籍《深入理解Java虚拟机》2020第三版,整理了一些常见的情况与处理方法,绘制了两张思维导图。
两张图内容一样,只是展现形式不一样:第一张为鱼骨图;第二张为常见的树状图。读者可根据自己的喜好选择合适的格式阅读
1. OutOfMemoryError分析鱼骨图
2. OutOfMemoryError分析树状图
3. 内存涉及概念
三、 思维导图大纲
1. Java堆
1. 原因:对象创建过多,内存溢出;内存泄漏
2. 报错识别:从报错信息上,能明显看出发生在heap: java.lang.OutOfMemoryError: Java heap space
3. 深层分析:使用内存映像分析工具对Dump出来的 储存快照进行分析
4. 解决办法
+ 内存泄漏
- 通过内存映像分析工具查看 泄漏对象到GC Roots的引用链
- 根据GC Roots引用链定位到 这些对象的创建位置,解决引用
+ 内存溢出
- 修改JVM堆参数
- 加物理内存
5. 参数:-Xms/-Xmx
2. 栈stack
1. 单线程
1). 原因:有些虚拟机选择不支持栈内存动态扩展; 或者栈内存因为物理内存或权限不够,无法动态扩展。 此时栈大小不够用时,会抛出OutOfMemoryError
2). 报错识别:从报错信息上无法直接识别,只能看到是执行方法时报错了
3). 特殊:Hotspot选择不支持栈内存动态扩展。它不会抛出 OutOfMemoryError,只会抛出StackOverflowError。 对Hotspot来说,"栈深"是动态变化的,并不是一个固定数值; 栈帧越大,栈深反而越小
4). 参数:-Xss 注意此参数设置的是"栈容量"(20MB),而不是"栈深"(500层)
5). 解决办法
- 虚拟机参数,栈容量加大
- 加物理内存
2. 多线程
1). 原因:多线程会启动多个栈,多个栈会占用更多内存,这样有可能抛出OutOfMemoryError
2). 报错识别:错误很明显 java.lang.OutOfMemoryError: unable to create native thread
3). 解决办法
- 减少线程启动数量
- 增加物理内存
- 更换64位虚拟机
- 减少堆和减少栈容量,以换取更多线程
3. 方法区与常量池
1. JDK6以前: 永久代
1). 原因
- 加载的类过多,方法区内存溢出
- 常量过多,运行时常量池溢出
2). 报错识别:java.lang.OutOfMemoryError: PermGen space
3). 参数:-XX:PermSize/-XX:MaxPermSize
2. JDK7以后: 元空间
1). 原因
- 元空间使用本地内存,很难内存溢出
- 字符串常量池移到了堆Heap中,只会堆溢出
2). 参数:-XX:MaxMetaspaceSize/-XX:MetaspaceSize
4. 本机直接内存溢出
1. 原因:通过某些方法获取本地直接内存太多,内存不够然后报错
2. 报错识别:错误不明显 java.lang.OutOfMemoryError, 栈路径中可以看到本地直接内存的调用API
3. 深层分析:Dump出来的储存快照文件很小,有直接或间接调用本地直接内存
4. 参数:-XX:MaxDirectMemorySize,如果不设置,则与-Xmx一致
5. StackOverflowError 分析
1.线程请求的栈深度大于虚拟机所允许的最大深度
2.JVM参数:-Xss,设置的是栈容量
3.抛出异常:java.lang.StackOverflowError,容易定位