jvm crash的崩溃日志详细分析及注意点

生成

1. 生成error 文件的路径:你可以通过参数设置-XX:ErrorFile=/path/hs_error%p.log, 默认是在Java运行的当前目录 [default: ./hs_err_pid%p.log]

2. 参数-XX:OnError  可以在crash退出的时候执行命令,格式是-XX:OnError=“string”,  <string> 可以是命令的集合,用分号做分隔符, 可以用"%p"来取到当前进程的ID.

例如:

// -XX:OnError="pmap %p"  // show memory map
// -XX:OnError="gcore %p; dbx - %p" // dump core and launch debugger

在Linux中系统会fork出一个子进程去执行shell的命令,因为是用fork可能会内存不够的情况,注意修改你的 /proc/sys/vm/overcommit_memory 参数,不清楚为什么这里不使用vfork

3. -XX:+ShowMessageBoxOnError 参数,当jvm crash的时候在linux里会启动gdb 去分析和调式,适合在测试环境中使用。

什么情况下不会生成error文件

linux 内核在发生OOM的时候会强制kill一些进程, 可以在/var/logs/messages中查找

Error crash 文件的几个重要部分

a.  错误信息概要

# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x0000000000043566, pid=32046, tid=1121192256
#
# JRE version: 6.0_17-b04
# Java VM: Java HotSpot(TM) 64-Bit Server VM (14.3-b01 mixed mode linux-amd64 )
# Problematic frame:
# C 0x0000000000043566
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug. 

SIGSEGV 错误的信号类型

pc 就是IP/PC寄存器值也就是执行指令的代码地址

pid 就是进程id

# Problematic frame:
# V  [libjvm.so+0x593045]

就是导致问题的动态链接库函数的地址

pc 和 +0x593045 指的是同一个地址,只是一个是动态的偏移地址,一个是运行的虚拟地址

b.信号信息

Java中在linux 中注册的信号处理函数,中间有2个参数info, ucvoid

static void crash_handler(int sig, siginfo_t* info, void* ucVoid) {
 // unmask current signal
 sigset_t newset;
 sigemptyset(&newset);
 sigaddset(&newset, sig);
 sigprocmask(SIG_UNBLOCK, &newset, NULL); 

 VMError err(NULL, sig, NULL, info, ucVoid);
 err.report_and_die();
} 

在crash report中的信号错误提示

siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x0000000000043566 

信号的详细信息和si_addr 出错误的内存,都保存在siginfo_t的结构体中,也就是信号注册函数crash_handler里的参数info,内核会保存导致错误的内存地址在用户空间的信号结构体中siginfo_t,这样在进程在注册的信号处理函数中可以取得导致错误的地址。

c.寄存器信息

Registers:
RAX=0x00002aacb5ae5de2, RBX=0x00002aaaaf46aa48, RCX=0x0000000000000219, RDX=0x00002aaaaf46b920
RSP=0x0000000042d3f968, RBP=0x0000000042d3f9c8, RSI=0x0000000042d3f9e8, RDI=0x0000000045aef9b8
R8 =0x0000000000000f80, R9 =0x00002aaab3d30ce8, R10=0x00002aaaab138ea1, R11=0x00002b017ae65110
R12=0x0000000042d3f6f0, R13=0x00002aaaaf46aa48, R14=0x0000000042d3f9e8, R15=0x0000000045aef800
RIP=0x0000000000043566, EFL=0x0000000000010202, CSGSFS=0x0000000000000033, ERR=0x0000000000000014
 TRAPNO=0x000000000000000e 

寄存器的信息就保存在b部分的信号处理函数参数 (ucontext_t*)usVoid中

在X86架构下:

void os::print_context(outputStream *st, void *context) {
 if (context == NULL) return; 

 ucontext_t *uc = (ucontext_t*)context;
 st->print_cr("Registers:");
#ifdef AMD64
 st->print( "RAX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RAX]);
 st->print(", RBX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RBX]);
 st->print(", RCX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RCX]);
 st->print(", RDX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RDX]);
 st->cr();
 st->print( "RSP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RSP]);
 st->print(", RBP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RBP]);
 st->print(", RSI=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RSI]);
 st->print(", RDI=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RDI]);
 st->cr();
 st->print( "R8 =" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R8]);
 st->print(", R9 =" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R9]);
 st->print(", R10=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R10]);
 st->print(", R11=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R11]);
 st->cr();
 st->print( "R12=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R12]);
 st->print(", R13=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R13]);
 st->print(", R14=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R14]);
 st->print(", R15=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_R15]);
 st->cr();
 st->print( "RIP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_RIP]);
 st->print(", EFL=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EFL]);
 st->print(", CSGSFS=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_CSGSFS]);
 st->print(", ERR=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_ERR]);
 st->cr();
 st->print(" TRAPNO=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_TRAPNO]);
#else
 st->print( "EAX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EAX]);
 st->print(", EBX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EBX]);
 st->print(", ECX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_ECX]);
 st->print(", EDX=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EDX]);
 st->cr();
 st->print( "ESP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_UESP]);
 st->print(", EBP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EBP]);
 st->print(", ESI=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_ESI]);
 st->print(", EDI=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EDI]);
 st->cr();
 st->print( "EIP=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EIP]);
 st->print(", CR2=" INTPTR_FORMAT, uc->uc_mcontext.cr2);
 st->print(", EFLAGS=" INTPTR_FORMAT, uc->uc_mcontext.gregs[REG_EFL]);
#endif // AMD64
 st->cr();
 st->cr(); 

 intptr_t *sp = (intptr_t *)os::Linux::ucontext_get_sp(uc);
 st->print_cr("Top of Stack: (sp=" PTR_FORMAT ")", sp);
 print_hex_dump(st, (address)sp, (address)(sp + 8*sizeof(intptr_t)), sizeof(intptr_t));
 st->cr(); 

 // Note: it may be unsafe to inspect memory near pc. For example, pc may
 // point to garbage if entry point in an nmethod is corrupted. Leave
 // this at the end, and hope for the best.
 address pc = os::Linux::ucontext_get_pc(uc);
 st->print_cr("Instructions: (pc=" PTR_FORMAT ")", pc);
 print_hex_dump(st, pc - 16, pc + 16, sizeof(char));
} 

寄存器的信息在分析出错的时候是非常重要的

打印出执行附近的部分机器码

Instructions: (pc=0x00007f48f14ef51a)
0x00007f48f14ef4fa: 90 90 55 48 89 e5 48 81 ec 98 9f 00 00 48 89 bd
0x00007f48f14ef50a: f8 5f ff ff 48 89 b5 f0 5f ff ff b8 00 00 00 00
0x00007f48f14ef51a: c7 00 01 00 00 00 c6 85 00 60 ff ff ff c9 c3 90
0x00007f48f14ef52a: 90 90 90 90 90 90 55 48 89 e5 53 48 8d 1d 94 00 

在instruction 部分中会打印出部分的机器码
格式是

地址:机器码 

第一种使用udis库里带的udcli工具来反汇编

命令:

echo '90 90 55 48 89 e5 48 81 ec 98 9f 00 00 48 89 bd' | udcli -intel -x -64 -o 0x00007f48f14ef4fa 

显示出对应的汇编

第二种可以用

objectdump -d -C libjvm.so >> jvmsodisass.dump  

查找偏移地址  0x593045, 就是当时的执行的汇编,然后结合上下文,源码推测出问题的语句。

d.寄存器对应的内存的值

RAX=0x0000000000000000 is an unknown value
RBX=0x000000041a07d1e8 is an oop
{method}
 - klass: {other class}
RCX=0x0000000000000000 is an unknown value
RDX=0x0000000040111800 is a thread
RSP=0x0000000041261b88 is pointing into the stack for thread: 0x0000000040111800
RBP=0x000000004126bb20 is pointing into the stack for thread: 0x0000000040111800
RSI=0x000000004126bb80 is pointing into the stack for thread: 0x0000000040111800
RDI=0x00000000401119d0 is an unknown value
R8 =0x0000000040111c40 is an unknown value
R9 =0x00007f48fcc8b550: <offset 0xa85550> in /usr/java/jdk1.6.0_30/jre/lib/amd64/server/libjvm.so at 0x00007f48fc206000
R10=0x00007f48f8ca7d41 is an Interpreter codelet
method entry point (kind = native) [0x00007f48f8ca7ae0, 0x00007f48f8ca8320] 2112 bytes
R11=0x00007f48fc98f270: <offset 0x789270> in /usr/java/jdk1.6.0_30/jre/lib/amd64/server/libjvm.so at 0x00007f48fc206000
R12=0x0000000000000000 is an unknown value
R13=0x000000041a07d1e8 is an oop
{method}
 - klass: {other class}
R14=0x000000004126bb88 is pointing into the stack for thread: 0x0000000040111800
R15=0x0000000040111800 is a thread 

jvm 会通过寄存器的值对找对应的对象,也是一个比较好的参考

e. 其他的信息

error 里面还有一些线程信息,还有当时内存映像信息,这些都可以作为分析的部分参考

crash 报告可以大概的反应出一个当时的情况,特别是在没有core dump的时候,是比较有助于帮助分析的,但如果有core dump的话,最终还是core dump能快速准确的发现问题原因。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • jvm crash的崩溃日志详细分析及注意点

    生成 1. 生成error 文件的路径:你可以通过参数设置-XX:ErrorFile=/path/hs_error%p.log, 默认是在Java运行的当前目录 [default: ./hs_err_pid%p.log] 2. 参数-XX:OnError  可以在crash退出的时候执行命令,格式是-XX:OnError="string",  <string> 可以是命令的集合,用分号做分隔符, 可以用"%p"来取到当前进程的ID. 例如: // -XX

  • JVM完全解读之GC日志记录分析

    相信大家在系统学习jvm的时候都会有遇到过这样的问题,散落的jvm知识点知道很多,但是真正在线上环境遇到一些莫名其妙的gc异常时候却无从下手去分析. 关于这块的苦我也表示能够理解,之前光是JVM相关的八股文就整理了许多,但是经常是不知道如何在实战中使用.最近也尝试在模拟一些案例来训练自己的JVM相关知识,本文特意记录下这段调优经历. Java应用的GC评估 可能大多数程序员在开发完某个需求之后,往线上环境一丢,然后就基本不怎么关注后续的变化了.但是是否有考虑过,这些新引入的代码会对原有系统造成的

  • MySQL事务日志(redo log和undo log)的详细分析

    目录 前言 1.redo log 1.1 redo log和二进制日志的区别 1.2 redo log的基本概念 1.3 日志块(log block) 1.4 log group和redo log file 1.5 redo log的格式 1.6 日志刷盘的规则 1.7 数据页刷盘的规则及checkpoint 1.8 LSN超详细分析 1.9 innodb的恢复行为 1.10 和redo log有关的几个变量 2.undo log 2.1 基本概念 2.2 undo log的存储方式 2.3 和

  • 详细分析JVM类加载机制

    目录 前言 1. jvm 的组成 2. 类加载 1. 加载 2. 链接 3. 初始化 3. 类加载器 引导类加载器(启动类加载器) 扩展类加载器 应用程序类加载器 4. 双亲委派机制 5. 类的主动/被动使用 结语 前言 ladies and gentleman , 你们好 ,我是羡羡 , 这节我们进入jvm的学习 , 我们知道 , jvm是java虚拟机, java代码的执行与 jvm 息息相关, 接下来我们来依次介绍 , 首先这节先来介绍 jvm 中的类加载部分 1. jvm 的组成 jvm

  • Android iOS常用APP崩溃日志获取命令方法

    目录 前言 Android 崩溃日志获取方法 iOS 崩溃日志获取方法 前言 ​ 在日常的测试工作过程中,app可能会出现闪退崩溃的情况,这个时候就需要测试同学快速抓取到崩溃日志,来有效的辅助开发定位问题,快速的去解决问题. 分享Android & iOS双端常用的崩溃日志获取方法 Android 崩溃日志获取方法 使用adb命令获取 # 语法: adb shell logcat # 可过滤app包名.日志级别来快速定位 adb shell logcat -v -v -v time>E:/c

  • 详细分析JAVA 线程池

    系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互.在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个 Runnable 对象或 Callable 对象传给线程池,线程池就会启动一个线程来执行它们的 run() 或 call() 方法,当 run() 或 call() 方法执行结束后,该线程并不会死亡,而是再次返回线程池成为空闲状态,等待执行下一个

  • 详细分析Redis集群故障

    故障表象: 业务层面显示提示查询redis失败 集群组成: 3主3从,每个节点的数据有8GB 机器分布: 在同一个机架中, xx.x.xxx.199 xx.x.xxx.200 xx.x.xxx.201 redis-server进程状态: 通过命令ps -eo pid,lstart | grep $pid, 发现进程已经持续运行了3个月 发生故障前集群的节点状态: xx.x.xxx.200:8371(bedab2c537fe94f8c0363ac4ae97d56832316e65) master

  • mysql中binlog_format模式与配置详细分析

    mysql复制主要有三种方式:基于SQL语句的复制(statement-based replication, SBR),基于行的复制(row-based replication, RBR),混合模式复制(mixed-based replication, MBR).对应的,binlog的格式也有三种:STATEMENT,ROW,MIXED. ① STATEMENT模式(SBR) 每一条会修改数据的sql语句会记录到binlog中.优点是并不需要记录每一条sql语句和每一行的数据变化,减少了binl

  • 详细分析java并发之volatile关键字

    Java面试中经常会涉及关于volatile的问题.本文梳理下volatile关键知识点. volatile字意为"易失性",在Java中用做修饰对象变量.它不是Java特有,在C,C++,C#等编程语言也存在,只是在其它编程语言中使用有所差异,但总体语义一致.比如使用volatile 能阻止编译器对变量的读写优化.简单说,如果一个变量被修饰为volatile,相当于告诉系统说我容易变化,编译器你不要随便优化(重排序,缓存)我. Happens-before 规范上,Java内存模型遵

  • 详细分析JAVA8新特性 Base64

    BASE64 编码是一种常用的字符编码,在很多地方都会用到.但base64不是安全领域下的加密解密算法.能起到安全作用的效果很差,而且很容易破解,他核心作用应该是传输数据的正确性,有些网关或系统只能使用ASCII字符.Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法,而且base64特别适合在http,mime协议下快速传输数据. JDK里面实现Base64的API 在JDK1.6之前,JDK核心类一直没有Base64的实现类,有人建议用Sun/Oracle JDK里面

随机推荐