Skip to content

Commit

Permalink
jcmd GC.heap_dump without options should write to location given by -…
Browse files Browse the repository at this point in the history
…XX:HeapDumpPath, if set (#1671)
  • Loading branch information
MBaesken authored Jun 11, 2024
1 parent 7e1d4d1 commit ab72d4e
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 83 deletions.
8 changes: 5 additions & 3 deletions src/hotspot/share/runtime/globals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -699,11 +699,13 @@ const int ObjectAlignmentInBytes = 8;
"from JVM " \
"(also see HeapDumpPath, HeapDumpGzipLevel)") \
\
/* SapMachine 2024-05-10: HeapDumpPath for jcmd */ \
product(ccstr, HeapDumpPath, nullptr, MANAGEABLE, \
"When HeapDumpOnOutOfMemoryError, HeapDumpBeforeFullGC " \
"or HeapDumpAfterFullGC is on, the path (filename or " \
"directory) of the dump file (defaults to java_pid<pid>.hprof " \
"in the working directory)") \
"or HeapDumpAfterFullGC is on, or a heap dump is triggered by " \
"jcmd GC.heap_dump without specifying a path, the path " \
"(filename or directory) of the dump file. " \
"(defaults to java_pid<pid>.hprof in the working directory)") \
\
product(int, HeapDumpGzipLevel, 0, MANAGEABLE, \
"When HeapDumpOnOutOfMemoryError, HeapDumpBeforeFullGC " \
Expand Down
24 changes: 22 additions & 2 deletions src/hotspot/share/services/diagnosticCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,10 @@ void FinalizerInfoDCmd::execute(DCmdSource source, TRAPS) {
}

#if INCLUDE_SERVICES // Heap dumping/inspection supported
// SapMachine 2024-05-10: HeapDumpPath for jcmd
HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
_filename("filename","Name of the dump file", "STRING",true),
_filename("filename","Name of the dump file", "STRING", false, "if no filename was specified, but -XX:HeapDumpPath=hdp is set, path hdp is taken"),
_all("-all", "Dump all objects, including unreachable objects",
"BOOLEAN", false, "false"),
_gzip("-gz", "If specified, the heap dump is written in gzipped format "
Expand All @@ -514,6 +515,8 @@ HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
jlong level = -1; // -1 means no compression.
jlong parallel = HeapDumper::default_num_of_dump_threads();
// SapMachine 2024-05-10: HeapDumpPath for jcmd
bool use_heapdump_path = false;

if (_gzip.is_set()) {
level = _gzip.value();
Expand All @@ -536,11 +539,28 @@ void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
}
}

// SapMachine 2024-05-10: HeapDumpPath for jcmd
if (!_filename.is_set()) {
if (HeapDumpPath != nullptr) {
// use HeapDumpPath (file or directory is possible)
use_heapdump_path = true;
} else {
output()->print_cr("Filename or -XX:HeapDumpPath must be set!");
return;
}
}

// Request a full GC before heap dump if _all is false
// This helps reduces the amount of unreachable objects in the dump
// and makes it easier to browse.
HeapDumper dumper(!_all.value() /* request GC if _all is false*/);
dumper.dump(_filename.value(), output(), (int) level, _overwrite.value(), (uint)parallel);

// SapMachine 2024-05-10: HeapDumpPath for jcmd
if (use_heapdump_path) {
dumper.dump_to(output(), (int)level, _overwrite.value(), (uint)parallel);
} else {
dumper.dump(_filename.value(), output(), (int)level, _overwrite.value(), (uint)parallel);
}
}

ClassHistogramDCmd::ClassHistogramDCmd(outputStream* output, bool heap) :
Expand Down
174 changes: 100 additions & 74 deletions src/hotspot/share/services/heapDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2595,6 +2595,99 @@ void VM_HeapDumper::dump_vthread(oop vt, AbstractDumpWriter* segment_writer) {
thread_dumper.dump_stack_refs(segment_writer);
}

// SapMachine 2024-05-10: HeapDumpPath for jcmd
static uint dump_file_seq = 0;

// helper function to create the heap dump path name
// the caller must free the returned pointer
static char* alloc_and_create_heapdump_pathname() {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
char* my_path;
const int max_digit_chars = 20;

const char* dump_file_name = "java_pid";
const char* dump_file_ext = HeapDumpGzipLevel > 0 ? ".hprof.gz" : ".hprof";

// The dump file defaults to java_pid<pid>.hprof in the current working
// directory. HeapDumpPath=<file> can be used to specify an alternative
// dump file name or a directory where dump file is created.
if (dump_file_seq == 0) { // first time in, we initialize base_path
// Calculate potentially longest base path and check if we have enough
// allocated statically.
const size_t total_length =
(HeapDumpPath == nullptr ? 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 nullptr;
}

bool use_default_filename = true;
if (HeapDumpPath == nullptr || HeapDumpPath[0] == '\0') {
// HeapDumpPath=<file> not specified
} else {
strcpy(base_path, HeapDumpPath);
// check if the path is a directory (must exist)
DIR* dir = os::opendir(base_path);
if (dir == nullptr) {
use_default_filename = false;
} else {
// HeapDumpPath specified a directory. We append a 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 wasn't a file name then we append the default name
if (use_default_filename) {
const size_t dlen = strlen(base_path); // if heap dump dir specified
jio_snprintf(&base_path[dlen], sizeof(base_path)-dlen, "%s%d%s",
dump_file_name, os::current_process_id(), dump_file_ext);
}
const size_t len = strlen(base_path) + 1;
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == nullptr) {
warning("Cannot create heap dump file. Out of system memory.");
return nullptr;
}
strncpy(my_path, base_path, len);
} else {
// Append a sequence number id for dumps following the first
const size_t len = strlen(base_path) + max_digit_chars + 2; // for '.' and \0
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == nullptr) {
warning("Cannot create heap dump file. Out of system memory.");
return nullptr;
}
jio_snprintf(my_path, len, "%s.%d", base_path, dump_file_seq);
}
dump_file_seq++; // increment seq number for next time we dump
return my_path;
}


int HeapDumper::dump_to(outputStream* out, int compression, bool overwrite, uint num_dump_threads) {
if (HeapDumpPath == nullptr || HeapDumpPath[0] == '\0') {
return -1;
}
// SapMachine 2024-05-10: HeapDumpPath for jcmd
char* h_path = alloc_and_create_heapdump_pathname();
if (h_path != nullptr) {
int res = dump(h_path, out, compression, overwrite, num_dump_threads);
os::free(h_path);
return res;
}
return -1;
}

// dump the heap to given path.
int HeapDumper::dump(const char* path, outputStream* out, int compression, bool overwrite, uint num_dump_threads) {
assert(path != nullptr && strlen(path) > 0, "path missing");
Expand Down Expand Up @@ -2744,79 +2837,12 @@ void HeapDumper::dump_heap() {
}

void HeapDumper::dump_heap(bool oome) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
char* my_path;
const int max_digit_chars = 20;

const char* dump_file_name = "java_pid";
const char* dump_file_ext = HeapDumpGzipLevel > 0 ? ".hprof.gz" : ".hprof";

// The dump file defaults to java_pid<pid>.hprof in the current working
// directory. HeapDumpPath=<file> can be used to specify an alternative
// dump file name or a directory where dump file is created.
if (dump_file_seq == 0) { // first time in, we initialize base_path
// Calculate potentially longest base path and check if we have enough
// allocated statically.
const size_t total_length =
(HeapDumpPath == nullptr ? 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 == nullptr || HeapDumpPath[0] == '\0') {
// HeapDumpPath=<file> not specified
} else {
strcpy(base_path, HeapDumpPath);
// check if the path is a directory (must exist)
DIR* dir = os::opendir(base_path);
if (dir == nullptr) {
use_default_filename = false;
} else {
// HeapDumpPath specified a directory. We append a 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 wasn't a file name then we append the default name
if (use_default_filename) {
const size_t dlen = strlen(base_path); // if heap dump dir specified
jio_snprintf(&base_path[dlen], sizeof(base_path)-dlen, "%s%d%s",
dump_file_name, os::current_process_id(), dump_file_ext);
}
const size_t len = strlen(base_path) + 1;
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == nullptr) {
warning("Cannot create heap dump file. Out of system memory.");
return;
}
strncpy(my_path, base_path, len);
} else {
// Append a sequence number id for dumps following the first
const size_t len = strlen(base_path) + max_digit_chars + 2; // for '.' and \0
my_path = (char*)os::malloc(len, mtInternal);
if (my_path == nullptr) {
warning("Cannot create heap dump file. Out of system memory.");
return;
}
jio_snprintf(my_path, len, "%s.%d", base_path, dump_file_seq);
// SapMachine 2024-05-10: HeapDumpPath for jcmd
char* h_path = alloc_and_create_heapdump_pathname();
if (h_path != nullptr) {
HeapDumper dumper(false /* no GC before heap dump */,
oome /* pass along out-of-memory-error flag */);
dumper.dump(h_path, tty, HeapDumpGzipLevel);
os::free(h_path);
}
dump_file_seq++; // increment seq number for next time we dump

HeapDumper dumper(false /* no GC before heap dump */,
oome /* pass along out-of-memory-error flag */);
dumper.dump(my_path, tty, HeapDumpGzipLevel);
os::free(my_path);
}
3 changes: 3 additions & 0 deletions src/hotspot/share/services/heapDumper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class HeapDumper : public StackObj {
// parallel_thread_num >= 0 indicates thread numbers of parallel object dump.
int dump(const char* path, outputStream* out = nullptr, int compression = -1, bool overwrite = false, uint parallel_thread_num = default_num_of_dump_threads());

// same as dump with path parameter, but uses the preset HeapDumpPath file or directory
int dump_to(outputStream* out, int compression, bool overwrite, uint parallel_thread_num);

// returns error message (resource allocated), or null if no error
char* error_as_C_string() const;

Expand Down
6 changes: 3 additions & 3 deletions src/java.base/share/man/java.1
Original file line number Diff line number Diff line change
Expand Up @@ -2799,9 +2799,9 @@ an \f[V]OutOfMemoryError\f[R] exception is thrown.
\f[V]-XX:HeapDumpPath=\f[R]\f[I]path\f[R]
Sets the path and file name for writing the heap dump provided by the
heap profiler (HPROF) when the \f[V]-XX:+HeapDumpOnOutOfMemoryError\f[R]
option is set.
By default, the file is created in the current working directory, and
it\[aq]s named \f[V]java_pid<pid>.hprof\f[R] where \f[V]<pid>\f[R] is
option is set or a heap dump is triggered by \f[V]jcmd GC.heap_dump\f[R] without specifying a path.
In case of HeapDumpOnOutOfMemoryError, by default, the file is created in the current
working directory, and it\[aq]s named \f[V]java_pid<pid>.hprof\f[R] where \f[V]<pid>\f[R] is
the identifier of the process that caused the error.
The following example shows how to set the default file explicitly
(\f[V]%p\f[R] represents the current process identifier):
Expand Down
4 changes: 3 additions & 1 deletion src/jdk.jcmd/share/man/jcmd.1
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ fewer.
.PP
\f[I]arguments\f[R]:
.IP \[bu] 2
\f[I]filename\f[R]: The name of the dump file (STRING, no default value)
\f[I]filename\f[R]: The name of the dump file; in case no file is specified
but -XX:HeapDumpPath=path is set, the path provided by HeapDumpPath is used
(STRING, no default value)
.RE
.TP
\f[V]GC.heap_info\f[R]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/**
* @test
* @summary Test jcmd GC.heap_dump without path option; path is set via HeapDumpPath
* @library /test/lib
* @run main/othervm -XX:HeapDumpPath=testjcmd.hprof HeapDumpJcmdPresetPathTest
*/

import java.io.File;

import jdk.test.lib.Asserts;
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.process.OutputAnalyzer;

public class HeapDumpJcmdPresetPathTest {

public static void main(String[] args) throws Exception {
PidJcmdExecutor executor = new PidJcmdExecutor();
OutputAnalyzer output = executor.execute("GC.heap_dump");
output.shouldContain("Dumping heap to testjcmd.hprof");
output.shouldContain("Heap dump file created");

Asserts.assertTrue(new File("testjcmd.hprof").exists());
}
}

0 comments on commit ab72d4e

Please sign in to comment.