| /* |
| * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2021, 2023 SAP SE. 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 "jvm_io.h" |
| #include "logging/log.hpp" |
| #include "logging/logStream.hpp" |
| #include "runtime/arguments.hpp" |
| #include "runtime/atomic.hpp" |
| #include "runtime/globals.hpp" |
| #include "runtime/os.hpp" |
| #include "runtime/safefetch.hpp" |
| #include "services/mallocHeader.inline.hpp" |
| #include "services/mallocLimit.hpp" |
| #include "services/mallocSiteTable.hpp" |
| #include "services/mallocTracker.hpp" |
| #include "services/memTracker.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/macros.hpp" |
| #include "utilities/ostream.hpp" |
| #include "utilities/vmError.hpp" |
| |
| MallocMemorySnapshot MallocMemorySummary::_snapshot; |
| |
| void MemoryCounter::update_peak(size_t size, size_t cnt) { |
| size_t peak_sz = peak_size(); |
| while (peak_sz < size) { |
| size_t old_sz = Atomic::cmpxchg(&_peak_size, peak_sz, size, memory_order_relaxed); |
| if (old_sz == peak_sz) { |
| // I won |
| _peak_count = cnt; |
| break; |
| } else { |
| peak_sz = old_sz; |
| } |
| } |
| } |
| |
| // Total malloc'd memory used by arenas |
| size_t MallocMemorySnapshot::total_arena() const { |
| size_t amount = 0; |
| for (int index = 0; index < mt_number_of_types; index ++) { |
| amount += _malloc[index].arena_size(); |
| } |
| return amount; |
| } |
| |
| // Make adjustment by subtracting chunks used by arenas |
| // from total chunks to get total free chunk size |
| void MallocMemorySnapshot::make_adjustment() { |
| size_t arena_size = total_arena(); |
| int chunk_idx = NMTUtil::flag_to_index(mtChunk); |
| _malloc[chunk_idx].record_free(arena_size); |
| _all_mallocs.deallocate(arena_size); |
| } |
| |
| void MallocMemorySummary::initialize() { |
| // Uses placement new operator to initialize static area. |
| MallocLimitHandler::initialize(MallocLimit); |
| } |
| |
| bool MallocMemorySummary::total_limit_reached(size_t s, size_t so_far, const malloclimit* limit) { |
| |
| // Ignore the limit break during error reporting to prevent secondary errors. |
| if (VMError::is_error_reported()) { |
| return false; |
| } |
| |
| #define FORMATTED \ |
| "MallocLimit: reached global limit (triggering allocation size: " PROPERFMT ", allocated so far: " PROPERFMT ", limit: " PROPERFMT ") ", \ |
| PROPERFMTARGS(s), PROPERFMTARGS(so_far), PROPERFMTARGS(limit->sz) |
| |
| if (limit->mode == MallocLimitMode::trigger_fatal) { |
| fatal(FORMATTED); |
| } else { |
| log_warning(nmt)(FORMATTED); |
| } |
| #undef FORMATTED |
| |
| return true; |
| } |
| |
| bool MallocMemorySummary::category_limit_reached(MEMFLAGS f, size_t s, size_t so_far, const malloclimit* limit) { |
| |
| // Ignore the limit break during error reporting to prevent secondary errors. |
| if (VMError::is_error_reported()) { |
| return false; |
| } |
| |
| #define FORMATTED \ |
| "MallocLimit: reached category \"%s\" limit (triggering allocation size: " PROPERFMT ", allocated so far: " PROPERFMT ", limit: " PROPERFMT ") ", \ |
| NMTUtil::flag_to_enum_name(f), PROPERFMTARGS(s), PROPERFMTARGS(so_far), PROPERFMTARGS(limit->sz) |
| |
| if (limit->mode == MallocLimitMode::trigger_fatal) { |
| fatal(FORMATTED); |
| } else { |
| log_warning(nmt)(FORMATTED); |
| } |
| #undef FORMATTED |
| |
| return true; |
| } |
| |
| bool MallocTracker::initialize(NMT_TrackingLevel level) { |
| if (level >= NMT_summary) { |
| MallocMemorySummary::initialize(); |
| } |
| |
| if (level == NMT_detail) { |
| return MallocSiteTable::initialize(); |
| } |
| return true; |
| } |
| |
| // Record a malloc memory allocation |
| void* MallocTracker::record_malloc(void* malloc_base, size_t size, MEMFLAGS flags, |
| const NativeCallStack& stack) |
| { |
| assert(MemTracker::enabled(), "precondition"); |
| assert(malloc_base != nullptr, "precondition"); |
| |
| MallocMemorySummary::record_malloc(size, flags); |
| uint32_t mst_marker = 0; |
| if (MemTracker::tracking_level() == NMT_detail) { |
| MallocSiteTable::allocation_at(stack, size, &mst_marker, flags); |
| } |
| |
| // Uses placement global new operator to initialize malloc header |
| MallocHeader* const header = ::new (malloc_base)MallocHeader(size, flags, mst_marker); |
| void* const memblock = (void*)((char*)malloc_base + sizeof(MallocHeader)); |
| |
| // The alignment check: 8 bytes alignment for 32 bit systems. |
| // 16 bytes alignment for 64-bit systems. |
| assert(((size_t)memblock & (sizeof(size_t) * 2 - 1)) == 0, "Alignment check"); |
| |
| #ifdef ASSERT |
| // Read back |
| { |
| const MallocHeader* header2 = MallocHeader::resolve_checked(memblock); |
| assert(header2->size() == size, "Wrong size"); |
| assert(header2->flags() == flags, "Wrong flags"); |
| } |
| #endif |
| |
| return memblock; |
| } |
| |
| void* MallocTracker::record_free_block(void* memblock) { |
| assert(MemTracker::enabled(), "Sanity"); |
| assert(memblock != nullptr, "precondition"); |
| |
| MallocHeader* header = MallocHeader::resolve_checked(memblock); |
| |
| deaccount(header->free_info()); |
| |
| header->mark_block_as_dead(); |
| |
| return (void*)header; |
| } |
| |
| void MallocTracker::deaccount(MallocHeader::FreeInfo free_info) { |
| MallocMemorySummary::record_free(free_info.size, free_info.flags); |
| if (MemTracker::tracking_level() == NMT_detail) { |
| MallocSiteTable::deallocation_at(free_info.size, free_info.mst_marker); |
| } |
| } |
| |
| // Given a pointer, look for the containing malloc block. |
| // Print the block. Note that since there is very low risk of memory looking |
| // accidentally like a valid malloc block header (canaries and all) so this is not |
| // totally failproof and may give a wrong answer. It is safe in that it will never |
| // crash, even when encountering unmapped memory. |
| bool MallocTracker::print_pointer_information(const void* p, outputStream* st) { |
| assert(MemTracker::enabled(), "NMT not enabled"); |
| |
| #if !INCLUDE_ASAN |
| |
| address addr = (address)p; |
| |
| // Carefully feel your way upwards and try to find a malloc header. Then check if |
| // we are within the block. |
| // We give preference to found live blocks; but if no live block had been found, |
| // but the pointer points into remnants of a dead block, print that instead. |
| const MallocHeader* likely_dead_block = nullptr; |
| const MallocHeader* likely_live_block = nullptr; |
| { |
| const size_t smallest_possible_alignment = sizeof(void*); |
| const uint8_t* here = align_down(addr, smallest_possible_alignment); |
| const uint8_t* const end = here - (0x1000 + sizeof(MallocHeader)); // stop searching after 4k |
| for (; here >= end; here -= smallest_possible_alignment) { |
| // JDK-8306561: cast to a MallocHeader needs to guarantee it can reside in readable memory |
| if (!os::is_readable_range(here, here + sizeof(MallocHeader))) { |
| // Probably OOB, give up |
| break; |
| } |
| const MallocHeader* const candidate = (const MallocHeader*)here; |
| if (!candidate->looks_valid()) { |
| // This is definitely not a header, go on to the next candidate. |
| continue; |
| } |
| |
| // fudge factor: |
| // We don't report blocks for which p is clearly outside of. That would cause us to return true and possibly prevent |
| // subsequent tests of p, see os::print_location(). But if p is just outside of the found block, this may be a |
| // narrow oob error and we'd like to know that. |
| const int fudge = 8; |
| const address start_block = (address)candidate; |
| const address start_payload = (address)(candidate + 1); |
| const address end_payload = start_payload + candidate->size(); |
| const address end_payload_plus_fudge = end_payload + fudge; |
| if (addr >= start_block && addr < end_payload_plus_fudge) { |
| // We found a block the pointer is pointing into, or almost into. |
| // If its a live block, we have our info. If its a dead block, we still |
| // may be within the borders of a larger live block we have not found yet - |
| // continue search. |
| if (candidate->is_live()) { |
| likely_live_block = candidate; |
| break; |
| } else { |
| likely_dead_block = candidate; |
| continue; |
| } |
| } |
| } |
| } |
| |
| // If we've found a reasonable candidate. Print the info. |
| const MallocHeader* block = likely_live_block != nullptr ? likely_live_block : likely_dead_block; |
| if (block != nullptr) { |
| const char* where = nullptr; |
| const address start_block = (address)block; |
| const address start_payload = (address)(block + 1); |
| const address end_payload = start_payload + block->size(); |
| if (addr < start_payload) { |
| where = "into header of"; |
| } else if (addr < end_payload) { |
| where = "into"; |
| } else { |
| where = "just outside of"; |
| } |
| st->print_cr(PTR_FORMAT " %s %s malloced block starting at " PTR_FORMAT ", size " SIZE_FORMAT ", tag %s", |
| p2i(p), where, |
| (block->is_dead() ? "dead" : "live"), |
| p2i(block + 1), // lets print the payload start, not the header |
| block->size(), NMTUtil::flag_to_enum_name(block->flags())); |
| if (MemTracker::tracking_level() == NMT_detail) { |
| NativeCallStack ncs; |
| if (block->get_stack(ncs)) { |
| ncs.print_on(st); |
| st->cr(); |
| } |
| } |
| return true; |
| } |
| |
| #endif // !INCLUDE_ASAN |
| |
| return false; |
| } |