blob: 515d3804638aff8b17edf460b21b7e8a3b6e5d03 [file] [edit]
/*
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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.
*
*/
#if defined(LINUX) || defined(_WIN64) || defined(__APPLE__)
#include "gc/shared/collectedHeap.hpp"
#include "logging/logAsyncWriter.hpp"
#include "memory/allocation.hpp"
#include "memory/universe.hpp"
#include "memory/resourceArea.hpp"
#include "nmt/memTag.hpp"
#include "nmt/memTagBitmap.hpp"
#include "nmt/memMapPrinter.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "runtime/nonJavaThread.hpp"
#include "runtime/osThread.hpp"
#include "runtime/thread.hpp"
#include "runtime/threadSMR.hpp"
#include "runtime/vmThread.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
#include "utilities/permitForbiddenFunctions.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 tags name.
#define NMT_FLAGS_DO(f) \
/* mem_tag, 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_mem_tag(MemTag mem_tag) {
#define DO(t, shortname, text) if (t == mem_tag) return shortname;
NMT_FLAGS_DO(DO)
#undef DO
return NMTUtil::tag_to_enum_name(mem_tag);
}
/// 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 MemTag, 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;
MemTag* _mem_tags;
size_t _count, _capacity;
mutable size_t _last;
public:
CachedNMTInformation() : _ranges(nullptr), _mem_tags(nullptr),
_count(0), _capacity(0), _last(0) {}
~CachedNMTInformation() {
permit_forbidden_function::free(_ranges);
permit_forbidden_function::free(_mem_tags);
}
bool add(const void* from, const void* to, MemTag mem_tag) {
// 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 mem_tag.
if (_count > 0 && from == _ranges[_count - 1].to && mem_tag == _mem_tags[_count - 1]) {
_ranges[_count - 1].to = to;
return true;
}
if (_count == _capacity) {
// Enlarge if needed
const size_t new_capacity = MAX2((size_t)4096, 2 * _capacity);
// Unfortunately, we need to allocate manually, raw, since we must prevent NMT deadlocks.
_ranges = (Range*)permit_forbidden_function::realloc(_ranges, new_capacity * sizeof(Range));
_mem_tags = (MemTag*)permit_forbidden_function::realloc(_mem_tags, new_capacity * sizeof(MemTag));
if (_ranges == nullptr || _mem_tags == nullptr) {
// In case of OOM lets make no fuss. Just return.
return false;
}
_capacity = new_capacity;
}
assert(_capacity > _count, "Sanity");
_ranges[_count] = Range { from, to };
_mem_tags[_count] = mem_tag;
_count++;
return true;
}
// Given a vma [from, to), find all regions that intersect with this vma and
// return their collective flags.
MemTagBitmap lookup(const void* from, const void* to) const {
assert(from <= to, "Sanity");
// We optimize for sequential lookups. Since this class is used when a list
// of OS mappings is scanned (VirtualQuery, /proc/pid/maps), and these lists
// are usually sorted in order of addresses, ascending.
if (to <= _ranges[_last].from) {
// the range is to the right of the given section, we need to re-start the search
_last = 0;
}
MemTagBitmap bm;
for(uintx i = _last; i < _count; i++) {
if (range_intersects(from, to, _ranges[i].from, _ranges[i].to)) {
bm.set_tag(_mem_tags[i]);
} else if (to <= _ranges[i].from) {
_last = i;
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->mem_tag());
}
// 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.
// Note it is possible to encounter a brand new thread that has not yet initialized its stack fields.
return t->stack_base_or_null() != nullptr && 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) {
// avoid commas and spaces in output to ease post-processing via awk
char tmp[64];
stringStream ss(tmp, sizeof(tmp));
ss.print(":%zu-%s", (uintx)thread_id, name);
for (int i = 0; tmp[i] != '\0'; i++) {
if (!isalnum(tmp[i])) {
tmp[i] = '-';
}
}
st->print_raw(tmp);
}
// 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) {
ResourceMark rm;
#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);
}
}
}
///////////////
MappingPrintSession::MappingPrintSession(outputStream* st, const CachedNMTInformation& nmt_info) :
_out(st), _nmt_info(nmt_info)
{}
void MappingPrintSession::print_nmt_flag_legend() const {
#define DO(flag, shortname, text) _out->print_cr("%10s: %s", shortname, text);
NMT_FLAGS_DO(DO)
#undef DO
}
bool MappingPrintSession::print_nmt_info_for_region(const void* vma_from, const void* vma_to) const {
int num_printed = 0;
// print NMT information, if available
if (MemTracker::enabled()) {
// Correlate vma region (from, to) with NMT region(s) we collected previously.
const MemTagBitmap flags = _nmt_info.lookup(vma_from, vma_to);
if (flags.has_any()) {
for (int i = 0; i < mt_number_of_tags; i++) {
const MemTag mem_tag = (MemTag)i;
if (flags.has_tag(mem_tag)) {
if (num_printed > 0) {
_out->put(',');
}
_out->print("%s", get_shortname_for_mem_tag(mem_tag));
if (mem_tag == mtThreadStack) {
print_thread_details_for_supposed_stack_address(vma_from, vma_to, _out);
}
num_printed++;
}
}
}
}
return num_printed > 0;
}
void MemMapPrinter::print_all_mappings(outputStream* st) {
CachedNMTInformation nmt_info;
st->print_cr("Memory mappings:");
// Prepare NMT info cache. But only do so if we print individual mappings,
// otherwise, we won't need it and can save that work.
if (MemTracker::enabled()) {
nmt_info.fill_from_nmt();
} else {
st->print_cr("NMT is disabled. VM info not available.");
}
MappingPrintSession session(st, nmt_info);
pd_print_all_mappings(session);
}
#endif // LINUX