chaojun-zhang / JavaFamily

记录学习点滴,分享技术干货
0 stars 0 forks source link

Java OOM归类 #6

Open chaojun-zhang opened 4 years ago

chaojun-zhang commented 4 years ago

Java OOM归类

除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能

内存模型

image

public class Heap { static List list = new ArrayList(); public static void main(String args[]) throws Exception { Integer[] array = new Integer[10000 * 10000]; } }

解决方案
    - 检查是否存在大对象分配,或者大数组分配
    - jmap dump下来在使用mat工具分析看是否有内存泄漏
    - 加大-Xmx内存
    - 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
    - 
- Error 2 GC Overhead limit exceeded 
这个主要说明了JVM的GC一直在运行,并且程序已经运行很慢。通常98%的时间在做GC,那么这个时候会抛出这个异常
``` java
// Java program to illustrate 
// GC Overhead limit exceeded 
import java.util.*; 

public class Wrapper { 
public static void main(String args[]) throws Exception 
    { 
        Map m = new HashMap(); 
        m = System.getProperties(); 
        Random r = new Random(); 
        while (true) { 
            m.put(r.nextInt(), "randomValue"); 
        } 
    } 
} 

如果你用java -Xmx100m -XX:+UseParallelGC 来运行上面的代码,那么这个错误就会抛出

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.valueOf(Integer.java:832) at Wrapper.main(error.java:9)

Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。

解决方法 1、检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。 2、添加参数 -XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。 3、dump内存,检查是否存在内存泄露,如果没有,加大内存。

public class Permgen { static ClassPool classPool = ClassPool.getDefault();

public static void main(String args[]) throws Exception { for (int i = 0; i < 1000000000; i++) { Class c = classPool.makeClass(com.saket.demo.Permgen" + i).toClass(); System.out.println(c.getName()); } } }

可以通过修改以下参数调整
>  java -XX:MaxPermSize=512m com.saket.demo.Permgen(这个已经在JAVA8 中废弃了) 

- Error 4 - Metaspace
java 类元数据是分配在native 内存中的,如果这个部分的内存超出,也会抛出OOM。 
``` java
// Java program to illustrate 
// Metaspace error 
import java.util.*; 

public class Metaspace { 
    static javassist.ClassPool cp = javassist.ClassPool.getDefault(); 

public static void main(String args[]) throws Exception 
    { 
        for (int i = 0; i < 100000; i++) { 
            Class c = cp.makeClass("com.saket.demo.Metaspace" + i).toClass(); 
        } 
    } 
} 

这段代码持续生成新类并且加载到元数据空间中,直到完全用完元数据空间,然后就会抛出java.lang.OutOfMemoryError: Metaspace。

解决方法 因为该OOM原因比较简单,解决方法有如下几种: 1、检查是否永久代空间或者元空间设置的过小 ( -XX:MaxMetaspaceSize) 2、检查代码中是否存在大量的反射操作 3、dump之后通过mat检查是否存在大量由于反射生成的代理类 4、放大招,重启JVM

public class GFG { public static void main(String args[]) throws Exception { while (true) { new Thread(new Runnable() { public void run() { try { Thread.sleep(1000000000); } catch (InterruptedException e) { } } }).start(); } } }

- Error 8 Map failed
- Caused by: java.lang.OutOfMemoryError: Map failed
这个一般是由于程序使用了FileChannelImpl这个类导致的,里面用到了底层的内存映射文件,当map file个数增长,系统的启动参数上加上关闭手动GC导致的(-XX:+DisableExplicitGC),导致map file个数达到了操作系统的max_map_count后直接OOM了。
Java的FilechannelImpl在使用时,如果发现map第一次出现OOM时,会调用System.gc去回收,但是我们又在启动参数上关闭了系统System.gc的能力。最后导致map失败。
解决方法:
-XX:+DisableExplicitGC这个参数不能随便关闭,原有是有些系统或者框架使用了DirectByteBuffer和FileChannel.map。这些占用的内存达到了最大临界值的时候,需要依赖调用System.gc来强制释放。

如果这个参数关闭了,那么内存释放就无效了,看到在下面第一次出现OutofMemoryError的时候,会调用System.gc(),然后等待100ms后,再次进行map操作,如果还是失败了,那就会抛出Map failed 。

``` java
        try {
           var7 = this.map0(var6, var13, var15);
        } catch (OutOfMemoryError var30) {
            System.gc();

            try {
                Thread.sleep(100L);
            } catch (InterruptedException var29) {
                Thread.currentThread().interrupt();
            }

            try {
                var7 = this.map0(var6, var13, var15);
            } catch (OutOfMemoryError var28) {
                throw new IOException("Map failed", var28);
            }
        }