公司最近買了一套老程式碼,在測試環境部署的時候發生了nested exception is java.lang.StackOverflowError
的異常,當時看到這個異常首先想到是棧記憶體溢位,網上給出的解決辦法就是加棧記憶體大小就行。趁著這個機會也瞭解一下什麼是Java虛擬機器棧。
Java虛擬機器棧
我們想要解決StackOverflowError
問題就得了解其內部工作機制,首先我們需要了解Java虛擬機器棧是什麼。瞭解Java虛擬機器棧之前我們先看一段程式碼。
public class TestStack {
public static void main(String[] args) {
a();
}
public static void a(){
int a = 10;
b();
}
public static void b(){
int b = 10;
}
}
複製程式碼
上面的一段程式碼非常簡單,整體流程就是
- 先執行main()方法
- 執行main()方法呼叫的a()方法,並且賦值a=10
- 執行a()方法呼叫的b()方法,並賦值b=10
現在讓我們瞭解上面一段簡單流程情況下棧的動作流程,首先我們需要知道棧是每個執行緒獨有的,而存放在棧裡面的叫做棧幀,每進入一個方法就會有一個棧幀入棧的動作,即下面的黃色方塊就是棧幀。每個棧幀裡面存放的是區域性變數表(例如此時的int a=10 ,a的值就存放在區域性變數表中)、運算元棧、動態連結、方法出口等等。
區域性變數表中存放的是編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、double、long)、物件引用(是指向存放的堆中例項物件的地址)
當每個方法執行完畢以後,就會將此棧幀彈出棧。
什麼造成了StackOverflowError
上面我們知道了每個執行緒都有自己獨有的棧空間,而棧幀中存放的私有的變數、物件地址引用、返回值都佔用這記憶體空間,如果執行緒棧的大小超過了規定的大小,那麼就會丟擲StackOverflowError
。例如我將上面程式碼稍微改一下
public class TestStack {
public static void main(String[] args) {
a();
}
public static void a(){
b();
}
public static void b(){
a();
}
}
複製程式碼
讓他一直迴圈呼叫,並且設定每個執行緒棧大小為160KXss160k
,就會發現不斷的加入棧幀,總會達到棧的最大值的,就會丟擲StackOverflowError
如何解決StackOverflowError
首先回到我們開頭的問題上,由於是買過來的程式碼,所以需要知道引起StackOverflowError
的是程式碼問題還是設定的棧記憶體小了。經過排檢視到有一塊驗證資訊的程式碼中用了責任鏈設計模式,在xml中設定的引用鏈條長達五十多個。因此在初始化的時候如果設定的棧記憶體不夠大的話那麼就會發生StackOverflowError
。
所以就直接增大棧記憶體大小就行了,我們使用的是公司給提供的Tomcat包部署的專案,經網上查詢在Tomcat中設定JVM引數只需要在catalina.sh
中設定JAVA_OPTS="$JAVA_OPTS -Xss500k"
引數即可,於是設定了此引數後又重新啟動,發現還是StackOverflowError
錯誤。
然後經過ps -ef |grep tomcat
檢視發現設定的棧大小沒有生效。於是在Tomcat目錄中grep -r "Xss" *
查詢看是哪裡配置的棧大小,最後發現是在setenv.sh
檔案中配置JVM資訊,而在setenv.sh
檔案中配置的是CATALINA_OPTS="$CATALINA_OPTS -Xss256k"
。
配置只是該Tomcat使用的環境變數,使用
CATALINA_OPTS
,而如果配置其他Java應用程式也要使用的環境變數,比如JBoss,在JAVA_OPTS
中設定
經過試驗發現如果在配置檔案中出現了相同的配置資訊。例如
CATALINA_OPTS="$CATALINA_OPTS -Xss300k"
JAVA_OPTS="$JAVA_OPTS -Xss200k"
複製程式碼
哪怕JAVA_OPTS
是在後面,那麼也是以CATALINA_OPTS
所配置的為準,如果兩個都是
CATALINA_OPTS="$CATALINA_OPTS -Xss300k"
CATALINA_OPTS ="$CATALINA_OPTS -Xss200k"
複製程式碼
那麼就以出現的先後順序,後出現的配置生效。隨後在setenv.sh
檔案中將棧空間大小增大問題也就隨之解決了。