一次奇怪的StackOverflowError問題查詢之旅

不學無數的程式設計師發表於2019-05-23

公司最近買了一套老程式碼,在測試環境部署的時候發生了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;
    }
}

複製程式碼

上面的一段程式碼非常簡單,整體流程就是

  1. 先執行main()方法
  2. 執行main()方法呼叫的a()方法,並且賦值a=10
  3. 執行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檔案中將棧空間大小增大問題也就隨之解決了。

相關文章