[翻譯]Java排錯指南 - 5 確定崩潰何地發生

fairjm發表於2018-12-05

本文來自 fairjm@圖靈社群 轉截請註明出處

這幾天公司其他組遇到了一個segmentation fault的問題,找到了這個官方文件,基於Java8,感覺不錯就翻譯了下. 一些地方翻譯比較生硬,如有問題請麻煩指正~^_^


原文地址: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/crashes001.html

這幾天公司其他組遇到了一個segmentation fault的問題,找到了這個官方文件,基於Java8,感覺不錯就翻譯了下.
一些地方翻譯比較生硬,如有問題請麻煩指正~^_^ by fairjm


5.1 確定崩潰發生在哪裡

這一節提供了一些例子來演示如何使用錯誤日誌來找到崩潰的原因,並且給出一些排查這些問題的建議.

錯誤日誌的頭指出了錯誤的型別和有問題的幀(frame),thread stack指出了當前的執行緒和堆疊軌跡.檢視Header Format

Crash in Native Code

Crash in Compiled Code

Crash in HotSpot Compiler Thread

Crash in VM Thread

Crash Due to Stack Overflow

5.1.1 原生程式碼崩潰

如果致命錯誤日誌(fatal error log)指出的問題幀來自於本地庫,那麼可能是本地庫或者JNI庫程式碼存在bug.這種崩潰當然也有可能是其他原因造成的,但是分析這個庫和其他的core file或者crash dump是一個很好的開始.
以下是一個致命錯誤日誌的頭:

# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  SIGSEGV (0xb) at pc=0x417789d7, pid=21139, tid=1024
#
# Java VM: Java HotSpot(TM) Server VM (6-beta2-b63 mixed mode)
# Problematic frame:
# C  [libApplication.so+0x9d7]

在這個例子中,SIGSEGV發生線上程執行libApplication.so中的程式碼.
一些其他例子是Java VM的本地庫導致的.在下面的例子中,JavaThread_thread_in_vm狀態中失敗了(表明它正在執行Java VM程式碼)

# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x08083d77, pid=3700, tid=2896
#
# Java VM: Java HotSpot(TM) Client VM (1.5-internal mixed mode)
# Problematic frame:
# V  [jvm.dll+0x83d77]

---------------  T H R E A D  ---------------

Current thread (0x00036960):  JavaThread "main" [_thread_in_vm, id=2896]
:
Stack: [0x00040000,0x00080000),  sp=0x0007f9f8,  free space=254k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [jvm.dll+0x83d77]
C  [App.dll+0x1047]          <========= C/native frame
j  Test.foo()V+0
j  Test.main([Ljava/lang/String;)V+0
v  ~StubRoutines::call_stub
V  [jvm.dll+0x80f13]
V  [jvm.dll+0xd3842]
V  [jvm.dll+0x80de4]
V  [jvm.dll+0x87cd2]
C  [java.exe+0x14c0]
C  [java.exe+0x64cd]
C  [kernel32.dll+0x214c7]
:

在這個例子中,雖然出問題的幀是VM的,執行緒棧顯示一個本地例子(native routine)App.dll已經被VM呼叫(可能是通過JNI).

解決這個本地庫崩潰的第一步是調查本地庫發生崩潰的那段原始碼.
- 如果本地庫是由你的應用程式提供,那麼調查本地庫的原始碼.大量的問題可以通過在執行應用程式時使用-Xcheck:jni引數被識別.檢視The -Xcheck:jni Option.
- 如果這個本地庫是由其他供應商提供,被你的程式所使用,那麼就向他們提供bug報告和致命錯誤日誌資訊.
- 如果本地庫是來自於JRE的(比如awt.dll,net.dll或其他的),那有可能你遇到了一個庫或者API的bug.那麼就儘可能獲取足夠的資訊提交一個bug並指名庫名稱.你可以在JRE的發行版中的 jre/lib 或 jre/bin目錄中找到JRE的庫.

如果可能的話,你可以通過本地debugger attach到core file或crash dump的方式來排查本地庫崩潰.取決於你所使用的系統,本地debugger有dbx,gdb,或windbg.檢視 Native Operating System Tools

5.1.2 編譯程式碼崩潰

如果致命錯誤日誌指出崩潰發生在編譯程式碼(compiled code),那有可能你遇到了一個編譯器導致的不正確程式碼生成的bug.你可以通過問題堆疊的型別是J(代表一個compiled java frame)來識別.

# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  SIGSEGV (0xb) at pc=0x0000002a99eb0c10, pid=6106, tid=278546
#
# Java VM: Java HotSpot(TM) 64-Bit Server VM (1.6.0-beta-b51 mixed mode)
# Problematic frame:
# J  org.foobar.Scanner.body()V
#
:
Stack: [0x0000002aea560000,0x0000002aea660000),  sp=0x0000002aea65ddf0,
free space=1015k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
J  org.foobar.Scanner.body()V

[error occurred during error reporting, step 120, id 0xb]

注意:該例子無法獲得一個完整的執行緒棧.輸出的"error occurred during error reporting" 表示問題發生在試圖獲取堆疊跟蹤(可能是棧損壞了).

通過更換編譯器的方式可能可以臨時解決(例如使用HotSpot Client VM取代HotSpot Server VM,或反過來)或者從編譯中排除掉導致崩潰的方法.在這個例子中,將64位的Server VM換成32位的Client VM可能會有用.

更多可能的規避措施見Working Around Crashes in the HotSpot Compiler Thread or Compiled Code.

5.1.3 HotSpot編譯器執行緒崩潰

如果核心錯誤日誌顯示的當前執行緒是一個叫CompilerThread0,CompilerThread1AdapterCompilerJavaThread,那麼你可能遇上了編譯器bug.可能解決方式和上一節一樣.(懶得復讀了...)

5.1.4 VM執行緒崩潰

如果核心錯誤日誌顯示的當前執行緒是VMThread,那麼檢視一下在THREAD那節包含VM_Operation的那一行.VMThread是特殊的的HotSpot VM執行緒.它執行一些特殊的工作比如GC.如果VM_Operation表明操作是GC,那麼你可能遇到了諸如堆損壞的問題.

包括GC問題之外,它同樣可能是一些其他問題(例如編譯器或者執行時bug)導致的物件引用在堆中處於一個不完整和不正確的狀態.這種情況下,收集儘可能多的環境資訊和嘗試可能的規避方案.如果發現和GC有關,你可能通過修改GC配置的方式來臨時保證正常執行.

對於更多的規避方案,檢視Working Around Crashes during Garbage Collection

5.1.5 爆棧崩潰

java語言的一個棧溢位通常會導致執行緒丟擲煩人的java.lang.StackOverflowError異常.另一方面,C和C++寫入超過了棧的結束會引起一個棧溢位.這是一個致命的錯誤,會導致程式終止.

在HotSpot實現中,Java方法和C/C++原生程式碼共享棧幀,即使用者原生程式碼和VM自身.
Java方法產生的程式碼會檢查棧離棧的結束是否會有固定距離可用的空間,所以原生程式碼的呼叫可以不擔心是否會超過棧空間.
到棧結束的距離被稱為Shadow Pages.這個大小取決於所在平臺,shadow pages在3到20頁之間.
這個距離是可以調整的,所以應用使用到了原生程式碼想要比預設更大的距離時可以增加shadow page的大小.
增加的引數是-XX:StackShadowPages=n,n設定為比當前平臺預設值大.

如果你的應用遇上了segmentation fault但是沒有core file或致命錯誤日誌參見Appendix A,或者在windows上STACK_OVERFLOW_ERROR,或者得到一個訊息"An irrecoverable stack overflow has occurred",這說明超過了StackShadowPages,需要更大的空間.

如果你增加了StackShadowPages,你可能也需要使用-Xss引數增加預設的執行緒棧大小.增加預設的執行緒棧大小可能會減少可建立的執行緒數,所以請小心選擇這個數值.執行緒棧的大小於不同平臺上在256KB到1024KB.

# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x10001011, pid=296, tid=2940
#
# Java VM: Java HotSpot(TM) Client VM (1.6-internal mixed mode, sharing)
# Problematic frame:
# C  [App.dll+0x1011]
#

---------------  T H R E A D  ---------------

Current thread (0x000367c0):  JavaThread "main" [_thread_in_native, id=2940]
:
Stack: [0x00040000,0x00080000),  sp=0x00041000,  free space=4k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [App.dll+0x1011]
C  [App.dll+0x1020]
C  [App.dll+0x1020]
:
C  [App.dll+0x1020]
C  [App.dll+0x1020]
...<more frames>...

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  Test.foo()V+0
j  Test.main([Ljava/lang/String;)V+0
v  ~StubRoutines::call_stub

你可以從例子中獲得以下資訊:
- 異常是EXCEPTION_STACK_OVERFLOW
- 執行緒的狀態是_thread_in_native,表示執行緒在執行native或者JNI程式碼.
- 執行緒資訊中,可用的空間僅僅只有4KB(windows系統中的單頁).另外執行緒指標(sp)在0x00041000,很接近棧結束0x00040000.
- 輸出的本地棧顯示一個遞迴的本地方法是這個問題的原因....<more frames>...表明還有更多的幀存在但是沒有輸出.輸出只限於100幀.


相關資料:
Do we need Unsafe in Java?

Shortest code that raises a SIGSEGV

Best way on how to solve/debug JVM crash (SIGSEGV)

openjdk相關bug:
JVM Crash in # Problematic frame: # J 569 C2 java.lang.Long.getChars(JI[C)V (221 bytes) @ 0x00007fb9b618dfd8 [0x00007fb9b618dc40+0x398]

JVM crash. Problematic frame: J 4518 C2 java.lang.Long.getChar

相關文章