tomcat莫名其妙崩溃了,但是也没有生成hs_err_<pid>.log日志,但是生成了core日志,tomcat的日志下边只报了一下边的错误:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0xff1d7e48, pid=24649, tid=113
#
# JRE version: Java(TM) SE Runtime Environment (7.0_80-b15) (build 1.7.0_80-b15)
# Java VM: Java HotSpot(TM) Server VM (24.80-b11 mixed mode solaris-sparc )
# Problematic frame:
# C [libc.so.1+0x57e48]# [ timer expired, abort... ]
C一般指的是调用本地代码引起的问题,比如JNI,之后排除了是因为调用图片库jmagick库引起的,这个库会调用动态库。那么这个问题是什么原因引起的,经过分析了JVM的C/C++源码,画了张流程图,如下:
这里做下简单说明:
2:JVM是运行在操作系统上,比如这里是Solaris操作系统上,则JVM启动时候注册了操作系统底层的信号处理方法,这些信号一般是内存溢出等信号,操作系统负责直接和物理内存打交道,因此在启动在捕获到这些问题时,会把这些错误信号抛给应用程序;
3:JVM的WatchThread随着JVM启动,其定时会被唤醒来检测JVM的运行情况,比如其观察一个致命错误标志全局变量,这个变量一旦出现问题,则JVM在调用信号处理方法进行处理时,WatchThread也会在后台监听,防止致命错误方法处理线程自己出现死锁等问题,这个进程会等待2分钟,然后直接kill调JVM;
4:我们的程序调用底层的JNI库出现内存溢出等问题,被操作系统捕获到,操作系统会抛出这个信号给JVM,JVM捕捉到后处理;
5:JVM错误处理线程设置全局致命错误标志位true,并且筹备打印hs_err_<pid>.log日志,不过这个线程在做这个事情的过程中,有可能自己也会崩溃,比如非法访问内存,他自己崩溃后又再次触发操作系统抛信号,然后再用一个新的JVM致命错误线程处理方法来处理这个信号错误,如果再次出现错误,则会重复捕获处理,重复抛出,直到遇到死锁;
6:与此同时,WatchThread在背后冷冷地注视着这一切,当他老人家发现全局致命错误日志为true时,他老人家只会等待2分钟,然后kill掉JVM。
注意,在上边的情况下,就会出现hs_err_<pid>.log也不会被打印出来的问题,但是这个情况下会生成core日志,因此我们可以使用gdb命令来分析core日志,关于core日志的作用是什么,他是JVM崩溃瞬间的内存快照,至于其他更详细的资料,大家自己查,这里不再赘述。
这里粘贴下JVM的C/C++部分关键源码,如图:
A:这个图是在Thread.cpp文件中,是WatchThread类中的代码:
B:下图是打日志的函数,在vmError.cpp文件中:
C:下图是信号错误处理函数,也在vmError.cpp文件中,如图:
至于其他注册信号处理方法的类,大家看那副流程图即可,虽然我画的很差劲。
发生这个hs_err_<pid>.log日志没有生成的问题是,这个错误处理线程在写日志的时候,由于要申请内存和锁,而再次引起内存错误,进而引起操作系统再次抛出SIGSEGV错误,这个时候一个新的线程和当前这个写日志的线程出现死锁或者是出现连锁反应一直抛出SIGSEGV而导致处理时间超过了2分钟,最终被WatchThread干掉整个JVM的问题引起的。
有的时候生成hs_err_<pid>.log文件后,一般不会再生成core文件。有的资料说这是因为操作系统认为程序已经捕获到该异常,因此操作系统自己不会再dump出core日志,相反如果生成hs_err_<pid>.log失败,操作系统会认为应用没有处理这个错误信号,则自己会生成一个core文件,这个还需要再进一步看JVM的源码,由于时间问题,打算将来再去看这些源码,有兴趣的人可以研究后给我留言,这方面资料网上极其匮乏,这里把研究心得分享出来,供大家参考。