实战:OutOfMemoryError 异常
Java 堆溢出
当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.OutOfMemoryError:Java heap space
原因
1、代码中可能存在大对象分配,通常是大数组
2、可能存在内存泄露(Memory Leak),大量对象没有被释放,导致在多次 GC 之后无法回收,还是无法找到一块足够大的内存容纳当前对象。
1 | public class JavaHeapSpaceDemo { |
解决方法
1、检查是否存在大对象的分配,大数组分配
2、通过 jmap 命令,把堆内存 dump 下来,使用 mat 工具分析,检查是否存在内存泄露的问题
3、如果没有找到明显的内存泄露,使用 -Xmx
加大堆内存
4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
虚拟机栈和本地方法栈溢出
1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError 异常。
1 | /** |
无论是由于栈帧太大还是虚拟机栈容量太小,当新的栈帧内存无法分配的时候, HotSpot 虚拟机抛出的都是 StackOverflowError 异常
原因
创建了大量的线程
解决方法
1、限制线程池大小;
2、使用 -Xss 参数减少线程栈的大小;
方法区和运行时常量池溢出
JDK 1.8 之后为元空间
1 | java.lang.OutOfMemoryError: PermGen space |
原因
永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT 编译后的代码等。
JDK8 后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化:
- 从 JDK 7 起,字符串常量由永久代转移到堆中
- 和永久代相关的 JVM 参数已移除
可能原因有如下几种:
1、在 Java7 之前,频繁的错误使用 String.intern() 方法
String.intern() 本地方法,它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的 String 对象的引用;否则,会将此 String 对象包含的字符串添加 到常量池中,并且返回此 String 对象的引用。
在 JDK 6 中,intern() 方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用。而 JDK 7 的 intern() 方法实现不需要再拷贝字符串的实例 到永久代,字符串常量池已经移到 Java 堆中,只需要在常量池里记录一下首次出现的实例引用即可
2、运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
3、应用长时间运行,没有重启
解决方法
该 OOM 原因比较简单,解决方法有如下几种:
1、程序启动报错,检查是否永久代空间 -XX:MaxPermSize
或者元空间 -XX:MaxMetaspaceSize
设置的过小
2、检查代码中是否存在大量的反射操作
3、dump 之后通过 mat 检查是否存在大量由于反射生成的代理类
4、重启 JVM
本机直接内存溢出
抛出 java.lang.OutOfMemoryError: Direct buffer memory
由直接内存导致的内存溢出,一个明显的特征是在 Heap Dump 文件中不会看见有什么明显的异常情况,如果内存溢出之后产生的 Dump 文件很小,而程序中又直接或间接使用了 DirectMemory(典型的间接使用就是 NIO),有可能是直接内存出了问题。
例如:写 NIO 程序经常使用 ByteBuffer
来读取或写入数据,它可以使用 Native
函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer
对象作为这块内存的引用进行操作,这样避免了在 Java 堆和 Native 堆中来回复制数据。但不断分配本地内存,堆内存很少使用,那么 JVM 就不会进行 GC,DirectByteBuffer
对象就不会被回收,此时堆内存充足,但是本地内存已经用光了,造成该 error
GC Overhead
java.lang.OutOfMemoryError: GC overhead limit exceeded
GC 回收时间过长(98% 时间用来做 GC,并且回收了不到 2% 的堆内存,CPU 利用率高,但是 GC 效果不明显)
创建多个线程
java.lang.OutOfMemoryError: unable to create new native thread
一个应用进程创建了多个线程,超过系统承载极限;或者服务器不允许创建这么多个线程。
参考
https://juejin.im/post/5dc6bf65f265da4d4a306e81#heading-12
https://cloud.tencent.com/developer/article/1492349
- 本文作者: Kelly Liu
- 本文链接: http://tiantianliu2018.github.io/2020/04/07/《深入理解-Java-虚拟机》OOM-笔记/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!