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


原文地址: 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM