Java 应用产生 core dump
什么是core dump
在 Linux 系统中,当一个进程崩溃(例如,由于段错误或其他严重错误),它通常会产生一个称为 "核心转储"(core dump)的文件。核心转储文件包含了进程崩溃时的内存映像和一些执行上下文信息,如寄存器状态、程序计数器位置、内存管理信息等。这些信息对于开发者来说非常有价值,因为它们可以用来调试程序,了解程序崩溃的原因。
Java 进程如何产生 core dump
对于正在运行的 Java 进程, 它就是一个标准的linux 进程, 可以使用 linux 上的各种工具来产生 core dump, 比如 gcore 或者 kill :
$ gcore <pid>
$ kill -11 <pid>
对于 Java 应用程序自身, 当它奔溃的时候, 默认会产生 core dump, 因为它有如下默认参数-XX:+CreateCoredumpOnCrash
:
$ java -XX:+PrintFlagsFinal -version | grep Core
bool CreateCoredumpOnCrash = true {product} {default}
虽然上面的方式都能产生 core dump, 但是很有可能你并不能看到 core dump, 因为有各种其它条件会阻碍 core dump的产生:
- 系统设置了 core size 的大小太小, 查看
ulimit -c
- core dump 要写入的文件夹没有权限
- core dump 被系统设置拦截, 比如 apport, 它产生了 crash report, 却拦截了 core dump的产生.
core dump 有什么用?
core dump 里有进程当时时间点上的全部内存信息, 寄存器信息, 栈信息, 栈上变量值, 打开文件句柄, 打开的socket 等各种非常有用的信息, 对于诊断应用为什么崩溃具有很大的意义.
gdb 可以直接打开core dump 文件, 并读取里面的信息.
$ gdb core core.720444`
使用 gdb 去debug Java 程序比较麻烦.
但是可以使用 JDK 自带的 jdb
:
# java 8 版本
$ jdb -listconnectors
$ jdb -connect sun.jvm.hotspot.jdi.SACoreAttachingConnector:javaExecutable=$JAVA_HOME/bin/java,core=core.720444
java 11 版本
$ jhsdb clhsdb --exe ErrorExample --core core.720444
Core Dump vs Java Heap Dump
Core Dump
- 定义:
Core dump 是操作系统在进程异常终止时生成的一个文件,它包含了进程终止时内存中的内容。 - 内容:
Core dump 包含了进程的整个内存映像,包括程序计数器、寄存器、堆栈、堆内存、全局变量、打开的文件描述符、环境变量、程序代码、加载的共享库等。 - 用途:
主要用于程序崩溃后的调试和故障排查。可以使用调试工具(如gdb
)来分析 core dump 文件,确定程序崩溃的原因。 - 大小:
Core dump 文件通常很大,因为它包含了整个进程的内存映像。 - 生成方式:
Core dump 通常由操作系统在进程崩溃时自动生成,或者可以使用gcore
命令手动生成。
Java Heap Dump
- 定义:
Java heap dump 是 JVM 在某一时刻的堆内存的快照,包含了所有的 Java 对象和类信息。 - 内容:
Heap dump 专注于 Java 堆内存,包括对象实例、数组、类实例、垃圾收集器信息等。 - 用途:
主要用于分析 Java 应用程序的内存使用情况,如检测内存泄漏、查看对象的分配和引用情况等。 - 大小:
Heap dump 文件的大小取决于 Java 堆的大小,通常比完整的 core dump 小。 - 生成方式:
Heap dump 可以通过 JVM 提供的工具(如jmap
)、管理接口(如 JMX)生成,或在发生OutOfMemoryError
时自动生成(如果配置了-XX:+HeapDumpOnOutOfMemoryError
JVM 参数)。
heap dump vs crash log file
这就类似
core dump -> apport crash report
heap dump -> crash log file (err_log_pid.log)
-XX:OnError 选项
如果启动参数配置了-XX:OnError
选项, 当 fatal error
产生的时候, JVM 就会执行该选项配置的命令. 多个执行命令可以用 ;
分割开. 可以用 %p
指代当前进程号, 因为 %
用作了特殊符, 所以遇到真的 %
, 就要用两个 %%
代替. 官方给的例子:
java -XX:OnError="pmap %p" MyApp #使用 pmap 查看内存空间
java -XX:OnError="gcore %p; dbx - %p" MyApp # 使用 gcore 产生core dump 并用 dbx 进行debug
java -XX:OnError="gdb - %p" MyApp # 使用 gdb debug
-XX:ErrorFile 选项
设置当 fatal error
产生的时候, 把log写到哪个文件去, 需要全路径名(%p指代当前进程号, %%=%).
如果该文件存在, 并且可以写, 那么就覆盖.
如果不设置, 它的默认行为是: 产生一个 java_error_%p.log
文件, %p
是进程号, 默认放在当前进程的当前工作目录(CWD), 如果当前工作目录不可用(比如写权限,空间不够等), 会写到临时文件夹目录(/tmp).
参考: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/felog001.html