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)
目前维护的开源产品:https://gitee.com/475660