1. 程式人生 > >Stack棧(優化配置、程式碼樣例)

Stack棧(優化配置、程式碼樣例)

最近有段時間沒有更新Netty的教程了,卻發了一些其他的東西。可能有的朋友會問,難道這就完事了?不會的。兩方面原因。第一、筆者也是需要工作的人,自然要完成好工作中的任務,這裡面也有很多東西需要學習和研究,這也是我分享的方向和源泉。第二、想要掌握好Netty,周邊的知識也不能少,這也是筆者在惡補的東西。所以,你完全可以把我最近分享的東西理解為為學習Netty所做的準備。

Java虛擬機器定義了若干種程式執行期間會使用到的執行時資料區,其中有一些會隨著虛擬機器啟動而建立,隨著虛擬機器退出而銷燬。另外一些則是與執行緒一一對應的,這些與執行緒對應的資料區域會隨著執行緒開始和結束而建立和銷燬。

Stack棧

棧也叫棧記憶體,是Java程式的執行區,是線上程建立時建立,它的生命期是跟隨執行緒的生命期,執行緒結束棧記憶體也就釋放,對於棧來說不存在垃圾回收問題,只要執行緒一結束,該棧就Over。問題出來了:棧中存的是那些資料呢?又什麼是格式呢? 棧中的資料都是以棧幀(Stack Frame)的格式存在,棧幀是一個記憶體區塊,是一個數據集,是一個有關方法(Method)和執行期資料的資料集,當一個方法A被呼叫時就產生了一個棧幀F1,並被壓入到棧中,A方法又呼叫了B方法,於是產生棧幀F2也被壓入棧,執行完畢後,先彈出F2棧幀,再彈出F1棧幀,遵循“先進後出”原則。 那棧幀中到底存在著什麼資料呢?棧幀中主要儲存3類資料:本地變數(Local Variables

),包括輸入引數和輸出引數以及方法內的變數;棧操作(Operand Stack),記錄出棧、入棧的操作;棧幀資料(Frame Data),包括類檔案、方法等等。光說比較枯燥,我們畫個圖來理解一下Java棧,如下圖所示:

圖示在一個棧中有兩個棧幀,棧幀2是最先被呼叫的方法,先入棧,然後方法2又呼叫了方法1,棧幀1處於棧頂的位置,棧幀2處於棧底,執行完畢後,依次彈出棧幀1和棧幀2,執行緒結束,棧釋放。

根據Java虛擬機器規範,Java虛擬機器規範允許Java虛擬機器棧被實現成固定大小的或者是根據計算動態擴充套件和收縮的。如果採用固定大小的Java虛擬機器棧設計,那每一條執行緒的Java虛擬機器棧容量應當在執行緒建立的時候獨立地選定。Java

虛擬機器實現應當提供給程式設計師或者終端使用者調節虛擬機器棧初始容量的手段,對於可以動態擴充套件和收縮Java虛擬機器棧來說,則應當提供調節其最大、最小容量的手段。 如果執行緒請求分配的棧容量超過Java虛擬機器棧允許的最大容量時,Java虛擬機器將會丟擲一個StackOverflowError異常。

根據上面的分析,我們來構造一個StackOverflowError來加深理解:

/**
 * JVM引數配置
 * 
 * @author lihzh(OneCoder)
 * @alia OneCoder
 * @Blog http://www.coderli.com
 * @date 2012-7-31 下午9:04:26
 */
public class JVMParams {
	/**
	 * @author lihzh(OneCoder)
	 * @date 2012-7-31 下午9:04:27
	 */
	public static void main(String[] args) {
		getStackOverFlowError(0);
	}

	/**
	 * 通過遞迴呼叫,誘發StackOverFlowError
	 * 
	 * @author lihzh
	 * @alia OneCoder
	 */
	private static void getStackOverFlowError(int count) {
		if (count < 10000) {
			System.out.println("count:" + count);
			getStackOverFlowError(++count);
		}
	}
}

count:4388 Exception in thread “main” java.lang.StackOverflowError at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564) at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619) at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:561) at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)

在遞迴呼叫4388遍時,出現stackoverflow異常。(該資料不穩定,大約在4300行左右)。

下面修改下虛擬機器棧的大小再配置一下,根據我們之前的文章介紹。設定

-Xss1024k。

count:13404 Exception in thread “main” java.lang.StackOverflowError at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)

發現確實有明顯的變化。再增大一倍試試。

count:24618 count:24619 Exception in thread “main” java.lang.StackOverflowError

Java虛擬機器規範》中對於Stack還有一段描述:

如果Java虛擬機器棧可以動態擴充套件,並且擴充套件的動作已經嘗試過,但是目前無法申請到足夠的記憶體去完成擴充套件,或者在建立新的執行緒時沒有足夠的記憶體去建立對應的虛擬機器棧,那Java虛擬機器將會丟擲一個OutOfMemoryError異常。

我們再來,構造一個 OutOfMemoryError 異常。設定

-Xss170m

(具體臨界值取決與測試機的系統環境,和JVM配置)。再跑一次,果然丟擲:

Error occurred during initialization of VM java.lang.OutOfMemoryError: unable to create new native thread

這裡網上給出了計算可以創建出最大執行緒數的計算公式:

MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = 執行緒數
MaxProcessMemory 指的是一個程序的最大記憶體
JVMMemory         JVM記憶體
ReservedOsMemory  保留的作業系統記憶體
ThreadStackSize      執行緒棧的大小

OneCoder在這裡猜測,是否是當計算出的執行緒數 <1 時,便會丟擲這樣的異常。這個公式涉及到4個值,不過有的可以有據可查的。在32位系統下,MaxProcessMemory一般認識為2GJVM記憶體我們可以通過-Xmx指定。保留的作業系統記憶體,網上說一般是120m左右,OneCoder這裡無據可查,暫不考慮。

不過可以看到,棧空間的分配是用的JVM之外的部分(如果錯誤,還請指正。),我們可以控制的是-Xmx的大小,所以OneCoder這裡把-Xmx設定成10m(改小。),測試-Xms180m的情形,果然可以成功運行了。

到這裡,OneCoder其實認為研究還遠未結束,OneCoder根據上面的公式和OneCoder的猜測,-Xss的最大值怎麼也不應該這麼小,當然這很可能是我猜測錯了,但是真像是什麼呢?。無奈一時間也無法驗證,暫且就當存疑吧,希望隨著對JVM的瞭解,能逐漸揭開一團。

OneCoder提醒:由此可見-Xss引數的設定是需要非常訊息的,太大,則可能會無法建立足夠的執行緒,太小,則可能無法進行足夠深層次的遞迴。需要你對的你程式有足夠的瞭解和把握的基礎上,合理的優化棧的大小。

希望高手不吝賜教。感激不盡。

參考:

  • 《Java虛擬機器規範》
  • 《慢慢琢磨JVM》