JVM进程崩溃故障定位

一:故障现象

Java微服务进程崩溃,发现Linux系统日志messages中有killed by SIGABRT的日志

日志位置

#cat /var/log/messages | grep "killed"

Jul 12 09:33:54 prod4-app8 abrt-hook-ccpp: Process 6245 (java) of user 1002 killed by SIGABRT - dumping core

二:messages日志说明

Linux 系统中的 /var/log/messages 日志文件是一个核心的系统级日志文件,它记录了内核、系统服务、守护进程以及其他关键系统组件产生的广泛信息。这些信息对于系统管理员进行故障排除、监控系统运行状况和审计安全事件至关重要。

以下是 /var/log/messages 通常记录的主要内容:

  1. 内核消息:

    • 系统启动和关闭过程中的关键信息。

    • 硬件检测和初始化信息(如 CPU、内存、磁盘、USB 设备、网卡等)。

    • 硬件错误和警告(如磁盘 I/O 错误、内存问题、温度警报)。

    • 内核模块加载和卸载 (insmodrmmodmodprobe)。

    • 内核参数更改。

    • 与虚拟化相关的事件(如果使用了 KVM、Xen 等)。

  2. 系统服务/守护进程的启动、停止和状态消息:

    • 当系统进入不同的运行级别时,启动或停止的服务。

    • sshdcrondsyslogd/rsyslogd/journaldhttpd/nginxmysqld/postgresqldnsmasq/bind 等常见服务的启动、停止、重启和运行状态信息。

    • 服务初始化过程中的错误或配置问题。

  3. 认证和安全相关事件:

    • 用户登录(通过控制台、SSH 等)和注销记录(通常更详细的信息在 /var/log/secure 或 /var/log/auth.log,但关键认证事件有时也会出现在 messages)。

    • sudo 命令的执行(同样,主要记录在 secure/auth.log,但关键失败或配置问题可能出现在 messages)。

    • PAM 认证模块的消息。

    • 与 cron 作业执行相关的身份验证(有时)。

  4. 磁盘和文件系统事件:

    • 文件系统挂载和卸载。

    • 文件系统检查 (fsck) 的结果。

    • 磁盘空间警告(通常由 cron 任务如 logwatch 或 sysstat 生成)。

    • 与 LVM 或 RAID 配置相关的重要事件。

  5. 网络配置事件:

    • 网络接口启动、关闭、状态变化。

    • DHCP 客户端获取或更新 IP 地址的信息。

    • 重要的防火墙规则变更(如 iptables/nftables 服务重启)。

  6. 系统错误和警告:

    • 应用程序或服务崩溃的核心转储通知(如果配置了)。

    • 资源耗尽警告(如内存不足 OOM killer 被触发)。

    • 其他未能归类到更专业日志文件(如 cronsecuremaillog)的系统级错误和警告。

  7. 定时任务 (cron) 执行摘要:

    • cron 守护进程本身的活动(启动、关闭)。

    • 用户 cron 作业执行时产生的输出(如果作业没有将输出重定向到其他地方,默认会发送邮件给用户,系统 cron 作业的输出通常也会记录在这里或 /var/log/cron)。

重要说明:

  • 发行版差异: 随着 Linux 日志系统的发展(从 syslog 到 rsyslog 再到 systemd-journald),不同发行版对 /var/log/messages 的使用有所变化。

    • 传统 Syslog 系统 (RHEL/CentOS 7 及更早, 一些较旧的发行版): /var/log/messages 是主要的、综合的系统日志文件,包含上述大部分内容。

    • Rsyslog 系统 (现代 RHEL/CentOS, Fedora, openSUSE 等): /var/log/messages 通常仍然是核心日志文件,但配置更灵活,内容可能根据 /etc/rsyslog.conf 的设置而有所不同。其他更专业的日志文件(如 securecronmaillog)通常会将更具体的消息分流出去。

    • Systemd Journald 系统 (现代 Debian/Ubuntu, Fedora, RHEL/CentOS 8+): 默认情况下,/var/log/messages 可能不存在或不作为主要日志journald 将日志存储在二进制格式的 journal 中(通常位于 /var/log/journal/)。不过,这些系统通常配置 rsyslog 或 syslog-ng 作为补充,并将 journal 的消息转发到传统的文本日志文件。在 Ubuntu 和 Debian 上,最常见的综合系统日志文件通常是 /var/log/syslog/var/log/messages 在 Ubuntu 上通常不存在或为空,除非特别配置。

  • 日志轮转: 日志文件会定期轮转(例如通过 logrotate 工具),旧的日志会被压缩或删除。你可能会看到 messagesmessages.1messages.2.gz 等文件。

  • 查看日志: 查看 messages 日志通常使用命令行工具:

    • tail -f /var/log/messages (实时查看最新追加的内容)

    • less /var/log/messages (分页查看整个文件)

    • grep "error" /var/log/messages (搜索包含特定关键词的行)

    • journalctl (在主要使用 journald 的系统上查看日志)

总结: /var/log/messages (或其等效文件如 Ubuntu 的 /var/log/syslog) 是 Linux 系统的“总控台”,它记录了内核、核心系统服务以及各种关键系统事件的广泛信息,是系统管理员进行监控、调试和维护的首要信息来源。务必注意不同发行版在默认日志文件路径和机制上的差异。

三:查看Java的报错日志hs_err_pid

messages记录的信息太少了,很难知道具体的崩溃原因,需要查看Java的hs_err_pid日志

先看看hs_err_pid的说明:

Java 程序崩溃(特别是 JVM 自身崩溃)时,通常会生成名为 hs_err_pid<pid>.log 的日志文件(例如 hs_err_pid12345.log)。这个文件是 HotSpot 虚拟机(大多数 Java 发行版使用的 JVM)在遇到无法恢复的致命错误(Fatal Error)时自动生成的错误报告文件,是诊断 JVM 崩溃问题的首要资源。

主要记录了以下关键信息:

  1. 崩溃原因概要:

    • 文件头部会明确指出导致 JVM 崩溃的根本原因(通常以 # A fatal error has been detected by the Java Runtime Environment: 开头)。

    • 常见原因包括:

      • EXCEPTION_ACCESS_VIOLATION: 试图访问无效内存地址(最常见,通常是本地代码问题或 JVM bug)。

      • EXCEPTION_STACK_OVERFLOW: 栈溢出(可能是无限递归或本地代码栈问题)。

      • SIGSEGV: 段错误信号(非法内存访问)。

      • SIGBUS: 总线错误(内存对齐问题等)。

      • SIGFPE: 算术错误(如除以零)。

      • Internal Error: JVM 内部的严重错误。

      • OutOfMemoryError: 当无法分配更多本地内存(Native Memory)时(注意:Java Heap 的 OutOfMemoryError 是 Java 异常,通常不会导致 JVM 崩溃并生成 hs_err 文件;但本地内存耗尽会导致 JVM 崩溃)。

  2. 问题发生的时刻和进程信息:

    • 时间戳。

    • JVM 进程 ID (pid).

    • 可执行文件路径 (jre/bin/java)。

  3. 系统信息:

    • 操作系统名称、版本、内核版本。

    • 系统架构(x86, x86_64, arm 等)。

    • 主机名。

    • CPU 信息(型号、核心数、特征)。

  4. JVM 版本和配置信息:

    • Java 运行时版本(java -version 的输出)。

    • Java 虚拟机信息(VM 版本、模式 Client/Server)。

    • 完整的 JVM 启动命令行参数 (CommandLine flags)。这是极其重要的信息,包含了所有 -X-XX 参数。

    • 类路径 (CLASSPATH)。

    • 库路径 (LD_LIBRARY_PATH 或 PATH)。

  5. 崩溃时的线程信息:

    • 崩溃线程: 导致崩溃的线程的详细信息(通常是最核心的部分):

      • 线程 ID。

      • 线程状态(正在运行、等待等)。

      • 该线程的完整本地栈和 Java 栈跟踪 (Stack: [0x...] 和 Java frames:)。这能精确显示崩溃发生时该线程正在执行的代码位置(是本地库调用还是 Java 方法)。

    • 所有其他线程: 列出 JVM 中所有其他线程的状态和堆栈信息("Thread" 开头的部分)。这有助于了解崩溃时整个应用程序的状态。

  6. 内存信息:

    • 寄存器状态: 崩溃点 CPU 寄存器的值(非常底层,对分析 JVM bug 或本地代码 bug 有用)。

    • 本地内存映射: 进程的虚拟内存区域列表 (Memory: 部分),显示加载了哪些库(DLL/SO)以及它们的基地址。这对于分析内存访问冲突(如访问了未映射的内存或错误的库偏移)至关重要。

    • 堆栈内存内容: 崩溃点附近栈内存的十六进制和 ASCII 转储。

    • 核心转储信息: 如果生成了核心转储文件(core dump),会记录其位置。

    • 堆内存摘要: (有时)包含 Heap 的简要信息(如分代大小、使用情况),但通常不是主要诊断点(因为 Heap OOM 一般不会导致 JVM 崩溃生成 hs_err)。

  7. 加载的模块信息:

    • 列出所有加载的共享库(.dll.so)及其路径和基地址。这对于识别问题库非常关键。

  8. 环境变量: 进程的环境变量列表。

关键点总结:

  1. 触发条件: 当 JVM 自身遇到无法处理的致命错误(通常是底层原生代码问题、严重的资源耗尽如本地内存、JVM 内部错误、硬件问题等)而崩溃退出时生成。普通的 Java 异常(如 NullPointerException)或 Java Heap 的 OutOfMemoryError 不会触发生成此文件(它们只会打印到标准错误或应用日志)。

  2. 核心价值: 提供崩溃瞬间 JVM 和系统的完整快照,特别是崩溃线程的堆栈、寄存器、内存映射和详细的 JVM 配置。是诊断 JVM 崩溃、本地库(JNI)问题、操作系统兼容性问题或 JVM bug 的首要依据

  3. 位置: 默认生成在 JVM 进程的当前工作目录下。有时也可能在操作系统的临时目录(如 /tmp)或用户主目录。可以通过 JVM 启动参数 -XX:ErrorFile=/path/to/file.log 指定生成位置。

  4. 分析: 分析该文件通常:

    • 从文件头部快速定位崩溃原因(EXCEPTION_ACCESS_VIOLATION 等)。

    • 重点查看崩溃线程的堆栈信息(Stack: 和 Java frames:),确定崩溃发生在 JVM 内部、JIT 编译的代码、还是你的 Java 代码调用的 JNI 本地方法。

    • 检查 CommandLine flags 确认 JVM 参数(特别是内存设置)。

    • 查看 Modules 或 Dynamic libraries 部分确认是否有可疑的本地库。

    • 结合 Memory 映射分析无效内存访问地址是否指向某个库。

    • 检查是否有 OutOfMemoryError 提示本地内存耗尽。

当你在 Java 应用运行目录或日志目录发现 hs_err_pid.log 文件时,就明确表明 JVM 进程发生了严重的崩溃。这个文件是解决此类问题的关键起点。

我们在微服务的日志目录查看到hs_err_pid.log

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f656f438aec, pid=6245, tid=0x00007f6514546700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_201-b09) (build 1.8.0_201-b09)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libc.so.6+0x85aec]  cfree+0x1c
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x00007f656808f000):  ConcurrentGCThread [stack: 0x00007f6514447000,0x00007f6514547000] [id=6301]

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x00007f629002cff8

Registers:
RAX=0x0000000000000000, RBX=0x00007f6445220930, RCX=0x0000000000000001, RDX=0x00007f629002d000
RSP=0x00007f6514545bf8, RBP=0x00000000ffffffff, RSI=0x00007f629002d000, RDI=0x00007f629002d000
R8 =0xffffffffffffffff, R9 =0x0000000000000000, R10=0x0000000000000022, R11=0x0000000000000246
R12=0x0000000000000000, R13=0x00007f6514545cf0, R14=0x0000000000000d70, R15=0x00007f656f776838
RIP=0x00007f656f438aec, EFLAGS=0x0000000000010206, CSGSFS=0x0000000000000033, ERR=0x0000000000000004
  TRAPNO=0x000000000000000e

Top of Stack: (sp=0x00007f6514545bf8)
0x00007f6514545bf8:   00007f656f42f8ea 00007f6445220930
0x00007f6514545c08:   00007f656f42f055 00007f6445220930
0x00007f6514545c18:   000000000000001c 000000000000001c
0x00007f6514545c28:   00007f656f42d9c0 0000000000000009
0x00007f6514545c38:   0000000000000021 0000000000000035
0x00007f6514545c48:   00007f6445220930 000000000000001c
0x00007f6514545c58:   0000000000000001 000000000000001c

关于这个日志的解析:

1.以下这句说明了这个崩溃是在执行 cfree 时发生释放了一段未分配或者未映射的内存地址引起。

# Problematic frame:
# C  [libc.so.6+0x85aec]  cfree+0x1c

2.引起的原因是JVM的ConcurrentGCThread线程

Current thread (0x00007f656808f000):  ConcurrentGCThread [stack: 0x00007f6514447000,0x00007f6514547000] [id=6301]

关于这个bug的修复可以参考官方文档:Bug ID: JDK-8191393 Random crashes during cfree+0x1c

大致原因是ConcurrentMarkThread与VMThread之间的竞态条件问题导致的。

修复的方案:升级JDK版本到8u261以上。

本人查看了线上的JVM版本是8u201,明显也是受影响的版本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值