JVM致命错误日志(hs_err_pid.log)分析
当JVM出现致命错误时,会生成一个错误文件 hs_err_pid<pid>.log
文件,其中包括了导致 JVM crash 的重要信息,可通过分析该文件定位到导致 crash 的根源。此文件默认会生成在工作目录下。也可以通过 jvm 参数指定生成路径(JDK6中引入):
1 | -XX:ErrorFile=./hs_err_pid<pid>.log |
hs_err_pid.log文件内容
以下是几个关键的信息
日志头文件
1 |
|
这里一个重要信息是“SIGSEGV(0xb)
”表示jvm crash
时正在执行jni
代码,而不是在执行java
或者jvm
的代码,如果没有在应用程序里手动调用jni
代码,那么很可能是JIT
动态编译时导致的该错误。其中SIGSEGV
是信号名称,0xb
是信号码,pc=0x00007fb8b18fdc6c
指的是程序计数器的值,pid=191899
是进程号,tid=140417770411776
是线程号。
PS:除了“SIGSEGV(0xb)”以外,常见的描述还有“EXCEPTION_ACCESS_VIOLATION”,该描述表示jvm crash时正在执行jvm自身的代码,这往往是因为jvm的bug导致的crash;另一种常见的描述是“EXCEPTION_STACK_OVERFLOW”,该描述表示这是个栈溢出导致的错误,这往往是应用程序中存在深层递归导致的。
还有一个重要信息是:
1 | # Problematic frame: |
这表示出现crash
时jvm
正在执行的代码,这里的“J
”表示正在执行java
代码,后面的表示执行的方法栈。除了“J
”外,还有可能是“C
”、“j
”、“V
”、“v
”,它们分别表示:
C
:Native C frame
j
:Interpreted Java frame
V
:VMframe
v
:VMgenerated stub frame
J
:Other frame types, including compiled Java frames
加上前面对SIGSEGV(0xb)
的分析,现在可以断定是JIT
动态编译导致的该错误。
经过查阅发现:
此异常是由于jdk JIT compiler optimization
导致,bug id 8021898
,官网描述如下:
1 | The JIT compiler optimization leads to a SIGSEGV or an NullPointerException at a place it must not happen. |
jdk1.7.0_25
到1.7.0_55
这几个版本都存在此bug
,1.7.0_60
后修复。可通过升级jdk解决此异常,具体参阅 http://bugs.java.com/view_bug.do?bug_id=8021898。
到这里该问题已经分析出原因了,但是咱们可以再深入一步,分析下其它信息。
导致crash
的线程信息
文件下面是导致crash
的线程信息和该线程栈信息,描述信息如下:
1 | Current thread (0x00007fb7b4014800): JavaThread "catalina-exec-251" daemon [_thread_in_Java, id=205044, stack(0x00007fb58f435000,0x00007fb58f536000)] |
以上表示导致出错的线程是0x00007fb7b4014800
(指针),线程类型是JavaThread
,JavaThread
表示执行的是java
线程,关于该线程其它类型还可能是:
VMThread
:jvm
的内部线程
CompilerThread
:用来调用JITing
,实时编译装卸class
。 通常,jvm
会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,例如:CompilerThread1
GCTaskThread
:执行gc
的线程WatcherThread
:jvm
周期性任务调度的线程,是一个单例对象。 该线程在JVM
内使用得比较频繁,比如:定期的内存监控、JVM
运行状况监控,还有我们经常需要去执行一些jstat
这类命令查看gc
的情况ConcurrentMarkSweepThread
:jvm
在进行CMS GC
的时候,会创建一个该线程去进行GC
,该线程被创建的同时会创建一个SurrogateLockerThread
(简称SLT
)线程并且启动它,SLT
启动之后,处于等待阶段。CMST
开始GC
时,会发一个消息给SLT
让它去获取Java
层Reference
对象的全局锁:Lock
后面的”catalina-exec-251”表示线程名,带有catalina前缀的线程一般是tomcat启动的线程,“daemon”表示该线程为守护线程,再后面的“[_thread_in_Java”表示线程正在执行解释或者编译后的Java代码,关于该描述其它类型还可能是:
- _thread_in_native:线程当前状态
- _thread_uninitialized:线程还没有创建,它只在内存原因崩溃的时候才出现
- _thread_new:线程已经被创建,但是还没有启动
- _thread_in_native:线程正在执行本地代码,一般这种情况很可能是本地代码有问题
- _thread_in_vm:线程正在执行虚拟机代码
- _thread_in_Java:线程正在执行解释或者编译后的Java代码
- _thread_blocked:线程处于阻塞状态
- …_trans:以_trans结尾,线程正处于要切换到其它状态的中间状态
最后的“id=205044”表示线程ID,stack(0x00007fb58f435000,0x00007fb58f536000)表示栈区间。
“siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x0000003f96dc9c6c”这部分是导致虚拟机终止的非预期的信号信息:其中si_errno和si_code是Linux下用来鉴别异常的,Windows下是一个ExceptionCode。
所有线程信息
再下面是线程信息:
1 | Java Threads: ( => current thread ) |
信息和上面介绍的类似,其中[_thread_blocked表示线程阻塞。
安全点和锁信息
再下面是安全点和锁信息:
1 | VM state:not at safepoint (normal execution) |
安全线信息为正常运行,其它可能得描述还有:
- not at a safepoint:正常运行状态
- at safepoint:所有线程都因为虚拟机等待状态而阻塞,等待一个虚拟机操作完成
- synchronizing:一个特殊的虚拟机操作,要求虚拟机内的其它线程保持等待状态
锁信息为未被线程持有,Mutex是虚拟机内部的锁,而Monitor则是synchronized锁或者其它关联到的Java对象。
堆信息
再下面是堆信息:
1 | Heap |
堆信息包括:新生代、老生代、永久代信息。这里标识了使用CMS垃圾收集器。
下面的“Card table”表示一种卡表,是jvm维护的一种数据结构,用于记录更改对象时的引用,以便gc时遍历更少的table和root。
本地代码缓存
再下面是本地代码缓存信息:
1 | Code Cache [0x00007fb8b1000000, 0x00007fb8b1a60000, 0x00007fb8b4000000) |
这是一块用于编译和保存本地代码的内存;注意是本地代码,它和PermGen(永久代)是不一样的,永久代是用来存放jvm和java类的元数据的。
编译事件
再下面是本地代码编译信息:
1 | Compilation events (10 events): |
可以看到,一共编译了10次;其中包含org.apache.http.impl.cookie.BestMatchSpec::formatCookies的编译;这和前面的结论相吻合。
gc相关记录
再下面是gc执行记录:
1 | GC Heap History (10 events): |
可以看到gc次数为10次(full gc),然后后面描述了每次gc前后的内存信息;看一看到并没有内存不足等问题。
jvm内存映射
再下面是jvm加载的库信息:
1 | Dynamic libraries: |
这些信息是虚拟机崩溃时的虚拟内存列表区域。它可以告诉你崩溃原因时哪些类库正在被使用,位置在哪里,还有堆栈和守护页信息。以列表中第一条为例介绍下:
- 00400000-00401000:内存区域
- r-xp:权限,r/w/x/p/s分别表示读/写/执行/私有/共享
- 00000000:文件内的偏移量
- 08:02:文件位置的majorID和minorID
- 39454583:索引节点号
- /home/service/jdk1.7.0_55/bin/java:文件位置
jvm启动参数
再下面是jvm启动参数信息:
1 | VM Arguments: |
上面是jvm参数,下面是系统的环境配置。
服务器信息
再下面是服务器信息:
1 | /proc/meminfo: |
上面是内存信息,主要关注下swap信息,看看有没有使用虚拟内存;下面是cpu信息。
一份完整的hs_err_pid.log文件
以下的错误本质原因是:创建了太多的线程,没有及时回收,而给JVM分配的内存越多,那么你能创建的线程数就越少。解决方案:改小堆内存
1 | # |