之因此想寫這篇文章,實際上是由於最近有很多系統出現了棧溢出致使進程crash的問題,而且很隱蔽,根本緣由還得藉助coredump才能分析出來,因而想從JVM實現的角度來全面分析下棧溢出的這類問題,或許你碰到過以下的場景:java
日誌裏出現了StackOverflowError的異常linux
進程忽然消失了,可是留下了crash日誌web
進程消失了,crash日誌也沒有留下緩存
這些均可能是棧溢出致使的。安全
上面提到的後面兩種狀況有可能不是咱們今天要聊的棧溢出的問題致使的crash,也許是別的一些可能,那如何肯定上面三種狀況是棧溢出致使的呢?jvm
出現了StackOverflowError,這種毫無疑問,必然是棧溢出,具體什麼方法致使的棧溢出從棧上是能知道的,不過要提醒一點,咱們打印出來看到的棧多是不全的,由於JVM裏對棧的輸出條數是能夠控制的,默認是1024,這個參數是-XX:MaxJavaStackTraceDepth=1024
,能夠將這個參數設置爲-1,那將會所有輸出對應的堆棧函數
若是進程消失了,可是留下了crash日誌,那請檢查下crash日誌裏的Current thread的stack範圍,以及RSP寄存器的值,若是RSP寄存器的值是超出這個stack範圍的,那說明是棧溢出了。ui
若是crash日誌也沒有留下,那隻能經過coredump來分析了,在進程運行前,先執行ulimit -c unlimited
,而後再跑進程,在進程掛掉以後,會產生一個core.<pid>
的文件,而後再經過jstack $JAVA_HOME/bin/java core.<pid>
來看輸出的棧,若是正常輸出了,那就能夠看是否存在很長的調用棧的線程,固然還有可能沒有正常輸出的,由於jstack的這條從core文件抓棧的命令實際上是基於serviceability agent來實現的,而SA在某些版本里是存在bug的,固然如今的SA也不能說徹底沒有bug,仍是存在很多bug的,祝你好運。spa
這個須要具體問題具體分析,由於致使棧溢出的緣由不少,提三個主要的: * java代碼寫得不當,好比出現遞歸死循環,這也是最多見的,只能靠寫代碼的人稍微當心了 * native代碼有棧上分配的邏輯,而且要求的內存還不小 * 線程棧空間設置比較小.net
有時候咱們的代碼須要調用到native裏去,最多見的一種狀況譬如java.net.SocketInputStream.read0
方法,這是一個native方法,在進入到這個方法裏以後,它首先就要求到棧上去分配一個64KB的緩存(64位linux),試想一下若是執行到read0這個方法的時候,剩餘的棧空間已經不足以分配64KB的內存了會怎樣?也許就是一開頭咱們提到的crash,這只是一個例子,還有其餘的一些native實現,包括咱們本身也可能寫這種native代碼,若是真有這種狀況,咱們就須要好好斟酌下咱們的線程棧到底要設置多大了。
若是咱們的代碼確實存在正常的很深的遞歸調用的話,一般是咱們的棧可能設置過小,咱們能夠經過-Xss
或者-XX:ThreadStackSize
來設置java線程棧的大小,若是兩個參數都設置了,那具體有效的是寫在後面的那個生效。順便提下,線程棧內存是和java heap獨立的內存,並非在java heap內分配的,是直接malloc分配的內存。
在jvm裏,線程其實不只僅只有一種,好比咱們java裏建立的叫作java線程,還有gc線程,編譯線程等,默認狀況下他們的棧大小以下:
可見默認狀況下編譯線程須要的棧空間是其餘種類線程的4倍。
各類類型的線程他們所須要的棧的大小實際上是能夠經過不一樣的參數來控制的:
java_thread
的stack_size,其實就是-Xss或者-XX:ThreadStackSize的值
compiler_thread
的stack_size,是-XX:CompilerThreadStackSize指定的值
vm內部的線程好比gc線程等能夠經過-XX:VMThreadStackSize來設置
JVM裏的棧溢出究竟是怎麼實現的,得從棧的大體結構提及:
會預留兩塊受保護的內存區域,分別叫作yellow page和red page,其中yellow page在前,另外若是是java建立的線程,最後並無圖示的一個page的glibc guard page
,非java線程是有的,可是沒有yellow和red page,好比咱們的gc線程,注意編譯線程實際上是java線程。
除了yellow page和red page,其實還有個shadow page,這三個page能夠分別經過vm參數-XX:StackYellowPages
,-XX:StackRedPages
,-XX:StackShadowPages
來控制。當咱們要調用某個java方法的時候,它須要多大的棧實際上是預先知道的,javac裏就計算好了,可是若是調用的是native方法,那這就很差辦了,在native方法裏到底須要多大內存,這個沒法得知,所以shadow page就是用來作一個大體的預測,看須要多大的棧空間,若是預測到新的RSP的值超過了yellowpage的位置,那就直接拋出棧溢出的異常,不然就去新的方法裏處理,當咱們的代碼訪問到yellow page或者red page裏的地址的時候,由於這塊內存是受保護的,因此會產生SIGSEGV的信號,此時會交給JVM裏的信號處理函數來處理,針對yellow page以及red page會有不一樣的處理策略,其中yellow page的處理是會拋出StackOverflowError的異常,進程不會掛掉,也就是文章開頭提到的第一個場景,可是若是是red page,那將直接致使進程退出,不過仍是會產生Crash的日誌,也就是文章開頭提到的第二個場景,另外還有第三個場景,實際上是沒有棧空間了而且訪問了超過了red page的地址,這個時候由於棧空間不夠了,因此信號處理函數都進不去,所以就直接crash了,crash日誌也不會產生。
瞭解上面的場景以後,再回過頭來想一想JVM爲何要設置這幾個page,實際上是爲了安全,能預測到棧溢出的話就拋出StackOverfolwError,而避免致使進程掛掉。