荐 不能不知道的OOM异常分析以及解决方案
在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区都会发生OOM异常的可能,本文通过几个例子来了解一下虚拟机常见的OOM异常。
本文的代码参考《深入理解Java虚拟机(第二版)》
Java堆溢出
Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免避免垃圾回收清除对象,那么这些对象达到最大堆的容量限制之后就会产生内存溢出异常。
- 设置VM参数,方便导出dump文件分析
-Xms20m //JVM初始分配的内存20m
-Xmx20m //JVM最大可用内存为20m
-XX:+HeapDumpOnOutOfMemoryError //当JVM发生OOM时,自动生成DUMP文件
-XX:HeapDumpPath=/Users/wangchengming/Desktop/ //生成DUMP文件的路径,方便后面进行分析
如果不加-XX:HeapDumpPath
和-XX:+HeapDumpOnOutOfMemoryError
参数的话,也可以通过jps
命令找到pid
,如下:
然后使用jmap -dump:format=b,file=serviceDump.dat 2513
命令手打导出dump文件,如下:
- 堆内存溢出代码
public class OOMTest {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> oomObjects = new ArrayList<>();
while (true) {
oomObjects.add(new OOMObject());
}
}
}
- 运行结果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/wangchengming/Desktop/java_pid2493.hprof ...
Heap dump file created [27779830 bytes in 0.088 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at OOMTest.main(OOMTest.java:16)
Process finished with exit code 1
- 使用JDK自带的VisualVM可视化工具导入dump文件
可以看出一共创建了810326个对象,占用了99.4%的内存,发现内存被撑爆了。
- 解决思路
- 通过上面的分析可以得出是
堆内存溢出
。这个时候可以定位到代码:OOMTest$OOMOject
,发现是死循环导致的,修改代码之后就不会出现堆内存溢出了 - 或者可以检查虚拟机堆参数(-Xmx和-Xms)是否可以调大
- 思考是不是存在GC Roots不可达的对象,如果存在GC Roots不可达的对象则是内存泄漏
- 通过上面的分析可以得出是
虚拟机栈和本地方法栈溢出
通常情况下栈容量是由-Xss
参数设定。关于虚拟机栈和本地方法栈,在Java虚拟机中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,则会抛出*Error
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则会抛出OOM
接下来演示一波*Error
- 设置VM参数
-Xss160k
,最小是160k
,默认1M
- 编写代码
private int stackLength = 1;
/**
* 递归
*/
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
OOMTest oomTest = new OOMTest();
try {
oomTest.stackLeak();
} catch (Throwable e) {
System.out.println("stack length : " + oomTest.stackLength);
throw e;
}
}
- 从运行结果来看,单线程下虚拟机抛出的是
*Error
stack length : 773
Exception in thread "main" java.lang.*Error
at OOMTest.stackLeak(OOMTest.java:23)
at OOMTest.stackLeak(OOMTest.java:24)
at OOMTest.stackLeak(OOMTest.java:24)
at OOMTest.stackLeak(OOMTest.java:24)
接下来演示一波OOM
- 设置VM参数
-Xss2M
- 编写代码
private void dontStop() {
while (true) {
}
}
private void stackLeakByThread() {
while (true) {
Thread thread = new Thread(this::dontStop);
thread.start();
}
}
public static void main(String[] args) {
OOMTest oomTest = new OOMTest();
oomTest.stackLeakByThread();
}
- 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
- 解决思路
- 通常在单线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出*Error 异常。如果是这种情况,就需要仔细检查代码有没有深度递归的情况。
- 不断地建立线程的方式会导致内存溢出。如果是这种情况就要检查是不是有死循环,或者通过
-Xss
降低的每个线程栈大小的容量
方法区和运行时常量池溢出
方法区,(又叫永久代,JDK8后,元空间替换了永久代),用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。
- 设置VM参数,
-XX:MetaspaceSize=10M
和-XX:MaxMetaspaceSize=10M
- 编写代码
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
- 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:387)
at OOMTest.main(OOMTest.java:31)
- 解决思路
- 首先我们看到错误信息
Caused by: java.lang.OutOfMemoryError: Metaspace
,然后检查代码是否应用了大量的代理,发现确实是应用了代理。 - 检查是否永久代空间设置得过小
- 检查代码是否频繁错误得使用String.intern方法
- 检查是否跟jsp有关。
- 检查是否使用CGLib生成了大量的代理类
- 重启大法,重启JVM
- 首先我们看到错误信息
本机直接内存溢出
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使用,而且也可能导致OOM。
在JDK1.4 中新加入了NIO(New Input/Output)类,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
DirectMemory容量可以通过-XX:MaxDirectMemorySize
指定,如果不指定则默认和Java堆最大值(-Xmx指定)一致。
- 设置VM参数
-Xmx256m
和-XX:MaxDirectMemorySize=100M
- 编写代码
public static void main(String[] args) throws IllegalAccessException, InterruptedException {
//分配128MB直接内存
ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);
TimeUnit.SECONDS.sleep(10);
System.out.println("ok");
}
- 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at OOMTest.main(OOMTest.java:85)
- 解决思路
- 检查代码是否恰当
- 检查JVM参数
-Xmx
,-XX:MaxDirectMemorySize
是否合理。
总结
通过以上的分析,一共得出以下几种常见OOM异常,希望可以帮到各位小伙伴
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: Direct buffer memory
本文地址:https://blog.csdn.net/wangchengming1/article/details/107343013