JVM内存溢出异常OOM

栈溢出 StackOverflowError

Java 里的 StackOverflowError。抛出这个错误表明应用程序因为深递归导致栈被耗尽了。每当java程序启动一个新的线程时,java虚拟机会为他分配一个栈,java栈以帧为单位保持线程运行状态;当线程调用一个方法是,jvm压入一个新的栈帧到这个线程的栈中,只要这个方法还没返回,这个栈帧就存在。 如果方法的嵌套调用层次太多(如递归调用),随着java栈中的帧的增多,最终导致这个线程的栈中的所有栈帧的大小的总和大于-Xss设置的值,而产生生StackOverflowError溢出异常。
StackOverflowError 是 VirtualMachineError 的扩展类,VirtualMachineError 表明 JVM 中断或者已经耗尽资源,无法运行。而且,VirtualMachineError类扩展自 Error 类,这个类用于指出那些应用程序不需捕获的严重问题。因为这些错误是在可能永远不会发生的异常情况下产生,所以方法中没有在它的 throw 语句中声明。
最常见的可能耗光 Java 应用程序的栈的场景是程序里的递归。递归时一个方法在执行过程中会调用自己。 递归被认为是一个强大的多用途编程技术,为了避免出现 StackOverflowError,使用时必须特别小心。

public class StackErrorMock {

    private static int index = 1;
     
    public void call(){
        index++;
        call();
    }
 
    public static void main(String[] args) {
        StackErrorMock mock = new StackErrorMock();
        try {
            mock.call();
        }catch (Throwable e){
            System.out.println("Stack deep : "+index);
            e.printStackTrace();
        }
    }
}

catch 捕获的是 Throwable,而不是 Exception。因为 StackOverflowError 和 OutOfMemoryError 都不属于 Exception 的子类。

在-Xss100M时
Stack deep : 6507194
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

在-Xss1M时
Stack deep : 21967
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

不配置时
Stack deep : 22106
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

依赖于安装的 Java 虚拟机,默认的线程栈大小可能是 512KB 或者 1MB。你可以使用 -Xss 标识来增加线程栈的大小。这个标识即可以通过项目的配置也可以通过命令行来指定。-Xss 参数的格式:

-Xss<size>[g|G|m|M|k|K]

堆溢出 OutOfMemoryError

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

public class HeapOverflow {
    public static void main(String[] args) {
        List<Object> listObj = new ArrayList<Object>();
        for(int i=0; i<10; i++){
            Byte[] bytes = new Byte[1024*1024];
            listObj.add(bytes);
        }
        System.out.println("已添加");
    }
}

设置参数

-Xms1m -Xmx5m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

根据以下的信息,可以看到,年轻代使用1536k,老年代4096(对象空间object space 4096K),其他Metaspace 2672K。

[GC (Allocation Failure) [PSYoungGen: 510K->504K(1024K)] 510K->520K(1536K), 0.0190917 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 752K->488K(1536K)] 768K->584K(5632K), 0.0009955 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 488K->504K(1536K)] 584K->608K(5632K), 0.0007268 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 104K->534K(2048K)] 608K->534K(3584K), [Metaspace: 2640K->2640K(1056768K)], 0.0153461 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 534K->534K(5632K), 0.0003560 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 534K->522K(3072K)] 534K->522K(4608K), [Metaspace: 2640K->2640K(1056768K)], 0.0065765 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8172.hprof ...
Heap dump file created [1232301 bytes in 0.008 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at HeadOverflow.main(HeadOverflow.java:8)
Heap
PSYoungGen total 1536K, used 41K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 4% used [0x00000000ffe00000,0x00000000ffe0a450,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 4096K, used 522K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000)
object space 4096K, 12% used [0x00000000ffa00000,0x00000000ffa828b8,0x00000000ffe00000)
Metaspace used 2672K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 291K, capacity 386K, committed 512K, reserved 1048576K

常量池永久代溢出(java8以前)

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

public class RuntimeConstantPoolOOM {
    /*
     * VM Args 1.8以前: -XX:PermSize=1M -XX:MaxPermsize=2M
     * 1.8以后:-XX:MetaspaceSize=1M -XX:MaxMetaspaceSize=2M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     */
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        //使用list保持常量池的引用,避免被Full GC回收
        List<String> list=new ArrayList<String>();
        int i=0;
        while(true)
        {
            list.add(String.valueOf(i).intern());
        }
 
    }

}

在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。
可以通过以下参数来指定元空间的大小:

1.8以前: -XX:PermSize=1M -XX:MaxPermsize=2M
1.8以后:-XX:MetaspaceSize=1M -XX:MaxMetaspaceSize=2M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

  -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid7928.hprof ...
Error occurred during initialization of VM
GC triggered before VM initialization completed. Try increasing NewSize, current value 84M.

直接内存溢出

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

本机直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制
直接内存由 -XX:MaxDirectMemorySize 指定
直接内存申请空间耗费更高的性能
直接内存IO读写的性能要优于普通的堆内存
当我们的需要频繁访问大的内存而不是申请和释放空间时,通过使用直接内存可以提高性能。

由于申请直接内存不由虚拟机管理,所以由此导致的 OOM 是不会在 Heap Dump 文件中看出明显的异常。当 OOM 后发现 Dump 文件很小同时程序直接或间接使用了 NIO ,就可以考虑一下这方面的原因。

 

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class DirectrMemoryOOM {
    private static final int _1M = 1024 * 1024;
    
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        int index =0 ;
        @SuppressWarnings("restriction")
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        try {
            while(true) {
                unsafe.allocateMemory(_1M);
                index++;
            }
        }catch (Throwable e){
            System.out.println("deep : "+index);
            e.printStackTrace();
        }
        

    }

}

参数

-Xmx20M -XX:MaxDirectMemorySize=10M

deep : 51966java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectrMemoryOOM.main(DirectrMemoryOOM.java:16)

posted @ 2020-06-30 10:03  昕友软件开发  阅读(796)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯