堆轉儲、線程轉儲和核心轉儲之間的區別
1. 概述
轉儲是從存儲介質查詢並存儲在某處以供進一步分析的數據。 Java虛擬機(JVM)幫助管理Java中的內存,當出現錯誤時,我們可以從JVM獲取轉儲文件來診斷錯誤。
在本教程中,我們將探討三種常見的 Java 轉儲文件——堆轉儲、線程轉儲和核心轉儲——並了解它們的用例。
2. 堆轉儲
在運行時期間,JVM 創建堆,其中包含對正在運行的 Java 應用程序中使用的對象的引用。堆轉儲包含運行時使用的所有對象的當前狀態的保存副本。
此外,它還用於分析 Java 中的OutOfMemoryError
錯誤。
此外,堆轉儲可以採用兩種格式——經典格式和可移植堆格式(PHD) 。
經典格式是人類可讀的,而 PHD 是二進制的,需要工具進行進一步分析。此外,PHD 是堆轉儲的默認值。
2.1.使用案例
堆轉儲可以幫助分析 Java 應用程序中的OutOfMemoryError
。
讓我們看一些拋出OutOfMemoryError
示例代碼:
public class HeapDump {
public static void main(String[] args) {
List numbers = new ArrayList<>();
try {
while (true) {
numbers.add(10);
}
} catch (OutOfMemoryError e) {
System.out.println("Out of memory error occurred!");
}
}
}
在上面的示例代碼中,我們創建了一個無限循環的場景,直到堆內存已滿。眾所周知,java中new
關鍵字有助於在堆上分配內存。
為了捕獲上面代碼的堆轉儲,我們需要一個工具。最常用的工具之一是jmap
。
首先,我們需要通過運行jps
命令來獲取機器上所有正在運行的 Java 進程的進程 ID:
$ jps
上面的命令將所有正在運行的 Java 進程輸出到控制台:
12789 Launcher
13302 Jps
7517 HeapDump
在這裡,我們感興趣的進程是HeapDump.
因此,讓我們使用HeapDump
進程 ID 運行jmap
命令來捕獲堆轉儲:
$ jmap -dump:live,file=hdump.hprof 7517
上面的命令在項目根目錄中生成hdump.hprof
文件。
最後,我們可以使用Eclipse Memory Analyzer等工具來分析轉儲文件。
3. 線程轉儲
線程轉儲包含正在運行的 Java 程序在特定時刻的所有線程的快照。
線程是進程的最小部分,它通過同時運行多個任務來幫助程序高效運行。
此外,線程轉儲可以幫助診斷 Java 應用程序中的效率問題。因此,它是分析性能問題的重要工具,尤其是當應用程序速度緩慢時。
此外,它還可以幫助檢測陷入無限循環的線程。它還可以幫助識別死鎖,即多個線程正在等待彼此釋放資源。
此外,它還可以識別某些線程沒有獲得足夠 CPU 時間的情況。這可以幫助識別性能瓶頸。
3.1.使用案例
以下是一個示例程序,由於長時間運行的任務,該程序可能會降低性能:
public class ThreadDump {
public static void main(String[] args) {
longRunningTask();
}
private static void longRunningTask() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted!");
break;
}
System.out.println(i);
}
}
}
在上面的示例代碼中,我們創建了一個循環到Integer.MAX_VALUE
並將值輸出到控制台的方法。這是一個長時間運行的操作,並且可能會出現性能問題。
為了分析性能,我們可以捕獲線程轉儲。首先,讓我們找到所有正在運行的Java程序的進程ID:
$ jps
jps
命令將所有 Java 進程輸出到控制台:
3042 ThreadDump
964 Main
3032 Launcher
3119 Jps
我們對ThreadDump
進程 ID 感興趣。接下來,讓我們使用jstack
命令和進程 ID 來獲取線程轉儲:
$ jstack -l 3042 > slow-running-task-thread-dump.txt
上面的命令捕獲線程轉儲並將其保存在txt
文件中以供進一步分析。
4. 核心轉儲
核心轉儲也稱為故障轉儲,包含程序崩潰或突然終止時的程序快照。
JVM 運行字節碼而不是本機代碼。因此,Java 代碼不會導致核心轉儲。
然而,一些Java程序使用Java本機接口(JNI)直接運行本機代碼。 JNI 可能會導致 JVM 崩潰,因為外部庫可能會崩潰。我們可以立即獲取核心轉儲並對其進行分析。
此外,核心轉儲是操作系統級別的轉儲,可用於查找 JVM 崩潰時本機調用的詳細信息。
4.1.使用案例
讓我們看一個使用 JNI 生成核心轉儲的示例。
首先,讓我們創建一個名為CoreDump
的類來加載本機庫:
public class CoreDump {
private native void core();
public static void main(String[] args) {
new CoreDump().core();
}
static {
System.loadLibrary("nativelib");
}
}
接下來,讓我們使用javac
命令編譯 Java 代碼:
$ CoreDump.java
然後,讓我們通過運行javac -h
命令來生成用於本機方法實現的標頭:
$ javac -h . CoreDump.java
最後,讓我們用 C 實現一個會使 JVM 崩潰的本機方法:
#include <jni.h>
#include "CoreDump.h"
void core() {
int *p = NULL;
*p = 0;
}
JNIEXPORT void JNICALL Java_CoreDump_core (JNIEnv *env, jobject obj) {
core();
};
void main() {
}
讓我們通過運行gcc
命令來編譯本機代碼:
$ gcc -fPIC -I"/usr/lib/jvm/java-17-graalvm/include" -I"/usr/lib/jvm/java-17-graalvm/include/linux" -shared -o libnativelib.so CoreDump.c
這會生成名為libnativelib.so
的共享庫。接下來,讓我們使用共享庫編譯 Java 代碼:
$ java -Djava.library.path=. CoreDump
本機方法使 JVM 崩潰並在項目目錄中生成核心轉儲:
// ...
# A fatal error has been detected by the Java Runtime Environment:
# SIGSEGV (0xb) at pc=0x00007f9c48878119, pid=65743, tid=65744
# C [libnativelib.so+0x1119] core+0x10
# Core dump will be written. Default location: Core dumps may be processed with
# "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h" (or dumping to /core-java-perf/core.65743)
# An error report file with more information is saved as:
# ~/core-java-perf/hs_err_pid65743.log
// ...
上面的輸出顯示了崩潰信息和轉儲文件的位置。
5. 主要差異
下面的匯總表顯示了三種類型的 Java 轉儲文件之間的主要區別:
轉儲類型 | 使用案例 | 包含 |
堆轉儲 | 診斷內存問題,例如OutOfMemoryError | Java 堆中對象的快照 |
線程轉儲 | 解決性能問題、線程死鎖和無限循環 | JVM中所有線程狀態的快照 |
核心轉儲 | 由本機庫引起的調試崩潰 | JVM崩潰時的進程狀態 |
六,結論
在本文中,我們通過查看堆轉儲、線程轉儲和核心轉儲的用途來了解它們之間的區別。此外,我們還看到了存在不同問題的示例代碼,並生成了轉儲文件以供進一步分析。每個轉儲文件都有不同的用途,用於對 Java 應用程序進行故障排除。
與往常一樣,示例的源代碼可在 GitHub 上獲取。