Java虚拟机-6-OOM

六、OOM

1、概念

java.lang.OutOfMemoryError,内存溢出,简称OOM

这里的内存主要是指堆内存(Heap)和方法区(Method Area),因为这两块区域主要用于存储对象的相关信息,使用的内存空间较大,而程序在运行期间,如果因为编码不当(一般是因为这样),就会引发OOM

2、java.lang.OutOfMemoryError: Java heap space

【1】异常出现

-Xms1m -Xmx1m

JVM启动参数

public static void main(String[] args) {
    
    
    byte[] arr = new byte[1024 * 1024];
    System.out.println(arr.length);
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at ???.main(???.java:???)

【2】异常原因

JVM堆内存主要用于存储对象的实例和数组等。因为我们设置了JVM堆的最大内存为1MB,而我们创建了一个byte数组,初始容量也为1MB。JVM在启动时,还需要加载其它类文件到内存当中,这样剩余的内存空间就不足1MB。如果此时创建的对象大于剩余内存空间,就会报该异常

此外如果程序在运行期间,创建的大对象,不能被GC回收掉,JVM也会报该异常

-Xms8m -Xmx8m

JVM启动参数

public static void main(String[] args) {
    
    
    int loop = 8;
    List<byte[]> list = new ArrayList<byte[]>(loop);
    for (int i = 1; i <= loop; i++) {
    
    
        byte[] arr = {
    
    };
        try {
    
    
            arr = new byte[1024 * 1024];
        } catch (Throwable t) {
    
    
            System.out.println("异常了!i=" + i);
            t.printStackTrace();
            break;
        }
        list.add(arr);
    }
}
异常了!i=7
java.lang.OutOfMemoryError: Java heap space
	at ???.main(???.java:???)

【3】解决办法

注意大对象的创建

注意集合容器的remove或clear方法的使用

3、java.lang.OutOfMemoryError: Metaspace

【1】异常出现

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Maven依赖

-XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=16m

JVM启动参数

public class TestOutOfMemoryError {
    
    

    public static void main(String[] args) {
    
    
        for (;;) {
    
    
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(User.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
    
    
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            try {
    
    
                Object proxy = enhancer.create();
            } catch (Throwable t) {
    
    
                t.printStackTrace();
                break;
            }
        }

    }

}

class User {
    
    }

java.lang.OutOfMemoryError: Metaspace
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:386)
	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219)
	at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
	at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
	at ???.TestOutOfMemoryError.main(TestOutOfMemoryError.java:???)

【2】异常原因

方法区主要用于存储类的相关信息,比如属性、方法等,每加载一个类,都需要把类的信息保存到该内存区域中

java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

HopSpot通过永久代(PermGen space)来实现方法区,在JDK8的时候,将永久代改为元空间(Metaspace)

CGLIB(Code Generation Library),是基于类继承,通过创建子类代理类的方式,来扩展被代理类(父类)的方法。所以每创建一个代理类,都会把类的信息加载到方法区内存当中

当方法区内存不足时,就会抛出该异常

JDK7示例:

java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

-XX:PermSize=2m -XX:MaxPermSize=2m

JVM启动参数

写个空的Main方法,并启动程序

Error occurred during initialization of VM
java.lang.OutOfMemoryError: PermGen space
	at java.io.InputStreamReader.<init>(InputStreamReader.java:74)
	at java.io.FileReader.<init>(FileReader.java:72)
	at sun.misc.MetaIndex.registerDirectory(MetaIndex.java:166)
	at sun.misc.Launcher$ExtClassLoader$1.run(Launcher.java:146)
	at sun.misc.Launcher$ExtClassLoader$1.run(Launcher.java:142)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Launcher.java:141)
	at sun.misc.Launcher.<init>(Launcher.java:71)
	at sun.misc.Launcher.<clinit>(Launcher.java:57)
	at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1489)
	at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1474)

【3】解决办法

调整JVM的启动参数,给予合适的方法区内存大小

  1. JDK8:MetaspaceSizeMaxMetaspaceSize
  2. JDK7:PermSizeMaxPermSize

4、java.lang.OutOfMemoryError: unable to create new native thread

【1】异常出现

在Linux系统上,由非root用户执行如下代码:

public static void main(String[] args) {
    
    
    for (int i = 1; ; i++) {
    
    
        try {
    
    
            new Thread(() -> {
    
    
                for (;;) {
    
    
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }).start();
        } catch (Throwable t) {
    
    
            System.out.println(i);
            t.printStackTrace();
            System.exit(1);
        }
    }

}

java.lang.OutOfMemoryError: unable to create new native thread
	at java.lang.Thread.start0(Native Method)
	at java.lang.Thread.start(Thread.java:717)

【2】异常原因

线程的创建是由操作系统来管理的,Java只负责调用本地方法(native)

所以能否创建线程,操作系统说了算

Linux操作系统对于线程创建的管理文件,位于/etc/security/limits.d/90-nproc.conf

# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.

*          soft    nproc     1024
root       soft    nproc     unlimited

可以看到非root用户,最多只能创建1024个线程(实际写代码创建不了1024个)。也可以通过命令ulimit -u来查看这个限制数

【3】解决办法

使用线程池来管理和复用线程

5、java.lang.OutOfMemoryError: Direct buffer memory

【1】异常出现

-XX:MaxDirectMemorySize=1m

JVM启动参数

public static void main(String[] args) {
    
    
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 2048);
    System.out.println(buffer.limit());
}

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 ???.main(???.java:???)

【2】异常原因

NIO,可以直接将数据分配到操作系统的物理内存当中,如果分配得过大,就会抛出该异常

可以通过JVM参数-XX:MaxDirectMemorySize=???来调整,默认是不限制的

【3】解决办法

调整代码逻辑

6、java.lang.OutOfMemoryError: GC overhead limit exceeded

【1】异常出现

-Xms8m -Xmx8m -XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=16m -XX:+PrintGCDetails

JVM启动参数

public class TestOutOfMemoryError {
    
    

    public static void main(String[] args) {
    
    
        List<MyArr> list = new ArrayList<MyArr>();
        for (int i = 1; ; i++) {
    
    
            try {
    
    
                list.add(new MyArr(i));
            } catch (Throwable t) {
    
    
                System.out.println(i);
                t.printStackTrace();
                break;
            }
        }

    }

}

class MyArr {
    
    
    private final String str;

    public MyArr(int num) {
    
    
        str = Integer.toString(num);
    }

}

java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.toString(Integer.java:401)

【2】异常原因

[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5514K->5514K(5632K)] 7050K->7050K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0345095 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5515K->5515K(5632K)] 7051K->7051K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0259106 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5517K->5517K(5632K)] 7053K->7053K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0221107 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]

看一下距离JVM抛出异常信息的最近的三条GC信息,可以看到无论怎样Full GC,各个内存区域在GC回收前和GC回收后的内存大小都是一样的,JVM为了防止继续这种“无用功”,从而报出该异常

一般是因为字符串常量过多导致的,JDK7之后,将字符串常量池移动到堆内存当中,一旦还存在引用关系,就无法回收

【3】解决办法

调整代码逻辑

7、内存诊断工具 – Memory Analyzer (MAT)

【1】介绍

The Eclipse Memory Analyzer 是一个快速并且功能强大的 Java堆内存分析工具,它可以帮助我们找到内存泄漏,以及减少内存消耗

【2】示例

1)异常代码

-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

JVM启动参数

package ???;

import java.util.ArrayList;
import java.util.List;

public class TestOutOfMemoryError {
    
    

    public static void main(String[] args) {
    
    
        int loop = 8;
        List<byte[]> list = new ArrayList<byte[]>(loop);
        for (int i = 0; i < loop; i++) {
    
    
            byte[] arr = new byte[1024 * 1024];
            list.add(arr);
        }
    }

}


java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid???.hprof ...
Heap dump file created [??? bytes in 0.019 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at ???.TestOutOfMemoryError.main(TestOutOfMemoryError.java:12)

2)分析内存快照文件

将内存快照文件java_pid???.hprof复制到空文件夹中

File —> Open Heap Dump… —> 打开快照文件

分析完成后,在弹出的对话框中,选择 Leak Suspects Report,并点击 Finish

然后有一个问题,Problem Suspect 1,点击 Details »

在 Accumulated Objects in Dominator Tree中,看到对象的内存分配情况

Class Name Shallow Heap Retained Heap Percentage
java.lang.Thread @ 0xffe8a0f8 main 120 6,292,112 92.46%
 java.util.ArrayList @ 0xffe8a028 24 6,291,624 92.46%
  java.lang.Object[8] @ 0xffeac700 48 6,291,600 92.46%
   byte[1048576] @ 0xff800000 1,048,592 1,048,592 15.41%
   byte[1048576] @ 0xff900010 1,048,592 1,048,592 15.41%
   byte[1048576] @ 0xffa00020 1,048,592 1,048,592 15.41%
   byte[1048576] @ 0xffb00030 1,048,592 1,048,592 15.41%
   byte[1048576] @ 0xffc00040 1,048,592 1,048,592 15.41%
   byte[1048576] @ 0xffd80000 1,048,592 1,048,592 15.41%
   Total: 6 entries 6,291,552 6,291,552 92.45%

在 Thread Stack中,看到线程栈信息

main
  at java.lang.OutOfMemoryError.<init>()V (OutOfMemoryError.java:??)
  at com.test.proxy.TestOutOfMemoryError.main([Ljava/lang/String;)V (TestOutOfMemoryError.java:12)

这样就可以定位到问题代码了

8、查看JVM堆内存信息 – jmap

使用JDK自带的工具jmap,语法 jmap -heap 进程ID

例如:jmap -heap 4371

Attaching to process ID 4371, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.212-b10

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 522190848 (498.0MB)
   NewSize                  = 11141120 (10.625MB)
   MaxNewSize               = 173342720 (165.3125MB)
   OldSize                  = 22413312 (21.375MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 10027008 (9.5625MB)
   used     = 534928 (0.5101470947265625MB)
   free     = 9492080 (9.052352905273438MB)
   5.334871578839869% used
Eden Space:
   capacity = 8912896 (8.5MB)
   used     = 534928 (0.5101470947265625MB)
   free     = 8377968 (7.9898529052734375MB)
   6.001730526194853% used
From Space:
   capacity = 1114112 (1.0625MB)
   used     = 0 (0.0MB)
   free     = 1114112 (1.0625MB)
   0.0% used
To Space:
   capacity = 1114112 (1.0625MB)
   used     = 0 (0.0MB)
   free     = 1114112 (1.0625MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 22413312 (21.375MB)
   used     = 0 (0.0MB)
   free     = 22413312 (21.375MB)
   0.0% used

703 interned Strings occupying 46848 bytes.

可以看到使用的垃圾收集器,堆内存的分配情况,以及堆内存的使用情况

猜你喜欢

转载自blog.csdn.net/adsl624153/article/details/103865661