名詞解釋
- StackOverflowError:棧溢出錯誤
OutOfMemoryError:內存溢出錯誤
棧溢出
如果一個(gè)線(xiàn)程在計算時(shí)所需要用到棧大小 > 配置允許最大的棧大小,那么Java虛擬機將拋出StackOverflowError
內存溢出
如果一個(gè)線(xiàn)程可以動(dòng)態(tài)地擴展本機方法棧,并且嘗試本地方法棧擴展(沒(méi)有大于配置允許最大的棧大小),但是內存不足可以提供, 或者如果不能提供足夠的內存來(lái)為新線(xiàn)程創(chuàng )建初始的本機方法堆棧,那么Java虛擬機將拋出OutOfMemoryError。
什么是棧?
我這里講得比簡(jiǎn)單,建議大家看看java虛擬機模型。下圖是一個(gè)線(xiàn)程里面的存放的數據
棧的特點(diǎn)
也叫棧內存,是java虛擬機的內存模型之一,每當啟動(dòng)一個(gè)新線(xiàn)程時(shí),Java虛擬機都會(huì )為它分配一個(gè)Java棧。虛擬機只會(huì )直接對Java棧執行兩種操作,以幀為單位的壓棧和出棧。
- 棧存儲的是什么
方法內的局部變量表、操作數棧、動(dòng)態(tài)鏈接、方法出口信息、其他等信息 - 棧棧的生命周期
是在線(xiàn)程創(chuàng )建時(shí)創(chuàng )建,線(xiàn)程結束而消亡,釋放內存,由此可見(jiàn)棧內存是私有的。
棧內存是以棧幀(Stack Frame)為單位存儲,棧幀是一個(gè)內存區塊,是一個(gè)有關(guān)方法(Method)和運行期數據的數據集。
當一個(gè)方法M1被調用時(shí)就產(chǎn)生了一個(gè)棧幀S1,并被壓入到棧中,M1方法又調用了M2方法,于是產(chǎn)生棧幀S2也被壓入棧,M2方法執行完畢后,S2棧幀先出棧,S1棧幀再出棧,遵循“先進(jìn)后出”原則。
棧幀
棧幀存儲數據包含以下5個(gè)部分
局部變量表
保存函數的參數以及局部變量用的,局部變量表中的變量只在當前函數調用中有效,當函數調用結束后,隨著(zhù)函數棧幀的銷(xiāo)毀,局部變量表也會(huì )隨之銷(xiāo)毀。
存放基本數據類(lèi)型變量(boolean、byte、char、short、int、float)、引用類(lèi)型的變量(reference)、returnAddress(指向一條字節碼指令的地址)類(lèi)型的變量。
操作數棧
主要用于保存計算過(guò)程的中間結果,同時(shí)作為計算過(guò)程中變量臨時(shí)的存儲空間。只支持出棧入棧操作。在概念模型中,兩個(gè)棧幀是相互獨立的。但是大多數虛擬機的實(shí)現都會(huì )進(jìn)行優(yōu)化,令兩個(gè)棧幀出現一部分重疊。令下面的部分操作數棧與上面的局部變量表重疊在一塊,這樣在方法調用的時(shí)候可以共用一部分數據,無(wú)需進(jìn)行額外的參數復制傳遞。
動(dòng)態(tài)鏈接
每個(gè)棧幀都包含一個(gè)指向運行時(shí)常量池中該棧幀所屬性方法的引用,持有這個(gè)引用是為了支持方法調用過(guò)程中的動(dòng)態(tài)連接。在Class文件的常量池中存有大量的 符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用一部分會(huì )在類(lèi)加載階段或第一次使用的時(shí)候轉化為直接引用,這種轉化 稱(chēng)為靜態(tài)解析。另外一部分將在每一次的運行期期間轉化為直接引用,這部分稱(chēng)為動(dòng)態(tài)連接。
方法出口信息
在方法退出之前,都需要返回到方法被調用的位置,程序才能繼續執行,方法返回時(shí)可能需要在棧幀中保存一些信息,用來(lái)幫助恢復它的上 層方法的執行狀態(tài)。一般來(lái)說(shuō),方法正常退出時(shí),調用者PC計數器的值就可以作為返回地址,棧幀中很可能會(huì )保存這個(gè)計數器值。而方法異常退出時(shí),返回地址是 要通過(guò)異常處理器來(lái)確定的,棧幀中一般不會(huì )保存這部分信息
其他
模擬StackOverflowError堆棧溢出錯誤
public class TestDemo { private int index = 1; public void method() { index++; method(); } @Test public void testStackOverflowError() { try { method(); } catch (StackOverflowError e) { System.out.println("程序所需要的棧大小 > 允許最大的棧大小,執行深度: " + index); e.printStackTrace(); } }}
出錯實(shí)例:Map<String, Object> paramMap = new HashMap<String, Object>(); List<Map<String, Object>> userInfoList = new ArrayList<Map<String,Object>>(); paramMap .put("bizCd", "1001"); paramMap .put("userNo", "111"); userInfoList.add(paramMap);paramMap.put("userNoList", userInfoList);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
程序所需要的棧大小 > 允許最大的棧大小,執行深度: 24612
java.lang.StackOverflowError at com.collection.map.MapDemo.method(MapDemo.java:29) at com.collection.map.MapDemo.method(MapDemo.java:29) at com.collection.map.MapDemo.method(MapDemo.java:29) at com.collection.map.MapDemo.method(MapDemo.java:29) at com.collection.map.MapDemo.method(MapDemo.java:29)
程序詳解:
執行main函數會(huì )創(chuàng )建一個(gè)線(xiàn)程,同時(shí)創(chuàng )建一個(gè)虛擬機棧(棧內存)
調用statck.method()時(shí),會(huì )對method()進(jìn)行壓棧操作,將method()運行期數據的數據集(批注1)保存到棧幀1(Stack Frame)(如果main方法里面調用多個(gè)方法,會(huì )執行多個(gè)壓棧操作)。
method()遞歸調用時(shí),都會(huì )產(chǎn)生一個(gè)新的棧幀區塊,這時(shí)就會(huì )連續的產(chǎn)生新的棧幀區塊
當棧內存超過(guò)系統配置的棧內存-Xss:2048(批注2),就會(huì )出現java.lang.StackOverflowError異常。這也是為什么對于需要謹慎使用遞歸調用的原因!
批注1:基本數據類(lèi)型變量、引用類(lèi)型的變量、returnAddress類(lèi)型的變量、操作數棧、動(dòng)態(tài)鏈接、方法出口等批注2:-Xss 為jvm啟動(dòng)的每個(gè)線(xiàn)程分配的內存大小
模擬OutOfMemoryError堆棧溢出錯誤
public class Heap { public static void main(String[] args) { ArrayList list = new ArrayList(); while (true) { list.add(new Heap()); } }}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458)
List是動(dòng)態(tài)增長(cháng)的,因此容量不夠了,就會(huì )擴容,一旦空閑內存分配完畢,請求不到其他內存,就拋出OutOfMemoryError。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。