C.4 线程部分格式
本节主要描述当JVM crash时候线程的信息。如果多线程在同一时刻crash,只有一个线程的信息会被打印出来。
C.4.1 线程信息
线程部分第一部分描述了引起严重错误的线程信息,如下所示:
Current thread (0x0805ac88): JavaThread "main" [_thread_in_native, id=21139]
| | | | +-- ID线程ID
| | | +------------- state线程状态
| | +-------------------------- name线程名称
| +------------------------------------ type线程类型
+--------------------------------------------------pointer计数器
线程计数器是指向JVM内部线程结构,如果你不想调试正在运行的JVM或者core文件的话,则该指针没有什么用处。
下面列出了可能的JVM线程类型
JavaThread
VMThread
CompilerThread
GCTaskThread
WatcherThread
ConcurrentMarkSweepThread
译注:具体线程含义还需要查看Oracle官方文档,但是具体也能见名知意。
下面列举了线程可能的状态:
线程 描述
_thread_uninitialized 线程没有创建,这只会出现在内存错误的前提下
_thread_new 线程被创建了,但是没有被start
_thread_in_native 线程运行在本地方法中,这意味着可能在本地代码存在Bug
_thread_in_vm 线程运行JVM代码
_thread_in_Java 线程运行在解释模式或者编译模式Java代码
_thread_blocked 线程被阻塞
_thread_trans 如果上面任何状态后面跟着这个标志,意味着线程在改变运行状态
在该输出中的线程ID代表一个本地线程标识(注:这应该jmm里面的,目前没到那种程度分析)
如果Java线程是dameon的话,则dameon会在线程状态前打印
C.4.2 信号信息
在错误日志下一部分信息是导致JVM终止的信号信息,在Windows平台下,输出如下所示:
siginfo: ExceptionCode=0xc0000005, reading address 0xd8ffecf1
在上述例子中,错误码是0xc0000005(ACCESS_VIOLATION),并且错误发生在线程试图读取地址
0xd8ffecf1
在Solaris或者Linux系统中,信号数字和信号码(这个有点糊涂)被用来识别异常信息,如下所示:
siginfo:si_signo=11, si_errno=0, si_code=1, si_addr=0x00004321
C.4.3 寄存器内容
错误日志的下一部分描述了错误发生时候寄存器的内容,具体输出格式和硬件架构相关,下面的例子描述在IA32下的输出内容:
Registers:
EAX=0x00004321, EBX=0x41779dc0, ECX=0x080b8d28, EDX=0x00000000
ESP=0xbfffc1e0, EBP=0xbfffc1f8, ESI=0x4a6b9278, EDI=0x0805ac88
EIP=0x417789d7, CR2=0x00004321, EFLAGS=0x00010216
寄存器内容可以和即将描述的指令结合起来,这是十分有用的对于指令排查
C.4.4 指令
在输出寄存器值后,错误日志包含了栈顶上的指令集合(128个字节码操作集合)在程序计数器PC附近,当JVM崩溃的时候。这些字节码可以被汇编器解码成对应的字节码指令,注意IA32和AMD64指令在长度上不一致,因此并不是每次都能解码成功(注:难道AMD64上有可能解析不出来?)
Top of Stack: (sp=0xbfffc1e0)
0xbfffc1e0: 00000000 00000000 0818d068 00000000
0xbfffc1f0: 00000044 4a6b9278 bfffd208 41778a10
0xbfffc200: 00004321 00000000 00000cd8 0818d328
0xbfffc210: 00000000 00000000 00000004 00000003
0xbfffc220: 00000000 4000c78c 00000004 00000000
0xbfffc230: 00000000 00000000 00180003 00000000
0xbfffc240: 42010322 417786ec 00000000 00000000
0xbfffc250: 4177864c 40045250 400131e8 00000000
Instructions: (pc=0x417789d7)
0x417789c7: ec 14 e8 72 ff ff ff 81 c3 f2 13 00 00 8b 45 08
0x417789d7: 0f b6 00 88 45 fb 8d 83 6f ee ff ff 89 04 24 e8
C.4.5 线程堆栈
大部分情况下,错误日志的下一部分是线程堆栈,这包含栈帧的栈顶和栈底也就是SP和BP的地址,当前栈指针(这个稍微有点不明白),以及没有被当前线程使用堆栈内存数量。随后的就是栈帧的详细调用信息,最多100个栈帧会被打印出来。对于C/C++栈帧,库名也会被打印出来。需要记住的是,在某些错误情形下,栈帧可能已经破损,因此相应的详细信息可能不会被完全打印出来。
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]
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]
C [java.exe+0x14c0]
C [java.exe+0x64cd]
C [kernel32.dll+0x214c7]
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
上面的日志包含两类线程堆栈栈帧
1、第一种是本地栈帧,它会打印出本地线程所有的方法调用,然而这类线程堆栈并没有把运行时inline的Java方法包括进去,因此如果方法被inline的话,则该方法会成为父栈帧的一部分。
在本地栈帧中的方法信息为调查JVM crash提供了重要的信息,通过分析上述列表中列举的库名称,你能够推断出那个库导致错误出现,并且将该信息报告给库的开发人员。
2、第二种是Java堆栈,这会打印出Java方法堆栈包括inline的方法,不包含本地堆栈,根据crash的结果,有可能打不出本地堆栈,而打出Java堆栈
C.4.6 进一步的信息
如果错误发生在VM线程或者一个compile线程中,更进一步的信息会被打印出来。例如,考虑VM线程的情况,如果VM线程正在执行一个VM操作的时候,如果此时错误发生,则该操作会被打印出来。在下面的输出例子中,compiler线程触发了一个错误,此时HotSpot client虚拟机正在编译一个方法,方法是hs101t004Thread.ackermann
Current CompileTask:
HotSpot Client Compiler:754 b
nsk.jvmti.scenarios.hotswap.HS101.hs101t004Thread.ackermann(IJ)J (42 bytes)
对于HotSpot Server虚拟机,该compile线程输出的结果略微不同,但也会包含完整的类名和方法名称。
译注:这节翻译的很晦涩,有些概念自己理解的也是模棱两可,望大家见谅和指正。