棧溢出 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)