Java開發筆記(七十四)內存溢出的兩種錯誤

前面介紹的幾種異常,其實都存在這樣那樣的邏輯問題,屬於程序員的編碼手誤。還有一大類系統錯誤,表面上看不出什麼問題,可是程序仍然運行不下去,茲舉二例說明。
第一個例子且看下列的測試代碼:html

	// 測試內存溢出錯誤:程序須要的內存超過了最大的堆內存配置
	private static void testUnlimitedString() {
		String str = "Hello world";
		String result = getUnlimitedString(str); // 獲取無限大小的字符串
		System.out.println("result="+result.toString());
	}

	// 獲取無限大小的字符串
	private static String getUnlimitedString(String str) {
		System.out.println("getUnlimitedString");
		String append = String.format("%s+%s", str, str);
		return getUnlimitedString(append);
	}

執行測試代碼中的testUnlimitedString方法,一開始程序正常打印日誌,然而不一下子就報錯退出了,錯誤信息爲「java.lang.OutOfMemoryError: Java heap space」,意思是內存溢出。仔細閱讀測試代碼,發現其中的getUnlimitedString方法會調用自身,從而造成了遞歸調用。要命的是,方法遞歸的同時不斷拼接更長的字符串append;而這意味着,每次遞歸調用以後,新的append串長度都要翻番;通過屢次調用,append串所需的存儲空間以指數級別增加,因而沒多久便撐爆了程序所能用到的內存了。java

第二個例子依舊先看下面的下面的測試代碼:程序員

	// 測試棧溢出錯誤:程序佔用的棧空間超過了配置的棧內存大小
	private static void testUnlimitedRecursion() {
		recursionAction(); // 用於遞歸動做的方法
	}

	// 用於遞歸動做的方法
	public static void recursionAction() {
		System.out.println("recursionAction");
		recursionAction();
	}

執行測試代碼中的testUnlimitedRecursion方法,結果仍是很快就報錯退出了,錯誤信息爲「java.lang.StackOverflowError」,意思是棧溢出。但是第二個例子在遞歸調用中並未拼接字符串,爲啥仍舊出現溢出錯誤了呢?這是由於程序在運行時會申請兩塊內存空間,一塊叫堆內存,另外一塊叫棧內存;其中堆內存承包了程序運行所需的大部分存儲需求,包括變量、數組、對象實例等等;而棧內存僅僅負責保管每次方法調用的現場數據,包括方法自身、方法的輸入參數、方法內部的基本變量等等,並在方法調用結束時釋放該方法佔用的內存空間。前述的第一個例子,它的內存溢出發生於堆內存;至於後面的第二個例子,它的內存溢出發生於棧內存。數組

那麼爲何方法調用的有關數據放在棧內存而不是堆內存呢?舉個現實生活中的例子,假設一對小夫妻帶着寶寶回家過年,隨身攜帶的物品都放在行李箱裏,則行李箱就是屬於他們的堆內存。而後一家三口準備坐動車回來,在路上還得處理一些事情,每件事情都至關於一次方法調用。例如在車站買車票,用手掏出錢包,抽出人民幣付款買完車票,再把錢包塞回去。在這個買車票的方法中,輸入參數是錢包,輸出參數是車票,而手充當了棧內存的角色。買票以前,兩手空空;買票的過程當中,一隻手抓着錢包;買完票後,錢包塞回去,兩手又變空了。打電話也可看做是方法調用,打電話前,兩手空空;打電話的時候,一隻手握住手機通話;打完電話,收好手機,兩手依然空空。此時雙手屬於分配給他們的棧內存,因爲有兩隻手,所以棧內存的大小爲二,即最多同時辦理兩件事情。
一家三口檢票上車,女人有事走開了一下子,這時寶寶餓得大哭,男人趕忙泡奶給寶寶喂。只見這個奶爸先用左手抱着寶寶,再用右手扶着奶瓶,至關於餵奶事件擁有方法嵌套,外層的餵奶方法佔用了左手這塊棧內存,內層的扶奶瓶方法又佔用了右手這塊棧內存。寶寶還在喝奶的時候,苦逼的奶爸突然內急,因而抱着寶寶一邊餵奶一邊飛奔至廁所,站在馬桶面前準備小便,猛然發現兩隻手都在忙,沒法解手。只有等寶寶喝完奶,右手把奶瓶放旁邊,這樣空出來的右手才能幫忙方便。可是寶寶喝奶的方法還沒結束調用,上廁所的方法已經等不及了,怎麼辦怎麼辦?可憐的奶爸情急之下只好尿褲子了,對程序來講便發生了棧內存溢出。要麼有個路人伸出援手(棧內存大小加一),一把扯下奶爸的褲子,方能避免尿褲子的尷尬(棧溢出的錯誤)。
總結一下,凡是編碼問題形成的程序崩潰,都歸類爲異常Exception;凡是系統不堪重負形成的程序崩潰,都歸類爲錯誤Error。不過異常與錯誤僅是分類上的區別,實際開發中,兩者的扔出和捕捉操做無甚差異,因此若沒有特殊狀況,從此將使用「異常」一詞統稱異常與錯誤。app



更多Java技術文章參見《Java開發筆記(序)章節目錄測試

相關文章
相關標籤/搜索