8318636: Add jcmd to print annotated process memory map

Reviewed-by: jsjolen, gziemski
This commit is contained in:
Thomas Stuefe 2023-11-13 08:26:42 +00:00
parent e035637a4c
commit 6f863b2a1b
9 changed files with 792 additions and 1 deletions

@ -0,0 +1,84 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*
*/
#include "precompiled.hpp"
#include "runtime/os.hpp"
#include "nmt/memMapPrinter.hpp"
#include "utilities/globalDefinitions.hpp"
#include <limits.h>
struct ProcMapsInfo {
void* from = 0;
void* to = 0;
char prot[20 + 1];
char offset[20 + 1];
char dev[20 + 1];
char inode[20 + 1];
char filename[1024 + 1];
bool scan_proc_maps_line(const char* line) {
prot[0] = offset[0] = dev[0] = inode[0] = filename[0] = '\0';
const int items_read = ::sscanf(line, "%p-%p %20s %20s %20s %20s %1024s",
&from, &to, prot, offset, dev, inode, filename);
return items_read >= 2; // need at least from and to
}
};
class LinuxMappingPrintInformation : public MappingPrintInformation {
const ProcMapsInfo _info;
public:
LinuxMappingPrintInformation(const void* from, const void* to, const ProcMapsInfo* info) :
MappingPrintInformation(from, to), _info(*info) {}
void print_OS_specific_details(outputStream* st) const override {
st->print("%s %s ", _info.prot, _info.offset);
}
const char* filename() const override { return _info.filename; }
};
void MemMapPrinter::pd_print_header(outputStream* st) {
st->print_cr("size prot offset What");
}
void MemMapPrinter::pd_iterate_all_mappings(MappingPrintClosure& closure) {
FILE* f = os::fopen("/proc/self/maps", "r");
if (f == nullptr) {
return;
}
constexpr size_t linesize = sizeof(ProcMapsInfo);
char line[linesize];
while (fgets(line, sizeof(line), f) == line) {
line[sizeof(line) - 1] = '\0';
ProcMapsInfo info;
if (info.scan_proc_maps_line(line)) {
LinuxMappingPrintInformation mapinfo(info.from, info.to, &info);
closure.do_it(&mapinfo);
}
}
::fclose(f);
}

@ -178,7 +178,6 @@ class AsyncLogWriter : public NonJavaThread {
NonJavaThread::pre_run();
log_debug(logging, thread)("starting AsyncLog Thread tid = " INTX_FORMAT, os::current_thread_id());
}
const char* name() const override { return "AsyncLog Thread"; }
const char* type_name() const override { return "AsyncLogWriter"; }
void print_on(outputStream* st) const override {
st->print("\"%s\" ", name());
@ -203,6 +202,8 @@ class AsyncLogWriter : public NonJavaThread {
static AsyncLogWriter* instance();
static void initialize();
static void flush();
const char* name() const override { return "AsyncLog Thread"; }
};
#endif // SHARE_LOGGING_LOGASYNCWRITER_HPP

@ -0,0 +1,54 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. 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.
*
*/
#ifndef SHARE_NMT_MEMFLAGBITMAP_HPP
#define SHARE_NMT_MEMFLAGBITMAP_HPP
#include "memory/allocation.hpp" // for mt_number_of_types
#include "utilities/globalDefinitions.hpp"
class MemFlagBitmap {
uint32_t _v;
STATIC_ASSERT(sizeof(_v) * BitsPerByte >= mt_number_of_types);
public:
MemFlagBitmap(uint32_t v = 0) : _v(v) {}
MemFlagBitmap(const MemFlagBitmap& o) : _v(o._v) {}
uint32_t raw_value() const { return _v; }
void set_flag(MEMFLAGS f) {
const int bitno = (int)f;
_v |= nth_bit(bitno);
}
bool has_flag(MEMFLAGS f) const {
const int bitno = (int)f;
return _v & nth_bit(bitno);
}
bool has_any() const { return _v > 0; }
};
#endif // SHARE_NMT_NMTUSAGE_HPP

@ -0,0 +1,309 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*
*/
#include "precompiled.hpp"
#ifdef LINUX
#include "logging/logAsyncWriter.hpp"
#include "gc/shared/collectedHeap.hpp"
#include "memory/allocation.hpp"
#include "memory/universe.hpp"
#include "runtime/nonJavaThread.hpp"
#include "runtime/osThread.hpp"
#include "runtime/thread.hpp"
#include "runtime/threadSMR.hpp"
#include "runtime/vmThread.hpp"
#include "nmt/memFlagBitmap.hpp"
#include "nmt/memMapPrinter.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/ostream.hpp"
// Note: throughout this code we will use the term "VMA" for OS system level memory mapping
/// NMT mechanics
// Short, clear, descriptive names for all possible markers. Note that we only expect to see
// those that have been used with mmap. Flags left out are printed with their nmt flag name.
#define NMT_FLAGS_DO(f) \
/* flag, short, description */ \
f(mtGCCardSet, "CARDTBL", "GC Card table") \
f(mtClassShared, "CDS", "CDS archives") \
f(mtClass, "CLASS", "Class Space") \
f(mtCode, "CODE", "Code Heap") \
f(mtGC, "GC", "GC support data (e.g. bitmaps)") \
f(mtInternal, "INTERN", "Internal") \
f(mtJavaHeap, "JAVAHEAP", "Java Heap") \
f(mtOther, "JDK", "allocated by JDK libraries other than VM") \
f(mtMetaspace, "META", "Metaspace nodes (non-class)") \
f(mtSafepoint, "POLL", "Polling pages") \
f(mtThreadStack, "STACK", "(known) Thread Stack") \
f(mtTest, "TEST", "JVM internal test mappings")
//end
static const char* get_shortname_for_nmt_flag(MEMFLAGS f) {
#define DO(flag, shortname, text) if (flag == f) return shortname;
NMT_FLAGS_DO(DO)
#undef DO
return NMTUtil::flag_to_enum_name(f);
}
/// NMT virtual memory
static bool range_intersects(const void* from1, const void* to1, const void* from2, const void* to2) {
return MAX2(from1, from2) < MIN2(to1, to2);
}
// A Cache that correlates range with MEMFLAG, optimized to be iterated quickly
// (cache friendly).
class CachedNMTInformation : public VirtualMemoryWalker {
struct Range { const void* from; const void* to; };
// We keep ranges apart from flags since that prevents the padding a combined
// structure would have, and it allows for faster iteration of ranges since more
// of them fit into a cache line.
Range* _ranges;
MEMFLAGS* _flags;
uintx _count, _capacity;
public:
CachedNMTInformation() : _ranges(nullptr), _flags(nullptr), _count(0), _capacity(0) {}
~CachedNMTInformation() {
ALLOW_C_FUNCTION(free, ::free(_ranges);)
ALLOW_C_FUNCTION(free, ::free(_flags);)
}
bool add(const void* from, const void* to, MEMFLAGS f) {
// We rely on NMT regions being sorted by base
assert(_count == 0 || (from >= _ranges[_count - 1].to), "NMT regions unordered?");
// we can just fold two regions if they are adjacent and have the same flag.
if (_count > 0 && from == _ranges[_count - 1].to && f == _flags[_count - 1]) {
_ranges[_count - 1].to = to;
return true;
}
if (_count == _capacity) {
// Enlarge if needed
const uintx new_capacity = MAX2((uintx)4096, 2 * _capacity);
// Unfortunately, we need to allocate manually, raw, since we must prevent NMT deadlocks (ThreadCritical).
ALLOW_C_FUNCTION(realloc, _ranges = (Range*)::realloc(_ranges, new_capacity * sizeof(Range));)
ALLOW_C_FUNCTION(realloc, _flags = (MEMFLAGS*)::realloc(_flags, new_capacity * sizeof(MEMFLAGS));)
if (_ranges == nullptr || _flags == nullptr) {
// In case of OOM lets make no fuzz. Just return.
return false;
}
_capacity = new_capacity;
}
assert(_capacity > _count, "Sanity");
_ranges[_count] = Range { from, to };
_flags[_count] = f;
_count++;
return true;
}
// Given a vma [from, to), find all regions that intersect with this vma and
// return their collective flags.
MemFlagBitmap lookup(const void* from, const void* to) const {
MemFlagBitmap bm;
for(uintx i = 0; i < _count; i++) {
if (range_intersects(from, to, _ranges[i].from, _ranges[i].to)) {
bm.set_flag(_flags[i]);
} else if (from < _ranges[i].to) {
break;
}
}
return bm;
}
bool do_allocation_site(const ReservedMemoryRegion* rgn) override {
// Cancel iteration if we run out of memory (add returns false);
return add(rgn->base(), rgn->end(), rgn->flag());
}
// Iterate all NMT virtual memory regions and fill this cache.
bool fill_from_nmt() {
return VirtualMemoryTracker::walk_virtual_memory(this);
}
};
/////// Thread information //////////////////////////
// Given a VMA [from, to) and a thread, check if vma intersects with thread stack
static bool vma_touches_thread_stack(const void* from, const void* to, const Thread* t) {
// Java thread stacks (and sometimes also other threads) have guard pages. Therefore they typically occupy
// at least two distinct neighboring VMAs. Therefore we typically have a 1:n relationshipt between thread
// stack and vma.
// Very rarely however is a VMA backing a thread stack folded together with another adjacent VMA by the
// kernel. That can happen, e.g., for non-java threads that don't have guard pages.
// Therefore we go for the simplest way here and check for intersection between VMA and thread stack.
return range_intersects(from, to, (const void*)t->stack_end(), (const void*)t->stack_base());
}
struct GCThreadClosure : public ThreadClosure {
bool _found;
uintx _tid;
const void* const _from;
const void* const _to;
GCThreadClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {}
void do_thread(Thread* t) override {
if (_tid == 0 && t != nullptr && vma_touches_thread_stack(_from, _to, t)) {
_found = true;
_tid = t->osthread()->thread_id();
// lemme stooop! No way to signal stop :(
}
}
};
static void print_thread_details(uintx thread_id, const char* name, outputStream* st) {
st->print("(" UINTX_FORMAT " \"%s\")", (uintx)thread_id, name);
}
// Given a region [from, to), if it intersects a known thread stack, print detail infos about that thread.
static void print_thread_details_for_supposed_stack_address(const void* from, const void* to, outputStream* st) {
#define HANDLE_THREAD(T) \
if (T != nullptr && vma_touches_thread_stack(from, to, T)) { \
print_thread_details((uintx)(T->osthread()->thread_id()), T->name(), st); \
return; \
}
for (JavaThreadIteratorWithHandle jtiwh; JavaThread* t = jtiwh.next(); ) {
HANDLE_THREAD(t);
}
HANDLE_THREAD(VMThread::vm_thread());
HANDLE_THREAD(WatcherThread::watcher_thread());
HANDLE_THREAD(AsyncLogWriter::instance());
#undef HANDLE_THREAD
if (Universe::heap() != nullptr) {
GCThreadClosure cl(from, to);
Universe::heap()->gc_threads_do(&cl);
if (cl._found) {
print_thread_details(cl._tid, "GC Thread", st);
}
}
}
///////////////
static void print_legend(outputStream* st) {
#define DO(flag, shortname, text) st->print_cr("%10s %s", shortname, text);
NMT_FLAGS_DO(DO)
#undef DO
}
MappingPrintClosure::MappingPrintClosure(outputStream* st, bool human_readable, const CachedNMTInformation& nmt_info) :
_out(st), _human_readable(human_readable),
_total_count(0), _total_vsize(0), _nmt_info(nmt_info)
{}
void MappingPrintClosure::do_it(const MappingPrintInformation* info) {
_total_count++;
const void* const vma_from = info->from();
const void* const vma_to = info->to();
// print from, to
_out->print(PTR_FORMAT " - " PTR_FORMAT " ", p2i(vma_from), p2i(vma_to));
const size_t size = pointer_delta(vma_to, vma_from, 1);
_total_vsize += size;
// print mapping size
if (_human_readable) {
_out->print(PROPERFMT " ", PROPERFMTARGS(size));
} else {
_out->print("%11zu", size);
}
assert(info->from() <= info->to(), "Invalid VMA");
_out->fill_to(53);
info->print_OS_specific_details(_out);
_out->fill_to(70);
// print NMT information, if available
if (MemTracker::enabled()) {
// Correlate vma region (from, to) with NMT region(s) we collected previously.
const MemFlagBitmap flags = _nmt_info.lookup(vma_from, vma_to);
if (flags.has_any()) {
for (int i = 0; i < mt_number_of_types; i++) {
const MEMFLAGS flag = (MEMFLAGS)i;
if (flags.has_flag(flag)) {
_out->print("%s", get_shortname_for_nmt_flag(flag));
if (flag == mtThreadStack) {
print_thread_details_for_supposed_stack_address(vma_from, vma_to, _out);
}
_out->print(" ");
}
}
}
}
// print file name, if available
const char* f = info->filename();
if (f != nullptr) {
_out->print_raw(f);
}
_out->cr();
}
void MemMapPrinter::print_header(outputStream* st) {
st->print(
#ifdef _LP64
// 0x0000000000000000 - 0x0000000000000000
"from to "
#else
// 0x00000000 - 0x00000000
"from to "
#endif
);
// Print platform-specific columns
pd_print_header(st);
}
void MemMapPrinter::print_all_mappings(outputStream* st, bool human_readable) {
// First collect all NMT information
CachedNMTInformation nmt_info;
nmt_info.fill_from_nmt();
st->print_cr("Memory mappings:");
if (!MemTracker::enabled()) {
st->cr();
st->print_cr(" (NMT is disabled, will not annotate mappings).");
}
st->cr();
print_legend(st);
st->print_cr("(*) - Mapping contains data from multiple regions");
st->cr();
pd_print_header(st);
MappingPrintClosure closure(st, human_readable, nmt_info);
pd_iterate_all_mappings(closure);
st->print_cr("Total: " UINTX_FORMAT " mappings with a total vsize of %zu (" PROPERFMT ")",
closure.total_count(), closure.total_vsize(), PROPERFMTARGS(closure.total_vsize()));
}
#endif // LINUX

@ -0,0 +1,75 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*
*/
#ifndef SHARE_SERVICES_MEMMAPPRINTER_HPP
#define SHARE_SERVICES_MEMMAPPRINTER_HPP
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "utilities/globalDefinitions.hpp"
#ifdef LINUX
class outputStream;
class CachedNMTInformation;
class MappingPrintInformation {
const void* const _from;
const void* const _to;
public:
MappingPrintInformation(const void* from, const void* to) : _from(from), _to(to) {}
const void* from() const { return _from; }
const void* to() const { return _to; }
// Will be called for each mapping before VM annotations are printed.
virtual void print_OS_specific_details(outputStream* st) const {}
// If mapping is backed by a file, the name of that file
virtual const char* filename() const { return nullptr; }
};
class MappingPrintClosure {
outputStream* const _out;
const bool _human_readable;
uintx _total_count;
size_t _total_vsize;
const CachedNMTInformation& _nmt_info;
public:
MappingPrintClosure(outputStream* st, bool human_readable, const CachedNMTInformation& nmt_info);
void do_it(const MappingPrintInformation* info); // returns false if timeout reached.
uintx total_count() const { return _total_count; }
size_t total_vsize() const { return _total_vsize; }
};
class MemMapPrinter : public AllStatic {
static void pd_print_header(outputStream* st);
static void print_header(outputStream* st);
static void pd_iterate_all_mappings(MappingPrintClosure& closure);
public:
static void mark_page_malloced(const void* p, MEMFLAGS f);
static void print_all_mappings(outputStream* st, bool human_readable);
};
#endif // LINUX
#endif // SHARE_SERVICES_MEMMAPPRINTER_HPP

@ -40,6 +40,8 @@
#include "memory/metaspace/metaspaceDCmd.hpp"
#include "memory/resourceArea.hpp"
#include "memory/universe.hpp"
#include "nmt/memMapPrinter.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/nmtDCmd.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/objArrayOop.inline.hpp"
@ -67,8 +69,10 @@
#include "utilities/macros.hpp"
#include "utilities/parseInteger.hpp"
#ifdef LINUX
#include "os_posix.hpp"
#include "mallocInfoDcmd.hpp"
#include "trimCHeapDCmd.hpp"
#include <errno.h>
#endif
static void loadAgentModule(TRAPS) {
@ -133,6 +137,8 @@ void DCmd::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<PerfMapDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<TrimCLibcHeapDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<MallocInfoDcmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SystemMapDCmd>(full_export, true,false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SystemDumpMapDCmd>(full_export, true,false));
#endif // LINUX
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CodeHeapAnalyticsDCmd>(full_export, true, false));
@ -1151,3 +1157,45 @@ void CompilationMemoryStatisticDCmd::execute(DCmdSource source, TRAPS) {
const size_t minsize = _minsize.has_value() ? _minsize.value()._size : 0;
CompilationMemoryStatistic::print_all_by_size(output(), human_readable, minsize);
}
#ifdef LINUX
SystemMapDCmd::SystemMapDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
_human_readable("-H", "Human readable format", "BOOLEAN", false, "false") {
_dcmdparser.add_dcmd_option(&_human_readable);
}
void SystemMapDCmd::execute(DCmdSource source, TRAPS) {
MemMapPrinter::print_all_mappings(output(), _human_readable.value());
}
SystemDumpMapDCmd::SystemDumpMapDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
_human_readable("-H", "Human readable format", "BOOLEAN", false, "false"),
_filename("-F", "file path (defaults: \"vm_memory_map_<pid>.txt\")", "STRING", false) {
_dcmdparser.add_dcmd_option(&_human_readable);
_dcmdparser.add_dcmd_option(&_filename);
}
void SystemDumpMapDCmd::execute(DCmdSource source, TRAPS) {
stringStream default_name;
default_name.print("vm_memory_map_%d.txt", os::current_process_id());
const char* name = _filename.is_set() ? _filename.value() : default_name.base();
fileStream fs(name);
if (fs.is_open()) {
if (!MemTracker::enabled()) {
output()->print_cr("(NMT is disabled, will not annotate mappings).");
}
MemMapPrinter::print_all_mappings(&fs, _human_readable.value());
// For the readers convenience, resolve path name.
char tmp[JVM_MAXPATHLEN];
const char* absname = os::Posix::realpath(name, tmp, sizeof(tmp));
name = absname != nullptr ? absname : name;
output()->print_cr("Memory map dumped to \"%s\".", name);
} else {
output()->print_cr("Failed to open \"%s\" for writing (%s).", name, os::strerror(errno));
}
}
#endif // LINUX

@ -978,4 +978,45 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
#ifdef LINUX
class SystemMapDCmd : public DCmdWithParser {
DCmdArgument<bool> _human_readable;
public:
static int num_arguments() { return 1; }
SystemMapDCmd(outputStream* output, bool heap);
static const char* name() { return "System.map"; }
static const char* description() {
return "Prints an annotated process memory map of the VM process (linux only).";
}
static const char* impact() { return "Low"; }
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission",
"control", nullptr};
return p;
}
virtual void execute(DCmdSource source, TRAPS);
};
class SystemDumpMapDCmd : public DCmdWithParser {
DCmdArgument<bool> _human_readable;
DCmdArgument<char*> _filename;
public:
static int num_arguments() { return 2; }
SystemDumpMapDCmd(outputStream* output, bool heap);
static const char* name() { return "System.dump_map"; }
static const char* description() {
return "Dumps an annotated process memory map to an output file (linux only).";
}
static const char* impact() { return "Low"; }
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission",
"control", nullptr};
return p;
}
virtual void execute(DCmdSource source, TRAPS);
};
#endif // LINUX
#endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP

@ -0,0 +1,111 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*/
import org.testng.annotations.Test;
import jdk.test.lib.dcmd.CommandExecutor;
import jdk.test.lib.dcmd.JMXExecutor;
import jdk.test.lib.process.OutputAnalyzer;
import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.regex.Pattern;
/*
* @test
* @summary Test of diagnostic command System.map
* @library /test/lib
* @requires (os.family=="linux")
* @modules java.base/jdk.internal.misc
* java.compiler
* java.management
* jdk.internal.jvmstat/sun.jvmstat.monitor
* @run testng SystemDumpMapTest
*/
public class SystemDumpMapTest {
private void run_test(CommandExecutor executor, boolean useDefaultFileName) {
String filenameOption = useDefaultFileName ? "" : "-F=test-map.txt";
OutputAnalyzer output = executor.execute("System.dump_map " + filenameOption);
output.reportDiagnosticSummary();
String filename = useDefaultFileName ?
output.firstMatch("Memory map dumped to \"(\\S*vm_memory_map_\\d+\\.txt)\".*", 1) :
output.firstMatch("Memory map dumped to \"(\\S*test-map.txt)\".*", 1);
if (filename == null) {
throw new RuntimeException("Did not find dump file in output.\n");
}
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
boolean NMTOff = output.contains("NMT is disabled");
String regexBase = ".*0x\\p{XDigit}+ - 0x\\p{XDigit}+ +\\d+";
HashSet<Pattern> patterns = new HashSet<>();
patterns.add(Pattern.compile(regexBase + ".*jvm.*"));
if (!NMTOff) { // expect VM annotations if NMT is on
patterns.add(Pattern.compile(regexBase + ".*JAVAHEAP.*"));
patterns.add(Pattern.compile(regexBase + ".*META.*"));
patterns.add(Pattern.compile(regexBase + ".*CODE.*"));
patterns.add(Pattern.compile(regexBase + ".*STACK.*main.*"));
}
do {
String line = reader.readLine();
if (line != null) {
for (Pattern pat : patterns) {
if (pat.matcher(line).matches()) {
patterns.remove(pat);
break;
}
}
} else {
break;
}
} while (patterns.size() > 0);
if (patterns.size() > 0) {
System.out.println("Missing patterns in dump:");
for (Pattern pat : patterns) {
System.out.println(pat);
}
throw new RuntimeException("Missing patterns");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void run(CommandExecutor executor) {
run_test(executor, false);
run_test(executor, true);
}
@Test
public void jmx() {
run(new JMXExecutor());
}
}

@ -0,0 +1,68 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*/
import org.testng.annotations.Test;
import jdk.test.lib.dcmd.CommandExecutor;
import jdk.test.lib.dcmd.JMXExecutor;
import jdk.test.lib.process.OutputAnalyzer;
import java.io.*;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.regex.Pattern;
/*
* @test
* @summary Test of diagnostic command System.map
* @library /test/lib
* @requires (os.family=="linux")
* @modules java.base/jdk.internal.misc
* java.compiler
* java.management
* jdk.internal.jvmstat/sun.jvmstat.monitor
* @run testng SystemMapTest
*/
public class SystemMapTest {
public void run(CommandExecutor executor) {
OutputAnalyzer output = executor.execute("System.map");
output.reportDiagnosticSummary();
boolean NMTOff = output.contains("NMT is disabled");
String regexBase = ".*0x\\p{XDigit}+ - 0x\\p{XDigit}+ +\\d+";
output.shouldMatch(regexBase + ".*jvm.*");
if (!NMTOff) { // expect VM annotations if NMT is on
output.shouldMatch(regexBase + ".*JAVAHEAP.*");
output.shouldMatch(regexBase + ".*META.*");
output.shouldMatch(regexBase + ".*CODE.*");
output.shouldMatch(regexBase + ".*STACK.*main.*");
}
}
@Test
public void jmx() {
run(new JMXExecutor());
}
}