0. Important Notice#
This article provides an in-depth analysis of JVM error handling and diagnostic parameter design principles, implementation mechanisms, and version evolution. The article covers:
- Heap Dump Related Parameters: HeapDumpOnOutOfMemoryError, HeapDumpBeforeFullGC, HeapDumpAfterFullGC, etc.
- OutOfMemoryError Handling Parameters: OnOutOfMemoryError, CrashOnOutOfMemoryError, ExitOnOutOfMemoryError
- Error Logging and Diagnostic Parameters: ErrorFile, ShowMessageBoxOnError, CreateCoredumpOnCrash, etc.
- Other Diagnostic Parameters: MaxJavaStackTraceDepth, SelfDestructTimer
This article involves JVM internal implementation details, including C++ source code analysis, safepoint mechanisms, VM_Operation mechanisms, etc. The design philosophy and implementation principle analysis in the article are mainly based on my personal understanding of the source code. If there are any inaccuracies, please feel free to correct them! This article is primarily based on analysis of HotSpot source code from JDK 11, 17, 21, and 25.
Among these parameters, the most commonly seen is probably -XX:+HeapDumpOnOutOfMemoryError, but regardless of version, it is not recommended to enable any Heap Dump parameters in production environments. Subsequent sections will explain why and show you how to diagnose Java heap OOM issues without relying on this parameter.
The most common error we need to diagnose is java.lang.OutOfMemoryError, and this issue becomes even more prominent after introducing virtual threads. Before virtual threads, I/O blocking would block threads and task queues, limiting throughput. After introducing virtual threads, I/O-blocked threads are suspended, allowing the scheduler to continue running other virtual threads to process tasks, significantly improving throughput. However, most frameworks haven’t yet developed mature solutions for backpressure and rate limiting in this context, so in high-concurrency scenarios, virtual threads may cause faster memory consumption, making it easier to trigger OutOfMemoryError. Furthermore, when OutOfMemoryError occurs, the JVM state is already unhealthy, and this error can be thrown at any code location that triggers object allocation. Regardless of where in the Java code—even internal JDK Java code—there’s no catching of OutOfMemoryError through catch (Throwable t), which causes internal state inconsistency issues. For example, JDK’s internal HashMap, when triggering OutOfMemoryError during put, may cause the HashMap’s internal array expansion, and if OutOfMemoryError occurs during expansion, the HashMap may be left in an inconsistent state, making it unusable afterward. Business code may have even more serious problems. After OutOfMemoryError occurs, our safest approach is to exit the process as quickly as possible to avoid continued operation causing more problems. And JDK’s internal parameter mechanisms allow us to achieve exactly this.
1. Overview#
1.1. Error Handling and Diagnostic Parameter Categories#
JVM error handling and diagnostic parameters can be categorized as follows:
1.1.1. Heap Dump Related Parameters#
Used to dump Java heap memory snapshots at specific times for subsequent analysis:
-XX:+HeapDumpOnOutOfMemoryError: Dump Java heap on Java heap OOM- Introduced in JDK 1.6 (not considering backports): https://bugs.openjdk.org/browse/JDK-6280629
- Default:
false(JDK 11, 17, 21, 25)
-XX:+HeapDumpBeforeFullGC: Dump Java heap before Full GC- Introduced in JDK 1.7 (not considering backports): https://bugs.openjdk.org/browse/JDK-6797870
- Default:
false(JDK 11, 17, 21, 25)
-XX:+HeapDumpAfterFullGC: Dump Java heap after Full GC- Introduced in JDK 1.7 (not considering backports): https://bugs.openjdk.org/browse/JDK-6797870
- Default:
false(JDK 11, 17, 21, 25)
-XX:HeapDumpPath=<path>: Java heap dump file path- Introduced in JDK 1.6 (not considering backports): https://bugs.openjdk.org/browse/JDK-6280629
- Default:
NULL(JDK 11, 17),nullptr(JDK 21, 25)
-XX:HeapDumpGzipLevel=<level>: Java heap dump file compression level- Introduced in JDK 17 (not considering backports): https://bugs.openjdk.org/browse/JDK-8260282
- Default: Not supported (JDK 11),
0(JDK 17, 21, 25)
-XX:FullGCHeapDumpLimit=<count>: Full GC heap dump count limit- Introduced in JDK 23 (not considering backports): https://bugs.openjdk.org/browse/JDK-8321442
- Default: Not supported (JDK 11, 17, 21),
0(JDK 25)
1.1.2. OutOfMemoryError Handling Parameters#
Used to perform specific operations when Java heap OOM occurs:
-XX:OnOutOfMemoryError=<command>: Execute user-defined command or script- Introduced in JDK 1.6 (not considering backports)
- Default:
""(empty string, JDK 11, 17, 21, 25)
-XX:+CrashOnOutOfMemoryError: Trigger JVM crash on Java heap OOM- Introduced in JDK 9 (not considering backports): https://bugs.openjdk.org/browse/JDK-8138745
- Default:
false(JDK 11, 17, 21, 25)
-XX:+ExitOnOutOfMemoryError: Exit JVM on Java heap OOM- Introduced in JDK 9 (not considering backports): https://bugs.openjdk.org/browse/JDK-8138745
- Default:
false(JDK 11, 17, 21, 25)
1.1.3. Error Logging and Diagnostic Parameters#
Used to control error log output and diagnostic behavior:
-XX:ErrorFile=<file>: Error log file path- Introduced in JDK 1.6 (not considering backports): https://bugs.openjdk.org/browse/JDK-6872355
- Default:
NULL(JDK 11, 17, 21, 25)
-XX:+ShowMessageBoxOnError: Show message box on fatal error (rarely used now)- Introduced in JDK 1.4 (not considering backports)
- Default:
false(JDK 11, 17, 21, 25)
-XX:+CreateCoredumpOnCrash/-XX:-CreateCoredumpOnCrash: Create core dump on fatal error- Introduced in early versions
- Default:
true(JDK 11, 17, 21, 25)
-XX:+SuppressFatalErrorMessage: Suppress fatal error messages- Introduced in early versions
- Default:
false(JDK 11, 17, 21, 25)
-XX:OnError=<command>: Execute user-defined command on fatal error- Introduced in early versions
- Default:
""(empty string, JDK 11, 17, 21, 25)
-XX:ErrorLogTimeout=<seconds>: Error log write timeout- Introduced in early versions
- Default:
120(2 minutes, JDK 11, 17, 21, 25)
1.1.4. Other Diagnostic Parameters#
-XX:MaxJavaStackTraceDepth=<depth>: Maximum Java exception stack trace depth- Introduced in early versions
- Default:
1024(JDK 11, 17, 21, 25)
-XX:SelfDestructTimer=<minutes>: Self-destruct timer (for testing)- Introduced in early versions
- Default:
0(disabled, JDK 11, 17, 21, 25)
1.2. Version Evolution Overview#
| Version | Main Features | Key Improvements |
|---|---|---|
| JDK 11 | Basic functionality | Basic Heap Dump and Java heap OOM handling support |
| JDK 17 | Compression support | Added gzip compression, improved error handling |
| JDK 21 | Fast exit | Uses _exit for fast exit, C++11 modernization |
| JDK 25 | Parallel dumping | Supports parallel dumping, %p placeholder, stack allocation, FullGC dump count limit |
2. Heap Dump Related Parameters#
2.1. Why Not Enable Heap Dump Parameters?#
Important Reminder: Regardless of version, it is not recommended to enable any Heap Dump parameters in production environments. The reasons are explained below, and subsequent sections will show you how to diagnose Java heap OOM issues without relying on these parameters.
2.1.1. Disk I/O Performance Limitations#
Heap Dump needs to write to disk, and disk I/O speed is relatively limited:
- HDD: Sequential write 100–200 MB/s
- SSD: Sequential write 500 MB/s to several GB/s
Heap Dump is sequential write, but the Java heap can be very large, such as 8GB, 16GB, or even larger. If the Java heap is very large, write time will be very long. Moreover, machines running Java applications won’t all be equipped with high-speed SSDs, and in cloud/container environments, disk I/O may be rate-limited and shared.
2.1.2. Compression Overhead#
Although JDK 17 introduced gzip compression for Java heap and JDK 25 introduced multi-threaded Heap Dump with multi-threaded compression, compression is CPU-intensive. Assuming 4 CPUs, an empirical metric is:
- Level 0/1 (fastest)
- Compression ratio: 2.3×–2.7×
- Throughput: 1.0–3.0 GB/s
- Time per GB: 0.33–1.0 s
- Level 3
- Compression ratio: 2.5×–2.9×
- Throughput: 0.9–2.2 GB/s
- Time per GB: 0.45–1.1 s
- Level 5 (commonly used for cost-effectiveness)
- Compression ratio: 2.7×–3.1×
- Throughput: 0.7–1.8 GB/s
- Time per GB: 0.55–1.4 s
- Level 6 (default/safe)
- Compression ratio: 2.8×–3.2×
- Throughput: 0.6–1.6 GB/s
- Time per GB: 0.62–1.7 s
- Level 7
- Compression ratio: 2.9×–3.3×
- Throughput: 0.45–1.2 GB/s
- Time per GB: 0.83–2.2 s
- Level 8
- Compression ratio: 2.9×–3.4×
- Throughput: 0.35–0.9 GB/s
- Time per GB: 1.1–2.9 s
- Level 9 (maximum compression)
- Compression ratio: 3.0×–3.5×
- Throughput: 0.25–0.7 GB/s
- Time per GB: 1.4–4.0 s
Assuming level 5, with 4 CPUs, for an 8GB Java heap, compression time is approximately 4.4–11.2 seconds, compressed file is approximately 2.6–3.2 GB, writing to HDD takes approximately 13–32 seconds, totaling approximately 17.4–43.2 seconds. This is an ideal estimate; actual performance may be worse.
2.1.3. Execution Order Impact#
Typically, production systems use -XX:OnOutOfMemoryError=xxxxx to specify a script that performs actions like deregistering the Java process microservice from the registry, because processes experiencing OutOfMemoryError are generally unhealthy and may have issues running business logic. However, if you enable HeapDumpOnOutOfMemoryError, the OnOutOfMemoryError script can only execute after HeapDumpOnOutOfMemoryError completes.
This means: The service cannot deregister from the registry promptly, and unhealthy services may continue receiving more requests. The earlier the script executes and deregisters, the better; delayed deregistration may affect more requests.
2.2. HeapDumpOnOutOfMemoryError#
2.2.1. Parameter Description#
Description: Automatically dump Java heap memory to file when java.lang.OutOfMemoryError occurs.
Default: false
Type: manageable (JDK 11) or product + MANAGEABLE (JDK 17+), can be modified dynamically via JMX
Example: -XX:+HeapDumpOnOutOfMemoryError
2.2.2. Trigger Scenarios#
Important Note: HeapDumpOnOutOfMemoryError only triggers Heap Dump when OOM types that call report_java_out_of_memory occur. The following OOM types will trigger Heap Dump:
java.lang.OutOfMemoryError: Java heap space: Java heap space exhausted (Java heap OOM)java.lang.OutOfMemoryError: GC overhead limit exceeded: GC overhead limit exceeded (Java heap-related OOM)java.lang.OutOfMemoryError: Metaspace/Compressed class space: Metaspace exhausted (triggers Java heap dump because loaded classes have Class objects on Java heap)java.lang.OutOfMemoryError: Requested array size exceeds VM limit: Array size exceeds limit (triggers Java heap dump as it typically indicates oversized collections)
OOM types that will NOT trigger Heap Dump:
- Thread creation failure (
unable to create native thread) - Unicode string allocation failure
- Class verifier out of memory
- Unsafe allocation failure
- Deoptimization realloc_objects failure
- Retryable allocation failure (
retry, JDK 17+) - Internal OOM marking scenarios (JDK 25+)
See section 2.2.3.1 for details.
2.2.3. Implementation Mechanism#
2.2.3.1. OutOfMemoryError Types and Heap Dump Trigger Relationship#
JVM defines multiple OutOfMemoryError types, but only types that call report_java_out_of_memory will trigger Heap Dump:
OutOfMemoryErrors that WILL trigger Heap Dump (✅):
java.lang.OutOfMemoryError: Java heap space- Java heap space exhaustedjava.lang.OutOfMemoryError: GC overhead limit exceeded- GC overhead limit exceededjava.lang.OutOfMemoryError: Metaspace- Metaspace exhaustedjava.lang.OutOfMemoryError: Compressed class space- Compressed class space exhaustedjava.lang.OutOfMemoryError: Requested array size exceeds VM limit- Array size exceeds limit
OutOfMemoryErrors that will NOT trigger Heap Dump (❌):
java.lang.OutOfMemoryError: realloc_objects- Deoptimization reallocation failure (thrown directly, doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError: retry(JDK 17+) - Retryable allocation failure (internal mechanism, doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError: Java heap space(no stack trace) (JDK 25+) - Internal OOM marking scenario (doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached- Thread creation failure (usesTHROW_MSG, doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError: could not allocate Unicode string- Unicode string allocation failure (usesTHROW_MSG_0, doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError(class verifier out of memory) - Out of memory during class verification (usesTHROW_MSG_, doesn’t callreport_java_out_of_memory)java.lang.OutOfMemoryError(Unsafe allocation failure) - Memory allocation failure in Unsafe operations (usesTHROW_0, doesn’t callreport_java_out_of_memory)
Other out-of-memory situations (don’t throw OutOfMemoryError):
- CodeCache out of memory - Directly calls
vm_exit_out_of_memory, VM exits directly without throwing exception
Trigger mechanism: Only OOM types that call report_java_out_of_memory will check the HeapDumpOnOutOfMemoryError flag and trigger Heap Dump. Other OOM types throw exceptions directly without going through the report_java_out_of_memory function.
2.2.3.1.1. Source Code Explanation for OOM Types That Won’t Trigger Heap Dump#
Thread Creation Failure:
jdk-jdk-11-28/src/hotspot/share/prims/jvm.cpp
if (native_thread->osthread() == NULL) {
// Thread creation failed, directly throw OutOfMemoryError without calling report_java_out_of_memory
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
os::native_thread_creation_failed_msg());
}
Unicode String Allocation Failure:
jdk-jdk-11-28/src/hotspot/share/classfile/javaClasses.cpp
// Unicode string allocation failed, directly throw OutOfMemoryError
THROW_MSG_0(vmSymbols::java_lang_OutOfMemoryError(), "could not allocate Unicode string");
Class Verifier Out of Memory:
jdk-jdk-11-28/src/hotspot/share/classfile/verifier.cpp
// Out of memory during class verification, directly throw OutOfMemoryError
THROW_MSG_(vmSymbols::java_lang_OutOfMemoryError(), message, NULL);
Unsafe Allocation Failure:
jdk-jdk-11-28/src/hotspot/share/prims/unsafe.cpp
u1* class_bytes = NEW_C_HEAP_ARRAY(u1, length, mtInternal);
if (class_bytes == NULL) {
// Memory allocation failure in Unsafe operation, directly throw OutOfMemoryError
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
CodeCache Out of Memory (doesn’t throw exception):
jdk-jdk-11-28/src/hotspot/share/code/codeBlob.cpp
if (blob == NULL) {
// CodeCache out of memory, directly exit VM without throwing exception
vm_exit_out_of_memory(size, OOM_MALLOC_ERROR, "CodeCache: no room for method handle adapter blob");
}
2.2.3.2. OOM Detection and Reporting Flow Diagram#
flowchart TD
A[Various Resource Allocation Scenarios] --> B{Resource Type}
B -->|Java Heap| C[MemAllocator::check_out_of_memory]
B -->|Metaspace| D[Metaspace::allocate]
B -->|Array Size| E1[TypeArrayKlass::allocate
JDK 11]
B -->|Array Size| E2[Klass::check_array_allocation_length
JDK 17+]
B -->|Other Resources| F[Other Allocation Paths
e.g., realloc_objects
Won't Trigger HeapDump]
C --> G{Allocation Failure Reason}
G -->|Heap Space Exhausted| H1{Retryable Allocation Check
JDK 17+: in_retryable_allocation
JDK 25+: is_in_internal_oome_mark}
G -->|GC Overhead Limit| I1{Retryable Allocation Check
JDK 17+: in_retryable_allocation
JDK 25+: is_in_internal_oome_mark}
H1 -->|No| H[report_java_out_of_memory
Java heap space]
H1 -->|Yes JDK 17+| H2[Throw retry OOM
Won't Trigger HeapDump]
H1 -->|Yes JDK 25+| H3[Throw OOM without stack trace
Won't Trigger HeapDump]
I1 -->|No| I[report_java_out_of_memory
GC overhead limit exceeded]
I1 -->|Yes JDK 17+| I2[Throw retry OOM
Won't Trigger HeapDump]
I1 -->|Yes JDK 25+| I3[Throw OOM without stack trace
Won't Trigger HeapDump]
D --> J[report_java_out_of_memory
Metaspace or
Compressed class space]
E1 --> K1[report_java_out_of_memory
Requested array size
exceeds VM limit]
E2 --> K2{Retryable Allocation Check
JDK 17+: in_retryable_allocation}
K2 -->|No| K[report_java_out_of_memory
Requested array size
exceeds VM limit]
K2 -->|Yes| K3[Throw retry OOM
Won't Trigger HeapDump]
H --> L[report_java_out_of_memory function]
I --> L
J --> L
K --> L
K1 --> L
L --> M1{Atomic::cmpxchg
JDK 11: cmpxchg 1, addr, 0
JDK 17+: cmpxchg addr, 0, 1}
M1 -->|Already Reported| Z[Skip]
M1 -->|First Report| N{HeapDumpOnOutOfMemoryError
Enabled?}
N -->|Yes| O[Print OOM Message]
N -->|No| P[Check OnOutOfMemoryError]
O --> Q[HeapDumper::dump_heap_from_oome]
Q --> R[HeapDumper::dump_heap true]
R --> S1[Build dump file path
JDK 11-21: os::malloc
JDK 25: Stack allocation]
S1 --> S2{HeapDumpPath
JDK 25: Supports %p placeholder}
S2 --> T[Create HeapDumper object]
T --> U1[VMThread::execute
Trigger safepoint]
U1 --> U2[VM_HeapDumper::doit
Execute dump at safepoint]
U2 --> U3{Compression
JDK 17+: HeapDumpGzipLevel}
U3 -->|Enabled| U4[Parallel compression
JDK 25: Multi-threaded]
U3 -->|Disabled| U5[Direct write]
U4 --> P
U5 --> P
P --> V{Other OOM Handling Options}
V --> W[CrashOnOutOfMemoryError]
V --> X[ExitOnOutOfMemoryError]
W --> Y1[fatal
JDK 11]
W --> Y2[report_fatal
JDK 17+]
X --> AA1[os::exit 3
JDK 11-17]
X --> AA2[os::_exit 3
JDK 21+
Fast exit]
style H fill:#90EE90
style I fill:#90EE90
style J fill:#FFB6C1
style K fill:#FFB6C1
style K1 fill:#FFB6C1
style Q fill:#87CEEB
style U2 fill:#87CEEB
style H2 fill:#FFE4B5
style I2 fill:#FFE4B5
style K3 fill:#FFE4B5
style H3 fill:#FFE4B5
style I3 fill:#FFE4B5
2.2.3.3. OOM Detection and Reporting Source Code Analysis#
2.2.3.3.1. Java heap space / GC overhead limit exceeded#
jdk-jdk-11-28/src/hotspot/share/gc/shared/memAllocator.cpp
JDK 11:
bool MemAllocator::Allocation::check_out_of_memory() {
// ... Omit allocation failure check ...
if (!_overhead_limit_exceeded) {
// Java heap space exhausted, call report_java_out_of_memory to trigger Heap Dump
report_java_out_of_memory("Java heap space");
// ... Omit JVMTI related code ...
THROW_OOP_(Universe::out_of_memory_error_java_heap(), true);
} else {
// GC overhead limit exceeded, call report_java_out_of_memory to trigger Heap Dump
report_java_out_of_memory("GC overhead limit exceeded");
// ... Omit JVMTI related code ...
THROW_OOP_(Universe::out_of_memory_error_gc_overhead_limit(), true);
}
}
JDK 17+:
bool MemAllocator::Allocation::check_out_of_memory() {
// ... Omit allocation failure check ...
const char* message = _overhead_limit_exceeded ?
"GC overhead limit exceeded" : "Java heap space";
// JDK 17+ added retryable allocation check to avoid triggering Heap Dump in retryable scenarios
if (!_thread->in_retryable_allocation()) { // JDK 17-21
// if (!_thread->is_in_internal_oome_mark()) { // JDK 25+
report_java_out_of_memory(message);
// ... Omit JVMTI related code ...
THROW_OOP_(exception, true);
} else {
// Retryable allocation failure, throw retry OOM (won't trigger Heap Dump)
THROW_OOP_(Universe::out_of_memory_error_retry(), true); // JDK 17-21
// THROW_OOP_(Universe::out_of_memory_error_java_heap_without_backtrace(), true); // JDK 25+
}
}
Version Differences:
- JDK 17+: Added
in_retryable_allocation()check, retryable allocation failures don’t trigger Heap Dump - JDK 25+: Uses
is_in_internal_oome_mark()instead, internal OOM marking scenarios don’t trigger Heap Dump
JDK 25+ Internal OOM Marking Scenario Explanation:
InternalOOMEMark is a RAII (Resource Acquisition Is Initialization) marking class used to identify memory allocation scenarios within internal JVM operations. In these scenarios, if memory allocation fails, it should not trigger Heap Dump and complete OOM handling flow because: It occurs within JVM internals (such as deoptimization, compilation, class loading, etc.), not directly triggered by user code. User code cannot catch these exceptions, and they may be quickly recoverable, or if they’re core operations, they can fail fast and exit.
Usage Scenarios:
- Deoptimization object reallocation: During deoptimization, objects need to be reallocated; if it fails, throw OOM without stack trace
- Compile-time string constant resolution: Allocation failure when resolving string constants during JIT compilation
- Array allocation during class loading: Array allocation failure during class loading
- JVMCI related operations: Memory allocation failures in JVMCI compiler-related operations
In these scenarios, if allocation fails, it throws out_of_memory_error_java_heap_without_backtrace(), which is an OOM exception without stack trace that doesn’t call report_java_out_of_memory, thus won’t trigger Heap Dump.
Source Code Example:
jdk-jdk-25-36/src/hotspot/share/gc/shared/memAllocator.hpp
// RAII class to mark thread in internal OOM handling scenario
// In these scenarios, OOM won't propagate to user code, so no stack trace needed
class InternalOOMEMark: public StackObj {
explicit InternalOOMEMark(JavaThread* thread) {
_outer = thread->is_in_internal_oome_mark();
thread->set_is_in_internal_oome_mark(true); // Set mark
_thread = thread;
}
~InternalOOMEMark() {
_thread->set_is_in_internal_oome_mark(_outer); // Restore mark
}
};
jdk-jdk-25-36/src/hotspot/share/runtime/deoptimization.cpp
// Reallocate objects during deoptimization, use InternalOOMEMark to mark
if (obj == nullptr && !cache_init_error) {
InternalOOMEMark iom(THREAD); // Enter internal OOM marking scenario
obj = ik->allocate_instance(THREAD); // If fails, throw OOM without stack trace
}
2.2.3.3.2. Metaspace / Compressed class space#
jdk-jdk-11-28/src/hotspot/share/memory/metaspace.cpp
void Metaspace::allocate(ClassLoaderData* cld, size_t word_size,
bool read_only, MetaspaceObj::Type type,
Metaspace** result, TRAPS) {
// ... Omit metaspace allocation logic ...
// Metaspace or compressed class space exhausted, call report_java_out_of_memory to trigger Heap Dump
// Rationale: Loaded classes have Class objects on Java heap, Heap Dump helps analyze class loading issues
const char* space_string = out_of_compressed_class_space ?
"Compressed class space" : "Metaspace";
report_java_out_of_memory(space_string);
// ... Omit exception throwing ...
}
2.2.3.3.3. Requested array size exceeds VM limit#
JDK 11:
jdk-jdk-11-28/src/hotspot/share/oops/typeArrayKlass.cpp
typeArrayOop TypeArrayKlass::allocate(int length, TRAPS) {
// ... Omit array allocation logic ...
if (length > max_length()) {
// Array size exceeds VM limit, call report_java_out_of_memory to trigger Heap Dump
// Rationale: Usually indicates oversized collections, possible memory leak
report_java_out_of_memory("Requested array size exceeds VM limit");
JvmtiExport::post_array_size_exhausted();
THROW_OOP_0(Universe::out_of_memory_error_array_size());
}
}
JDK 17+:
jdk-jdk-17-35/src/hotspot/share/oops/klass.cpp
void Klass::check_array_allocation_length(int length, int max_length, TRAPS) {
if (length > max_length) {
// JDK 17+ added retryable allocation check
if (!THREAD->in_retryable_allocation()) {
report_java_out_of_memory("Requested array size exceeds VM limit");
JvmtiExport::post_array_size_exhausted();
THROW_OOP(Universe::out_of_memory_error_array_size());
} else {
// Retryable allocation failure, throw retry OOM (won't trigger Heap Dump)
THROW_OOP(Universe::out_of_memory_error_retry());
}
}
}
2.2.3.4. OOM Report Handling#
jdk-jdk-11-28/src/hotspot/share/utilities/debug.cpp
JDK 11:
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
// Multiple threads may try to report OutOfMemoryError simultaneously, use atomic operation to ensure only once
// JDK 11: Atomic::cmpxchg(expected, addr, new_value) returns old value
if (Atomic::cmpxchg(1, &out_of_memory_reported, 0) == 0) {
// Create Java heap dump before OnOutOfMemoryError command execution
if (HeapDumpOnOutOfMemoryError) {
tty->print_cr("java.lang.OutOfMemoryError: %s", message);
HeapDumper::dump_heap_from_oome();
}
// ... Omit OnOutOfMemoryError handling ...
if (CrashOnOutOfMemoryError) {
fatal("OutOfMemory encountered: %s", message);
}
if (ExitOnOutOfMemoryError) {
os::exit(3);
}
}
}
JDK 17+:
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
// JDK 17+: Atomic::cmpxchg(addr, expected, new_value) parameter order changed
if (Atomic::cmpxchg(&out_of_memory_reported, 0, 1) == 0) {
if (HeapDumpOnOutOfMemoryError) {
tty->print_cr("java.lang.OutOfMemoryError: %s", message);
HeapDumper::dump_heap_from_oome();
}
// ... Omit OnOutOfMemoryError handling ...
if (CrashOnOutOfMemoryError) {
// JDK 17+: Use report_fatal instead of fatal
report_fatal(OOM_JAVA_HEAP_FATAL, __FILE__, __LINE__, "OutOfMemory encountered: %s", message);
}
if (ExitOnOutOfMemoryError) {
os::exit(3); // JDK 17
// os::_exit(3); // JDK 21+: Fast exit, doesn't run cleanup hooks
}
}
}
Key Points:
- Atomic operation version difference: JDK 11 uses
cmpxchg(1, &addr, 0), JDK 17+ usescmpxchg(&addr, 0, 1)(parameter order changed) - Exit mechanism difference: JDK 21+ uses
os::_exit(3)for fast exit without running cleanup hooks - Note: All places calling
report_java_out_of_memorycheckHeapDumpOnOutOfMemoryError, no filtering by message type
2.2.3.5. Safepoint Execution Mechanism#
jdk-jdk-11-28/src/hotspot/share/services/heapDumper.cpp
// Called by OOM error reporting, in Java thread, not at safepoint
void HeapDumper::dump_heap_from_oome() {
HeapDumper::dump_heap(true);
}
// Called by error reporting (not at safepoint) or by VM thread at GC safepoint
void HeapDumper::dump(const char* path) {
// ... Omit writer creation code ...
// Create VM_HeapDumper operation object
VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome);
if (Thread::current()->is_VM_thread()) {
// If already VM thread, must be at safepoint
assert(SafepointSynchronize::is_at_safepoint(), "Expected to be called at a safepoint");
dumper.doit();
} else {
// Otherwise trigger safepoint via VMThread::execute, all Java threads pause
VMThread::execute(&dumper);
}
// ... Omit other code ...
}
Safepoint Execution Mechanism:
- OOM Scenario:
report_java_out_of_memorycalled by Java thread, not at safepoint - Dump Trigger: Calls
HeapDumper::dump_heap_from_oome()→HeapDumper::dump_heap() - Safepoint Entry: Triggers safepoint via
VMThread::execute(&dumper), all Java threads pause - Dump Execution: VM_thread executes
VM_HeapDumper::doit()at safepoint, traverses Java heap
Key Points:
- ✅ Java heap dump executes at safepoint, ensuring Java heap state consistency
- ✅ Via VM_Operation mechanism, executed by VM_thread at safepoint
- ✅ All Java threads pause during dump, avoiding concurrent modifications
2.2.3.6. Java Heap Dump Execution#
JDK 11:
jdk-jdk-11-28/src/hotspot/share/services/heapDumper.cpp
void HeapDumper::dump_heap(bool oome) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
char* my_path;
const char* dump_file_name = "java_pid";
const char* dump_file_ext = ".hprof";
if (dump_file_seq == 0) {
// First call: Process HeapDumpPath, build base path
// ... Omit path validation and directory check ...
// If HeapDumpPath not specified or is directory, use default filename
if (use_default_filename) {
jio_snprintf(&base_path[dlen], sizeof(base_path)-dlen, "%s%d%s",
dump_file_name, os::current_process_id(), dump_file_ext);
}
// JDK 11-21: Use os::malloc to allocate path memory from native heap
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == NULL) {
warning("Cannot create heap dump file. Out of system memory.");
return;
}
strncpy(my_path, base_path, len);
} else {
// Subsequent dumps: Append sequence number
jio_snprintf(my_path, len, "%s.%d", base_path, dump_file_seq);
}
dump_file_seq++;
// Create HeapDumper object and execute dump (no compression support)
HeapDumper dumper(false, true, oome);
dumper.dump(my_path);
os::free(my_path);
}
JDK 17+:
void HeapDumper::dump_heap(bool oome) {
// ... Omit path processing logic (similar to JDK 11) ...
// JDK 17+: Determine file extension based on HeapDumpGzipLevel
const char* dump_file_ext = (HeapDumpGzipLevel > 0) ? ".hprof.gz" : ".hprof";
// ... Omit path construction ...
// Create HeapDumper object, supports compression
HeapDumper dumper(false, true, oome);
dumper.dump(my_path);
os::free(my_path);
}
JDK 25+:
void HeapDumper::dump_heap(bool oome) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
// JDK 25+: Use stack allocation to avoid allocation failure in OOM scenarios
char my_path[JVM_MAXPATHLEN];
if (dump_file_seq == 0) {
// JDK 25+: Supports %p placeholder, use Arguments::copy_expand_pid to expand
if (HeapDumpPath != nullptr && strstr(HeapDumpPath, "%p") != nullptr) {
Arguments::copy_expand_pid(HeapDumpPath, my_path, JVM_MAXPATHLEN);
}
// ... Omit other path processing ...
}
// JDK 25+: Supports parallel dump and compression
HeapDumper dumper(false, true, oome);
dumper.dump(my_path);
}
Version Difference Summary:
- JDK 11: No compression support, uses
os::mallocto allocate path memory - JDK 17+: Supports gzip compression (
HeapDumpGzipLevel), file extension is.hprofor.hprof.gz - JDK 21+: Uses
nullptrinstead ofNULL(C++11 style) - JDK 25+: Supports stack allocation for path (avoids allocation failure in OOM scenarios), supports
%pplaceholder, supports parallel dump and compression
2.2.3.7. JDK 25+ Parallel Dump and Compression Mechanism Details#
JDK 25 introduced a parallel dump mechanism that can fully utilize multi-core CPUs to accelerate the dump process for large Java heaps. Parallel dump adopts a two-phase design of segmented writing + merging.
2.2.3.7.1. Parallel Dump Flow Diagram#
flowchart TD A[HeapDumper::dump] --> B[Create DumpWriter
and GZipCompressor] B --> C[VM_HeapDumper constructor
Pass num_dump_threads] C --> D[VMThread::execute
Trigger safepoint] D --> E[VM_HeapDumper::doit
Execute at safepoint] E --> F[Optional: GC before dump] F --> G[prepare_parallel_dump
Determine actual thread count] G --> H{Parallel dump?} H -->|No| I[Single-threaded dump
work VMDumperId] H -->|Yes| J[Create ParallelObjectIterator
Initialize heap partitioning] J --> K[workers->run_task
Start multiple workers] K --> L1[Worker 0: VM Dumper] K --> L2[Worker 1-N: Parallel Dumpers] L1 --> M1[lock_global_writer
Acquire global write lock] M1 --> N1[Write file header
HPROF_HEADER] N1 --> O1[Write UTF8 records
HPROF_UTF8] O1 --> P1[Write class load records
HPROF_LOAD_CLASS] P1 --> Q1[Write stack traces
HPROF_TRACE] Q1 --> R1[unlock_global_writer
Release global write lock] L2 --> M2[wait_for_start_signal
Wait for VM Dumper to complete non-heap data] M2 --> N2[Create segment files
base_path.p1, .p2, ...] R1 --> S[Parallel phase begins] S --> T1[VM Dumper: Write class dumps
HPROF_GC_CLASS_DUMP] S --> T2[VM Dumper: Write thread objects
HPROF_GC_ROOT_THREAD_OBJ] S --> T3[VM Dumper: Write JNI global refs
HPROF_GC_ROOT_JNI_GLOBAL] T1 --> U[Parallel heap object traversal] T2 --> U T3 --> U U --> V1[Worker 0: ParallelObjectIterator
Traverse heap region 0] U --> V2[Worker 1: ParallelObjectIterator
Traverse heap region 1] U --> V3[Worker N: ParallelObjectIterator
Traverse heap region N] V1 --> W1[Write instance dumps
HPROF_GC_INSTANCE_DUMP
to segment file .p0] V2 --> W2[Write instance dumps
HPROF_GC_INSTANCE_DUMP
to segment file .p1] V3 --> W3[Write instance dumps
HPROF_GC_INSTANCE_DUMP
to segment file .pN] W1 --> X1{Compression enabled?} W2 --> X2{Compression enabled?} W3 --> X3{Compression enabled?} X1 -->|Yes| Y1[GZip compress segment data
Independent compression buffer] X1 -->|No| Z1[Direct write to segment file] X2 -->|Yes| Y2[GZip compress segment data
Independent compression buffer] X2 -->|No| Z2[Direct write to segment file] X3 -->|Yes| Y3[GZip compress segment data
Independent compression buffer] X3 -->|No| Z3[Direct write to segment file] Y1 --> AA1[finish_dump_segment
Complete segment write] Y2 --> AA2[finish_dump_segment
Complete segment write] Y3 --> AA3[finish_dump_segment
Complete segment write] Z1 --> AA1 Z2 --> AA2 Z3 --> AA3 AA1 --> BB1[dumper_complete
Notify completion] AA2 --> BB2[dumper_complete
Notify completion] AA3 --> BB3[dumper_complete
Notify completion] BB1 --> CC[VM Dumper waits
wait_all_dumpers_complete] BB2 --> CC BB3 --> CC CC --> DD[Safepoint ends
Return to calling thread] DD --> EE[DumpMerger::do_merge
Merge segment files] EE --> FF[Read segment files .p0, .p1, ..., .pN] FF --> GG{Linux?} GG -->|Yes| HH[sendfile system call
Zero-copy merge] GG -->|No| II[read + write
Regular merge] HH --> JJ[Write HPROF_HEAP_DUMP_END] II --> JJ JJ --> KK[Delete temporary segment files] KK --> LL[Complete merge
Generate final .hprof file] style L1 fill:#87CEEB style L2 fill:#90EE90 style U fill:#FFB6C1 style EE fill:#FFE4B5 style HH fill:#DDA0DD
2.2.3.7.2. Thread Count Determination#
jdk-jdk-25-36/src/hotspot/share/services/heapDumper.hpp
// Default thread count: 3/8 of CPU cores
static uint default_num_of_dump_threads() {
return MAX2<uint>(1, (uint)os::initial_active_processor_count() * 3 / 8);
}
jdk-jdk-25-36/src/hotspot/share/services/heapDumper.cpp
int HeapDumper::dump(const char* path, outputStream* out, int compression, bool overwrite, uint num_dump_threads) {
// Thread count limit in OOM scenarios: Each thread requires approximately 20MB memory
if (_oome && num_dump_threads > 1) {
// DumpWriter buffer, DumperClassCacheTable, GZipCompressor buffers
julong max_threads = os::free_memory() / (20 * M);
if (num_dump_threads > max_threads) {
num_dump_threads = MAX2<uint>(1, (uint)max_threads);
}
}
// ... Omit other code ...
}
void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) {
uint num_active_workers = workers != nullptr ? workers->active_workers() : 0;
uint num_requested_dump_threads = _num_dumper_threads;
// Check if parallel dump is possible
if (num_active_workers <= 1 || num_requested_dump_threads <= 1) {
_num_dumper_threads = 1; // Single-threaded dump
} else {
// Limit between 2 and active_workers
_num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers);
}
_dumper_controller = new (std::nothrow) DumperController(_num_dumper_threads);
// ... Log recording ...
}
Thread Count Determination Rules:
- Default value:
CPU cores * 3 / 8(e.g., 8-core CPU defaults to 3 threads) - OOM scenario limit: Dynamically adjusted based on available memory, each thread requires approximately 20MB (DumpWriter buffer, DumperClassCacheTable, GZipCompressor buffers)
- Actual thread count:
clamp(requested threads, 2, active_workers), at least 2 threads to enable parallel, at most not exceeding GC worker thread count - Single-thread fallback: If
active_workers <= 1orrequested threads <= 1, fall back to single-threaded dump
2.2.3.7.3. Heap Partitioning Method#
Parallel dump uses ParallelObjectIterator to partition the heap; different GC implementations have different partitioning strategies:
G1 GC Partitioning Method:
jdk-jdk-25-36/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
class G1ParallelObjectIterator : public ParallelObjectIteratorImpl {
G1HeapRegionClaimer _claimer; // Partition by region
public:
G1ParallelObjectIterator(uint thread_num) :
_heap(G1CollectedHeap::heap()),
_claimer(thread_num == 0 ? G1CollectedHeap::heap()->workers()->active_workers() : thread_num) {}
virtual void object_iterate(ObjectClosure* cl, uint worker_id) {
// Each worker processes different regions
_heap->object_iterate_parallel(cl, worker_id, &_claimer);
}
};
Partitioning Strategies:
- G1 GC: Partition by Heap Region, each worker thread processes different regions
- ZGC: Partition by Page
- Shenandoah: Partition by region, using task queue
- Parallel GC: Partition by heap area
Key Points:
- Each worker thread independently traverses assigned heap areas
- Uses mechanisms like
G1HeapRegionClaimerto ensure regions aren’t processed repeatedly - Partitioning granularity depends on GC implementation, usually consistent with GC’s parallel strategy
2.2.3.7.4. Parallel Dump Execution Flow#
jdk-jdk-25-36/src/hotspot/share/services/heapDumper.cpp
void VM_HeapDumper::doit() {
CollectedHeap* ch = Universe::heap();
// Optional: Execute GC before dump
if (_gc_before_heap_dump) {
ch->collect_as_vm_thread(GCCause::_heap_dump);
}
WorkerThreads* workers = ch->safepoint_workers();
prepare_parallel_dump(workers); // Determine actual thread count
if (!is_parallel_dump()) {
// Single-threaded dump
work(VMDumperId);
} else {
// Parallel dump: Create ParallelObjectIterator and start multiple workers
ParallelObjectIterator poi(_num_dumper_threads);
_poi = &poi;
workers->run_task(this, _num_dumper_threads); // Start worker threads
_poi = nullptr;
}
}
void VM_HeapDumper::work(uint worker_id) {
int dumper_id = get_next_dumper_id(); // Atomically get dumper ID
if (is_vm_dumper(dumper_id)) {
// VM Dumper (worker_id=0): Responsible for non-heap data
_dumper_controller->lock_global_writer();
_dumper_controller->signal_start();
// Write file header, UTF8, class loading, stack traces, etc.
writer()->write_raw("JAVA PROFILE 1.0.2", ...);
SymbolTable::symbols_do(&sym_dumper);
ClassLoaderDataGraph::classes_do(&loaded_class_dumper);
dump_stack_traces(writer());
_dumper_controller->unlock_global_writer(); // Release lock, allow parallel dumpers to start
} else {
// Parallel Dumpers (worker_id=1-N): Wait for VM Dumper to complete non-heap data
_dumper_controller->wait_for_start_signal();
}
// Create segment file: base_path.p0, .p1, .p2, ...
DumpWriter segment_writer(DumpMerger::get_writer_path(writer()->get_file_path(), dumper_id),
writer()->is_overwrite(), writer()->compressor());
if (is_vm_dumper(dumper_id)) {
// VM Dumper also responsible for writing class dumps, thread objects, JNI global refs, etc.
ClassDumper class_dumper(&segment_writer);
ClassLoaderDataGraph::classes_do(&class_dumper);
dump_threads(&segment_writer);
// ... Omit other GC roots ...
}
// Parallel heap object traversal
HeapObjectDumper obj_dumper(&segment_writer, this);
if (!is_parallel_dump()) {
Universe::heap()->object_iterate(&obj_dumper);
} else {
// Parallel dump: Each worker traverses assigned heap areas
_poi->object_iterate(&obj_dumper, worker_id);
}
segment_writer.finish_dump_segment();
segment_writer.flush();
_dumper_controller->dumper_complete(&segment_writer, writer());
if (is_vm_dumper(dumper_id)) {
// VM Dumper waits for all parallel dumpers to complete
_dumper_controller->wait_all_dumpers_complete();
writer()->flush();
// At this point all segment files are written, need to merge outside safepoint
}
}
Execution Flow Key Points:
VM Dumper (worker_id=0):
- Acquires global write lock, writes non-heap data (file header, UTF8, class info, stack traces)
- After releasing lock, continues writing class dumps, thread objects, etc.
- Finally waits for all parallel dumpers to complete
Parallel Dumpers (worker_id=1-N):
- Wait for VM Dumper to complete non-heap data writing
- Create independent segment files (
.p1,.p2, …,.pN) - Parallel traverse assigned heap areas, write instance dump records
- Each segment file independently compressed (if enabled)
Synchronization Mechanism:
- Uses
DumperControllerto coordinate multiple dumpers lock_global_writer/unlock_global_writer: Protect non-heap data writingwait_for_start_signal/signal_start: Ensure parallel dumpers start after VM Dumper completeswait_all_dumpers_complete: VM Dumper waits for all parallel dumpers to complete
- Uses
2.2.3.7.5. Parallel Compression Mechanism#
jdk-jdk-25-36/src/hotspot/share/services/heapDumperCompression.hpp
class GZipCompressor : public AbstractCompressor {
int _level; // Compression level 0-9
size_t _block_size; // Compression block size
bool _is_first; // Whether first block
public:
virtual char const* compress(char* in, size_t in_size, char* out, size_t out_size,
char* tmp, size_t tmp_size, size_t* compressed_size) {
// Each segment file independently compressed, using independent compression buffer
if (_is_first) {
// First block writes block size comment
jio_snprintf(buf, sizeof(buf), "HPROF BLOCKSIZE=%zu", _block_size);
*compressed_size = ZipLibrary::compress(..., buf, ...);
_is_first = false;
} else {
*compressed_size = ZipLibrary::compress(..., nullptr, ...);
}
}
};
Parallel Compression Features:
- Independent compression: Each segment file uses independent
GZipCompressorinstance and compression buffer - Block-level compression: Data compressed by blocks, each block processed independently
- No global synchronization: Each worker thread compresses independently, no synchronization needed
- Memory overhead: Each thread requires independent compression buffer (approximately 20MB/thread)
2.2.3.7.6. Segment File Merge Mechanism#
jdk-jdk-25-36/src/hotspot/share/services/heapDumper.cpp
int HeapDumper::dump(const char* path, ...) {
// Phase 1: Write segment files in parallel within safepoint
VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome, num_dump_threads);
VMThread::execute(&dumper);
// Phase 2: Merge segment files outside safepoint (doesn't occupy VM Thread)
DumpMerger merger(path, &writer, dumper.dump_seq());
merger.do_merge();
}
void DumpMerger::do_merge() {
// No need to compress again during merge (segment files already compressed)
AbstractCompressor* saved_compressor = _writer->compressor();
_writer->set_compressor(nullptr);
// Merge all segment files in order
for (int i = 0; i < _dump_seq; i++) {
const char* path = get_writer_path(_path, i); // base_path.p0, .p1, ...
merge_file(path);
remove(path); // Delete temporary segment file
}
_writer->set_compressor(saved_compressor);
// Write HPROF_HEAP_DUMP_END record
DumperSupport::end_of_dump(_writer);
}
#ifdef LINUX
void DumpMerger::merge_file(const char* path) {
// Linux: Use sendfile for zero-copy merge (efficient)
int segment_fd = os::open(path, O_RDONLY, 0);
os::Linux::sendfile(_writer->get_fd(), segment_fd, &offset, st.st_size);
::close(segment_fd);
}
#else
void DumpMerger::merge_file(const char* path) {
// Other platforms: Use read + write merge
fileStream segment_fs(path, "rb");
while ((cnt = segment_fs.read(_writer->buffer(), 1, _writer->buffer_size())) != 0) {
_writer->set_position(cnt);
_writer->flush();
}
}
#endif
Merge Mechanism Features:
Two-phase design:
- Phase 1: Write segment files in parallel within safepoint (
.p0,.p1, …,.pN) - Phase 2: Merge segment files outside safepoint (doesn’t occupy VM Thread, doesn’t affect GC)
- Phase 1: Write segment files in parallel within safepoint (
Merge optimization:
- Linux: Uses
sendfilesystem call for zero-copy merge - Other platforms: Uses
read + writeregular merge - No need to compress again during merge (segment files already compressed)
- Linux: Uses
File naming:
- Segment files:
base_path.p0,base_path.p1, …,base_path.pN - Final file:
base_path(all segment files deleted after merge)
- Segment files:
2.2.3.7.7. Design and Limitations#
Design Considerations:
- Parallel traversal: Multiple threads simultaneously traverse different heap areas, fully utilizing multi-core CPUs
- Parallel compression: Each thread compresses independently, compression speed scales linearly with thread count (ideal case)
- Zero-copy merge: Linux platform uses
sendfilefor efficient segment file merging
Limitations:
- Memory overhead: Each thread requires approximately 20MB memory (DumpWriter buffer, compression buffer, etc.)
- OOM scenario limitations: Limited available memory during OOM, may not enable parallel dump
- Disk I/O bottleneck: Even with parallel dump, ultimately limited by disk I/O speed
- Merge overhead: Merge phase needs to read all segment files and write to final file
2.3. HeapDumpBeforeFullGC / HeapDumpAfterFullGC#
2.3.1. Parameter Description#
Description:
HeapDumpBeforeFullGC: Dump Java heap before Full GCHeapDumpAfterFullGC: Dump Java heap after Full GC
Default: false
Type: manageable (JDK 11) or product + MANAGEABLE (JDK 17+), can be modified dynamically via JMX
Example:
-XX:+HeapDumpBeforeFullGC-XX:+HeapDumpAfterFullGC
2.3.2. Implementation Mechanism#
Calls CollectedHeap::full_gc_dump() before and after Full GC; if the corresponding flag is enabled, triggers Java heap dump.
JDK 11-24 Implementation:
jdk-jdk-11-28/src/hotspot/share/gc/shared/collectedHeap.cpp
void CollectedHeap::full_gc_dump(GCTimer* timer, bool before) {
assert(timer != NULL, "timer is null");
// Check if corresponding Heap Dump parameter is enabled
if ((HeapDumpBeforeFullGC && before) || (HeapDumpAfterFullGC && !before)) {
// Record GC log and trigger Java heap dump
GCTraceTime(Info, gc) tm(before ? "Heap Dump (before full gc)" : "Heap Dump (after full gc)", timer);
HeapDumper::dump_heap();
}
// ... Omit other code ...
}
JDK 25+ Changes: Added FullGCHeapDumpLimit to prevent excessive dump files from frequent Full GCs:
jdk-jdk-25-36/src/hotspot/share/gc/shared/collectedHeap.cpp
void CollectedHeap::full_gc_dump(GCTimer* timer, bool before) {
assert(timer != nullptr, "timer is null");
static uint count = 0; // Static counter, records number of dumps
// Check if corresponding Heap Dump parameter is enabled
if ((HeapDumpBeforeFullGC && before) || (HeapDumpAfterFullGC && !before)) {
// Check dump count limit: 0 means unlimited, otherwise check if limit exceeded
if (FullGCHeapDumpLimit == 0 || count < FullGCHeapDumpLimit) {
GCTraceTime(Info, gc) tm(before ? "Heap Dump (before full gc)" : "Heap Dump (after full gc)", timer);
HeapDumper::dump_heap();
count++; // Increment counter after dump
}
}
// ... Omit other code ...
}
Key Points:
- Full GC Java heap dump executes during Full GC safepoint
- JDK 25 added dump count limit to prevent excessive dump files from frequent Full GCs
2.4. HeapDumpPath#
2.4.1. Parameter Description#
Description: Specifies the path (filename or directory) for Java heap dump files.
Default: NULL (JDK 11-17) or nullptr (JDK 21+)
Type: manageable (JDK 11) or product + MANAGEABLE (JDK 17+), can be modified dynamically via JMX
Example:
-XX:HeapDumpPath=/path/to/dump.hprof(specify file)-XX:HeapDumpPath=/path/to/dumps/(specify directory, will auto-generate filename)
2.4.2. File Naming Rules#
- If not specified or empty, default filename is
java_pid<pid>.hprof - If specified as directory, will create default filename in directory
- For multiple dumps, subsequent files append sequence number:
java_pid<pid>.hprof.1,java_pid<pid>.hprof.2, etc.
JDK 25 Addition: Supports %p placeholder, automatically replaced with process ID.
2.4.3. Implementation Mechanism#
2.4.3.1. Parameter Definition and Parsing#
Parameter Definition:
jdk-jdk-17-35/src/hotspot/share/runtime/globals.hpp
product(ccstr, HeapDumpPath, NULL, MANAGEABLE,
"When HeapDumpOnOutOfMemoryError is on, the path (filename or "
"directory) of the dump file (defaults to java_pid<pid>.hprof "
"in the current working directory)")
Parameter Type Description:
ccstr: C string type, stores pointer to stringMANAGEABLE: Can be modified dynamically via JMX- Default value:
NULL(JDK 11-17) ornullptr(JDK 21+)
Parameter Parsing:
- Specified at JVM startup via
-XX:HeapDumpPath=<path> - Can be modified at runtime via JMX
HotSpotDiagnosticMXBean.setVMOption() - Parameter value stored in global variable
HeapDumpPath
2.4.3.2. Path Processing Logic#
JDK 11-21 Implementation:
jdk-jdk-17-35/src/hotspot/share/services/heapDumper.cpp
void HeapDumper::dump_heap(bool oome) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
char* my_path;
const char* dump_file_name = "java_pid";
const char* dump_file_ext = HeapDumpGzipLevel > 0 ? ".hprof.gz" : ".hprof";
if (dump_file_seq == 0) {
// First call: Calculate maximum path length and validate
const size_t total_length =
(HeapDumpPath == NULL ? 0 : strlen(HeapDumpPath)) +
strlen(os::file_separator()) + max_digit_chars +
strlen(dump_file_name) + strlen(dump_file_ext) + 1;
if (total_length > sizeof(base_path)) {
warning("Cannot create heap dump file. HeapDumpPath is too long.");
return;
}
bool use_default_filename = true;
if (HeapDumpPath == NULL || HeapDumpPath[0] == '\0') {
// HeapDumpPath not specified, use default filename
} else {
strcpy(base_path, HeapDumpPath);
// Check if path is existing directory
DIR* dir = os::opendir(base_path);
if (dir == NULL) {
// Not a directory, treat as filename
use_default_filename = false;
} else {
// Is directory, append file separator (if needed)
os::closedir(dir);
size_t fs_len = strlen(os::file_separator());
if (strlen(base_path) >= fs_len) {
char* end = base_path;
end += (strlen(base_path) - fs_len);
if (strcmp(end, os::file_separator()) != 0) {
strcat(base_path, os::file_separator());
}
}
}
}
// If HeapDumpPath is not filename, append default filename
if (use_default_filename) {
const size_t dlen = strlen(base_path);
jio_snprintf(&base_path[dlen], sizeof(base_path)-dlen, "%s%d%s",
dump_file_name, os::current_process_id(), dump_file_ext);
}
// Use os::malloc to allocate path memory from native heap
const size_t len = strlen(base_path) + 1;
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == NULL) {
warning("Cannot create heap dump file. Out of system memory.");
return;
}
strncpy(my_path, base_path, len);
} else {
// Subsequent dumps: Append sequence number
const size_t len = strlen(base_path) + max_digit_chars + 2; // for '.' and \0
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == NULL) {
warning("Cannot create heap dump file. Out of system memory.");
return;
}
jio_snprintf(my_path, len, "%s.%d", base_path, dump_file_seq);
}
dump_file_seq++;
// ... Execute dump ...
os::free(my_path);
}
Key Processing Logic:
- Path length validation: On first call, calculate maximum possible path length; if exceeds
JVM_MAXPATHLEN, warn and return - Directory detection: Use
os::opendir()to check if path is existing directory - File separator handling: If directory, automatically append file separator (if needed)
- Default filename generation: If not specified or is directory, generate
java_pid<pid>.hprofformat filename - Sequence number appending: For multiple dumps, append
.1,.2, etc. sequence numbers after base path - Memory allocation: Use
os::malloc()to allocate path memory from native heap, free after dump completes
JDK 25+ Changes: Supports %p placeholder and stack allocation for path variable:
jdk-jdk-25-36/src/hotspot/share/services/heapDumper.cpp
void HeapDumper::dump_heap(bool oome) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
char my_path[JVM_MAXPATHLEN]; // JDK 25+: Use stack allocation to avoid allocation failure in OOM scenarios
const int max_digit_chars = 20;
const char* dump_file_name = HeapDumpGzipLevel > 0 ? "java_pid%p.hprof.gz" : "java_pid%p.hprof";
if (dump_file_seq == 0) {
// Set base path (filename or directory, default or custom, without sequence number), perform %p replacement
const char *path_src = (HeapDumpPath != nullptr && HeapDumpPath[0] != '\0') ? HeapDumpPath : dump_file_name;
// Use Arguments::copy_expand_pid to expand %p placeholder
if (!Arguments::copy_expand_pid(path_src, strlen(path_src), base_path, JVM_MAXPATHLEN - max_digit_chars)) {
warning("Cannot create heap dump file. HeapDumpPath is too long.");
return;
}
// Check if path is existing directory
DIR* dir = os::opendir(base_path);
if (dir != nullptr) {
os::closedir(dir);
// Path is directory, append file separator (if needed)
size_t fs_len = strlen(os::file_separator());
if (strlen(base_path) >= fs_len) {
char* end = base_path;
end += (strlen(base_path) - fs_len);
if (strcmp(end, os::file_separator()) != 0) {
strcat(base_path, os::file_separator());
}
}
// Then add default filename, perform %p replacement. Use my_path for temporary storage
if (!Arguments::copy_expand_pid(dump_file_name, strlen(dump_file_name), my_path, JVM_MAXPATHLEN - max_digit_chars)) {
warning("Cannot create heap dump file. HeapDumpPath is too long.");
return;
}
const size_t dlen = strlen(base_path);
jio_snprintf(&base_path[dlen], sizeof(base_path) - dlen, "%s", my_path);
}
strncpy(my_path, base_path, JVM_MAXPATHLEN);
} else {
// Subsequent dumps: Append sequence number
const size_t len = strlen(base_path) + max_digit_chars + 2; // for '.' and \0
jio_snprintf(my_path, len, "%s.%d", base_path, dump_file_seq);
}
dump_file_seq++;
// ... Execute dump ...
}
JDK 25+ Key Changes:
- Stack allocation: Uses stack array
char my_path[JVM_MAXPATHLEN]instead ofos::malloc()to avoid memory allocation failure in OOM scenarios - %p placeholder support: Uses
Arguments::copy_expand_pid()to expand%pplaceholder to process ID - Default filename includes %p: Default filename format changed to
java_pid%p.hprof, replaced with actual process ID during expansion
2.4.3.3. %p Placeholder Expansion Mechanism#
JDK 25+ Introduction: Arguments::copy_expand_pid() function used to expand %p placeholder in paths:
jdk-jdk-17-35/src/hotspot/share/runtime/arguments.cpp
bool Arguments::copy_expand_pid(const char* src, size_t srclen,
char* buf, size_t buflen) {
const char* p = src;
char* b = buf;
const char* src_end = &src[srclen];
char* buf_end = &buf[buflen - 1];
while (p < src_end && b < buf_end) {
if (*p == '%') {
switch (*(++p)) {
case '%': // "%%" ==> "%" (escape)
*b++ = *p++;
break;
case 'p': { // "%p" ==> current process ID
// Calculate available buffer size
size_t buf_sz = buf_end - b + 1;
// Use jio_snprintf to format process ID
int ret = jio_snprintf(b, buf_sz, "%d", os::current_process_id());
// If formatting fails or buffer insufficient, return false
if (ret < 0 || ret >= (int)buf_sz) {
return false;
} else {
b += ret; // Move buffer pointer
assert(*b == '\0', "fail in copy_expand_pid");
if (p == src_end && b == buf_end + 1) {
// Reached buffer end
return true;
}
}
p++; // Skip 'p'
break;
}
default:
// Unknown placeholder, copy as-is
*b++ = '%';
*b++ = *p++;
break;
}
} else {
// Regular character, copy directly
*b++ = *p++;
}
}
*b = '\0'; // Ensure string null-terminated
return true;
}
Placeholder Expansion Rules:
%p: Replaced with current process ID (obtained viaos::current_process_id())%%: Escaped to single%character- Other
%X: Unknown placeholders preserved as-is (%+X) - Buffer check: Ensures expanded path doesn’t exceed buffer size, otherwise returns
false
Usage Examples:
-XX:HeapDumpPath=./dump_%p.hprof→./dump_12345.hprof(assuming process ID is 12345)-XX:HeapDumpPath=/var/log/java_pid%p.hprof→/var/log/java_pid12345.hprof-XX:HeapDumpPath=/tmp/dumps/→/tmp/dumps/java_pid12345.hprof(directory + default filename)
Key Points:
- Path length limit: Expanded path length cannot exceed
JVM_MAXPATHLEN - max_digit_chars(reserve space for sequence number) - Directory detection timing:
%pexpansion executes before directory detection, so%pcan be used in directory paths - Multiple dumps: First dump expands
%p, subsequent dumps use already-expanded base path with appended sequence number
2.5. HeapDumpGzipLevel#
2.5.1. Parameter Description#
Description: Specifies gzip compression level (0-9) for Java heap dump files.
Default: 0 (compression disabled)
Type: product + MANAGEABLE (JDK 17+), can be modified dynamically via JMX
Range: 0-9
0: Compression disabled1-9: Compression level, higher numbers mean better compression but longer compression time
Example: -XX:HeapDumpGzipLevel=5
2.5.2. Compression Performance Reference#
Assuming 4 CPUs, compression time estimate for 8GB Java heap:
- Level 5 (commonly used for cost-effectiveness):
- Compression ratio: 2.7×–3.1×
- Compression time: 4.4–11.2 seconds
- Compressed file: 2.6–3.2 GB
- Write to HDD: 13–32 seconds
- Total time: 17.4–43.2 seconds
Note: This is an ideal estimate; actual performance may be worse.
2.6. FullGCHeapDumpLimit#
2.6.1. Parameter Description#
Description: Limits the number of Java heap dumps triggered by Full GC.
Default: 0 (unlimited)
Type: product + MANAGEABLE (JDK 25+), can be modified dynamically via JMX
Example: -XX:FullGCHeapDumpLimit=5
2.6.2. Implementation Mechanism#
jdk-jdk-25-36/src/hotspot/share/gc/shared/collectedHeap.cpp
void CollectedHeap::full_gc_dump(GCTimer* timer, bool before) {
assert(timer != nullptr, "timer is null");
static uint count = 0; // Static counter, records number of dumps
if ((HeapDumpBeforeFullGC && before) || (HeapDumpAfterFullGC && !before)) {
// Check dump count limit: 0 means unlimited, otherwise check if limit exceeded
if (FullGCHeapDumpLimit == 0 || count < FullGCHeapDumpLimit) {
GCTraceTime(Info, gc) tm(before ? "Heap Dump (before full gc)" : "Heap Dump (after full gc)", timer);
HeapDumper::dump_heap();
count++; // Increment counter after dump
}
}
}
Key Points:
- Uses static counter
countto track number of dumps 0means unlimited, otherwise stops dumping after reaching limit- Counter shared by both
HeapDumpBeforeFullGCandHeapDumpAfterFullGC
2.7. Best Practices for Heap Dump Parameters#
2.7.1. Production Environment Recommendations#
Not Recommended:
- ❌
-XX:+HeapDumpOnOutOfMemoryError- Blocks OOM handling, long dump time - ❌
-XX:+HeapDumpBeforeFullGC- Affects GC performance, generates excessive files - ❌
-XX:+HeapDumpAfterFullGC- Same issues as above
Recommended Approach:
- Use monitoring tools: Continuously monitor heap usage via JMX/monitoring systems
- Manual dump: When issues detected, manually trigger dump via
jmap -dump - Quick exit: Use
-XX:+ExitOnOutOfMemoryErrorto quickly exit unhealthy processes - Service deregistration: Use
-XX:OnOutOfMemoryErrorto execute deregistration script
2.7.2. How to Diagnose Java Heap OOM Without HeapDumpOnOutOfMemoryError#
Step 1: Monitor heap usage trends
- Use monitoring tools (Prometheus, Grafana, etc.) to track heap usage
- Set alerts: Trigger when heap usage exceeds threshold (e.g., 80%)
Step 2: Proactive dump before OOM
- When heap usage approaches limit, manually trigger dump:
jmap -dump:live,format=b,file=/path/to/dump.hprof <pid> - Or use JMX remote trigger dump
Step 3: Analyze dump file
- Use MAT (Memory Analyzer Tool) or similar tools to analyze dump file
- Identify memory leak sources: Large objects, retained objects, etc.
Step 4: Quick exit on OOM
- Configure
-XX:+ExitOnOutOfMemoryErrorto quickly exit on OOM - Configure
-XX:OnOutOfMemoryError=/path/to/deregister.shto execute deregistration script
Advantages:
- ✅ No blocking of OOM handling flow
- ✅ Dump at optimal timing (before OOM, heap state clearer)
- ✅ No disk I/O performance impact
- ✅ Service can deregister quickly
2.7.3. Testing/Development Environment Usage#
Acceptable in testing/development environments:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dumps/-XX:HeapDumpGzipLevel=1(fast compression, saves disk space)
Notes:
- Ensure sufficient disk space
- Regularly clean old dump files
- Don’t use in performance testing (affects test results)
3. OutOfMemoryError Handling Parameters#
3.1. OnOutOfMemoryError#
3.1.1. Parameter Description#
Description: Execute user-defined command or script when Java heap OOM occurs.
Default: "" (empty string)
Type: product (JDK 11, 17, 21, 25)
Example: -XX:OnOutOfMemoryError="/path/to/script.sh %p"
3.1.2. Execution Timing#
OnOutOfMemoryError executes after HeapDumpOnOutOfMemoryError (if enabled):
- OOM detected →
report_java_out_of_memory() - If
HeapDumpOnOutOfMemoryErrorenabled → Execute heap dump - Execute
OnOutOfMemoryErrorcommand - Check
CrashOnOutOfMemoryError/ExitOnOutOfMemoryError
3.1.3. Implementation Mechanism#
jdk-jdk-11-28/src/hotspot/share/utilities/debug.cpp
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
if (Atomic::cmpxchg(1, &out_of_memory_reported, 0) == 0) {
// ... HeapDumpOnOutOfMemoryError handling ...
if (OnOutOfMemoryError && OnOutOfMemoryError[0]) {
// Execute user-defined command
VMError::report_java_out_of_memory(message);
}
// ... CrashOnOutOfMemoryError / ExitOnOutOfMemoryError handling ...
}
}
jdk-jdk-11-28/src/hotspot/share/utilities/vmError.cpp
void VMError::report_java_out_of_memory(const char* message) {
// Expand %p placeholder in command
char cmd[O_BUFLEN];
jio_snprintf(cmd, sizeof(cmd), "%s", OnOutOfMemoryError);
// Execute command
char* cmd_line = cmd;
while (*cmd_line != '\0') {
if (*cmd_line == '%' && *(cmd_line + 1) == 'p') {
// Replace %p with process ID
char pid_str[32];
jio_snprintf(pid_str, sizeof(pid_str), "%d", os::current_process_id());
// ... String replacement logic ...
}
cmd_line++;
}
// Execute command via os::fork_and_exec or similar
os::fork_and_exec(cmd);
}
Key Points:
- Supports
%pplaceholder, replaced with current process ID - Command executed in separate process, doesn’t block JVM exit
- If command execution fails, doesn’t affect subsequent OOM handling
3.1.4. Usage Examples#
Example 1: Service deregistration script
-XX:OnOutOfMemoryError="/opt/scripts/deregister.sh %p"
deregister.sh content:
#!/bin/bash
PID=$1
echo "Process $PID encountered OOM, deregistering from service registry..."
# Call service registry API to deregister
curl -X DELETE http://registry:8500/v1/agent/service/deregister/my-service-$PID
Example 2: Send alert notification
-XX:OnOutOfMemoryError="/opt/scripts/alert.sh %p 'Java heap OOM'"
Example 3: Collect diagnostic information
-XX:OnOutOfMemoryError="/opt/scripts/collect-info.sh %p"
collect-info.sh content:
#!/bin/bash
PID=$1
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="/var/log/oom-diagnostics/$PID-$TIMESTAMP"
mkdir -p $OUTPUT_DIR
# Collect thread dump
jstack $PID > $OUTPUT_DIR/thread-dump.txt
# Collect GC log (if available)
cp /var/log/gc-$PID.log $OUTPUT_DIR/ 2>/dev/null
# Record system info
top -b -n 1 > $OUTPUT_DIR/top.txt
free -h > $OUTPUT_DIR/memory.txt
3.1.5. Best Practices#
Recommended:
- ✅ Use for service deregistration (quickly remove unhealthy instances)
- ✅ Use for alert notifications (notify operations team)
- ✅ Keep script simple and fast (avoid long-running operations)
- ✅ Ensure script has execute permissions
Not Recommended:
- ❌ Don’t attempt to “fix” OOM in script (process already unhealthy)
- ❌ Don’t perform complex operations (may fail due to resource constraints)
- ❌ Don’t rely on script to prevent process exit (use with
ExitOnOutOfMemoryError)
3.2. CrashOnOutOfMemoryError#
3.2.1. Parameter Description#
Description: Trigger JVM crash (generate core dump and hs_err log) when Java heap OOM occurs.
Default: false
Type: product (JDK 11, 17, 21, 25)
Example: -XX:+CrashOnOutOfMemoryError
3.2.2. Implementation Mechanism#
JDK 11:
jdk-jdk-11-28/src/hotspot/share/utilities/debug.cpp
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
if (Atomic::cmpxchg(1, &out_of_memory_reported, 0) == 0) {
// ... HeapDumpOnOutOfMemoryError handling ...
// ... OnOutOfMemoryError handling ...
if (CrashOnOutOfMemoryError) {
// Trigger fatal error, generate hs_err log and core dump
fatal("OutOfMemory encountered: %s", message);
}
if (ExitOnOutOfMemoryError) {
os::exit(3);
}
}
}
JDK 17+:
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
if (Atomic::cmpxchg(&out_of_memory_reported, 0, 1) == 0) {
// ... HeapDumpOnOutOfMemoryError handling ...
// ... OnOutOfMemoryError handling ...
if (CrashOnOutOfMemoryError) {
// JDK 17+: Use report_fatal instead of fatal
report_fatal(OOM_JAVA_HEAP_FATAL, __FILE__, __LINE__,
"OutOfMemory encountered: %s", message);
}
if (ExitOnOutOfMemoryError) {
os::exit(3); // JDK 17
// os::_exit(3); // JDK 21+
}
}
}
Crash behavior:
- Generates
hs_err_pid<pid>.logfile (error log) - Generates core dump file (if
CreateCoredumpOnCrashenabled) - JVM process terminates with signal (e.g., SIGABRT)
3.2.3. Usage Scenarios#
Suitable for:
- Development/testing environments: Need complete crash information for debugging
- Critical systems: Need to preserve complete scene for post-mortem analysis
- Automated restart systems: Rely on process crash to trigger restart
Not suitable for:
- Production environments with high availability requirements (crash causes service interruption)
- Systems without automated restart mechanisms (manual intervention required)
- Disk space limited environments (core dump files can be very large)
3.2.4. Comparison with ExitOnOutOfMemoryError#
| Feature | CrashOnOutOfMemoryError | ExitOnOutOfMemoryError |
|---|---|---|
| Exit method | Crash (signal) | Normal exit (exit code 3) |
| hs_err log | ✅ Generated | ❌ Not generated |
| Core dump | ✅ Generated (if enabled) | ❌ Not generated |
| Exit speed | Slower (generates logs) | Faster |
| Diagnostic info | Complete | Minimal |
| Suitable scenarios | Debugging, post-mortem analysis | Quick exit, high availability |
3.3. ExitOnOutOfMemoryError#
3.3.1. Parameter Description#
Description: Quickly exit JVM (exit code 3) when Java heap OOM occurs.
Default: false
Type: product (JDK 11, 17, 21, 25)
Example: -XX:+ExitOnOutOfMemoryError
3.3.2. Implementation Mechanism#
JDK 11-17:
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
if (Atomic::cmpxchg(&out_of_memory_reported, 0, 1) == 0) {
// ... HeapDumpOnOutOfMemoryError handling ...
// ... OnOutOfMemoryError handling ...
// ... CrashOnOutOfMemoryError handling ...
if (ExitOnOutOfMemoryError) {
// Normal exit, exit code 3
os::exit(3);
}
}
}
JDK 21+:
void report_java_out_of_memory(const char* message) {
static int out_of_memory_reported = 0;
if (Atomic::cmpxchg(&out_of_memory_reported, 0, 1) == 0) {
// ... HeapDumpOnOutOfMemoryError handling ...
// ... OnOutOfMemoryError handling ...
// ... CrashOnOutOfMemoryError handling ...
if (ExitOnOutOfMemoryError) {
// JDK 21+: Use _exit for fast exit, doesn't run cleanup hooks
os::_exit(3);
}
}
}
Version differences:
- JDK 11-17: Uses
os::exit(3), runs shutdown hooks and finalizers - JDK 21+: Uses
os::_exit(3), fast exit without running cleanup hooks
3.3.3. Exit Code#
Exit code is 3, can be used in automated scripts to identify OOM exit:
#!/bin/bash
java -XX:+ExitOnOutOfMemoryError -jar app.jar
EXIT_CODE=$?
if [ $EXIT_CODE -eq 3 ]; then
echo "Application exited due to OutOfMemoryError"
# Trigger alert or automated handling
fi
3.3.4. Best Practices#
Recommended usage:
-XX:+ExitOnOutOfMemoryError \
-XX:OnOutOfMemoryError="/opt/scripts/deregister.sh %p"
Execution flow:
- OOM detected
- Execute
OnOutOfMemoryErrorscript (service deregistration) - Execute
ExitOnOutOfMemoryError(process exits) - Container orchestration system (K8s, etc.) detects exit and restarts container
Advantages:
- ✅ Quickly exits unhealthy process
- ✅ Avoids continued operation causing more problems
- ✅ Cooperates with orchestration system for automated recovery
- ✅ Exit code 3 clearly identifies OOM cause
3.3.5. Combination with Orchestration Systems#
Kubernetes example:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:latest
env:
- name: JAVA_OPTS
value: "-XX:+ExitOnOutOfMemoryError -XX:OnOutOfMemoryError='/scripts/deregister.sh %p'"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
restartPolicy: Always # Automatically restart on exit
Docker Compose example:
version: '3'
services:
app:
image: my-app:latest
environment:
- JAVA_OPTS=-XX:+ExitOnOutOfMemoryError
restart: always # Automatically restart on exit
4. Error Logging and Diagnostic Parameters#
4.1. ErrorFile#
4.1.1. Parameter Description#
Description: Specifies error log file path (hs_err log).
Default: NULL (default is hs_err_pid<pid>.log in current directory)
Type: product (JDK 11, 17, 21, 25)
Example: -XX:ErrorFile=/var/log/java/hs_err_pid%p.log
4.1.2. File Naming#
- Supports
%pplaceholder, replaced with process ID - If path is directory, automatically appends default filename
- If file creation fails, attempts to create in
/tmpor current directory
4.1.3. hs_err Log Content#
hs_err log contains:
- Error summary: Error type, message, thread info
- Thread state: All thread stack traces
- Heap info: Heap usage, GC statistics
- Native memory: Native memory usage
- System info: OS version, CPU info, memory info
- VM arguments: All JVM parameters
- Dynamic libraries: Loaded dynamic library list
- Compilation events: Recent JIT compilation events
- GC events: Recent GC events
4.1.4. Best Practices#
Production environment recommendations:
-XX:ErrorFile=/var/log/java/hs_err_pid%p_%t.log
%p: Process ID%t: Timestamp (JDK 17+)
Ensure:
- Log directory has write permissions
- Sufficient disk space
- Regularly clean old log files
4.2. CreateCoredumpOnCrash#
4.2.1. Parameter Description#
Description: Whether to create core dump file on fatal error.
Default: true
Type: product (JDK 11, 17, 21, 25)
Example:
-XX:+CreateCoredumpOnCrash(enable, default)-XX:-CreateCoredumpOnCrash(disable)
4.2.2. Core Dump File#
File location:
- Linux: Current directory or
/cores(depends on system configuration) - Check system configuration:
cat /proc/sys/kernel/core_pattern
File size:
- Can be very large (equals process memory usage)
- Limit core dump size:
ulimit -c <size>orulimit -c unlimited
4.2.3. Usage Scenarios#
Enable core dump (-XX:+CreateCoredumpOnCrash):
- Development/testing environments
- Need to debug native code issues
- Need complete memory snapshot for post-mortem analysis
Disable core dump (-XX:-CreateCoredumpOnCrash):
- Production environments (saves disk space)
- Disk space limited
- Security concerns (core dump may contain sensitive data)
4.2.4. Core Dump Analysis#
Use GDB to analyze:
gdb $JAVA_HOME/bin/java core.<pid>
# In GDB
(gdb) bt # View call stack
(gdb) info threads # View all threads
(gdb) thread <n> # Switch to thread n
(gdb) bt # View thread n's call stack
Use jstack to analyze:
jstack $JAVA_HOME/bin/java core.<pid>
4.3. OnError#
4.3.1. Parameter Description#
Description: Execute user-defined command on fatal error (not just OOM).
Default: "" (empty string)
Type: product (JDK 11, 17, 21, 25)
Example: -XX:OnError="/opt/scripts/on-error.sh %p"
4.3.2. Difference from OnOutOfMemoryError#
| Feature | OnError | OnOutOfMemoryError |
|---|---|---|
| Trigger condition | Any fatal error | Only Java heap OOM |
| Trigger scenarios | Segfault, assertion failure, etc. | OutOfMemoryError |
| Execution timing | During error handling | During OOM handling |
| Use cases | General error handling | OOM-specific handling |
4.3.3. Usage Examples#
Example: Collect crash information:
-XX:OnError="/opt/scripts/collect-crash-info.sh %p"
collect-crash-info.sh:
#!/bin/bash
PID=$1
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="/var/log/crash-diagnostics/$PID-$TIMESTAMP"
mkdir -p $OUTPUT_DIR
# Copy hs_err log
cp hs_err_pid$PID.log $OUTPUT_DIR/ 2>/dev/null
# Copy core dump (if exists)
cp core.$PID $OUTPUT_DIR/ 2>/dev/null
# Record system info
uname -a > $OUTPUT_DIR/system-info.txt
free -h > $OUTPUT_DIR/memory.txt
4.4. Other Diagnostic Parameters#
4.4.1. ShowMessageBoxOnError#
Description: Show message box on fatal error (Windows only, rarely used).
Default: false
Note: Mainly for debugging, not suitable for production environments.
4.4.2. SuppressFatalErrorMessage#
Description: Suppress fatal error messages.
Default: false
Note: Not recommended, makes debugging difficult.
4.4.3. ErrorLogTimeout#
Description: Error log write timeout (seconds).
Default: 120 (2 minutes)
Purpose: Prevent error log writing from hanging indefinitely.
5. Other Diagnostic Parameters#
5.1. MaxJavaStackTraceDepth#
5.1.1. Parameter Description#
Description: Maximum depth of Java exception stack traces.
Default: 1024
Type: product (JDK 11, 17, 21, 25)
Example: -XX:MaxJavaStackTraceDepth=2048
5.1.2. Impact#
- Stack trace truncation: If stack depth exceeds this value, stack trace is truncated
- Performance: Larger values may impact exception throwing performance
- Diagnostic: Smaller values may lose important call chain information
5.1.3. Best Practices#
Default value (1024) is usually sufficient, only increase if:
- Deep recursion scenarios
- Complex framework call chains
- Need complete call chain for debugging
Not recommended:
- Setting too small (e.g., < 100) - loses important information
- Setting too large (e.g., > 10000) - impacts performance
5.2. SelfDestructTimer#
5.2.1. Parameter Description#
Description: Self-destruct timer (minutes), for testing purposes.
Default: 0 (disabled)
Type: diagnostic (JDK 11, 17, 21, 25)
Example: -XX:SelfDestructTimer=60 (self-destruct after 60 minutes)
5.2.2. Purpose#
Testing purposes only:
- Test automated restart mechanisms
- Test monitoring and alerting systems
- Simulate process crashes
Not for production use: May cause unexpected process termination.
6. Summary and Recommendations#
6.1. Production Environment Best Practices#
Recommended configuration:
# OOM handling: Quick exit + service deregistration
-XX:+ExitOnOutOfMemoryError \
-XX:OnOutOfMemoryError="/opt/scripts/deregister.sh %p" \
# Error logging: Specify log path
-XX:ErrorFile=/var/log/java/hs_err_pid%p.log \
# Disable core dump (save disk space)
-XX:-CreateCoredumpOnCrash \
# Do NOT enable heap dump
# -XX:-HeapDumpOnOutOfMemoryError
Monitoring and alerting:
- Monitor heap usage trends via JMX
- Set alerts: Trigger when heap usage exceeds 80%
- Proactively dump before OOM:
jmap -dump:live,format=b,file=dump.hprof <pid>
6.2. Development/Testing Environment Configuration#
Recommended configuration:
# OOM handling: Heap dump + crash
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/dumps/ \
-XX:HeapDumpGzipLevel=1 \
-XX:+CrashOnOutOfMemoryError \
# Error logging
-XX:ErrorFile=/tmp/hs_err_pid%p.log \
# Enable core dump
-XX:+CreateCoredumpOnCrash
6.3. Key Takeaways#
- Don’t enable HeapDumpOnOutOfMemoryError in production - Blocks OOM handling, long dump time
- Use ExitOnOutOfMemoryError for quick exit - Cooperate with orchestration systems for automated recovery
- Use OnOutOfMemoryError for service deregistration - Quickly remove unhealthy instances
- Proactive monitoring and dumping - Dump before OOM, clearer heap state
- Understand version differences - JDK 21+ uses
_exitfor faster exit, JDK 25+ supports parallel dump
6.4. Diagnostic Workflow#
When OOM occurs:
- Monitoring system detects heap usage approaching limit
- Proactive dump via
jmapor JMX - Analyze dump file using MAT or similar tools
- If OOM occurs →
OnOutOfMemoryErrorscript executes (service deregistration) - Process exits →
ExitOnOutOfMemoryErrortriggers quick exit - Orchestration system detects exit and restarts container
- Post-mortem analysis using dump file and hs_err log
When other fatal errors occur:
- OnError script executes (collect crash information)
- hs_err log generated (error details)
- Core dump generated (if enabled)
- Process crashes (if CrashOnOutOfMemoryError enabled)
- Post-mortem analysis using hs_err log and core dump
This concludes the comprehensive analysis of JVM error handling and diagnostic parameters. Understanding these mechanisms helps build more robust and diagnosable Java applications.
