| /* |
| * Copyright (c) 2014, 2023, 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. |
| * |
| */ |
| #include "precompiled.hpp" |
| |
| |
| #include "memory/allocation.inline.hpp" |
| #include "runtime/atomic.hpp" |
| #include "services/mallocSiteTable.hpp" |
| |
| // Malloc site hashtable buckets |
| MallocSiteHashtableEntry** MallocSiteTable::_table = nullptr; |
| const NativeCallStack* MallocSiteTable::_hash_entry_allocation_stack = nullptr; |
| const MallocSiteHashtableEntry* MallocSiteTable::_hash_entry_allocation_site = nullptr; |
| |
| /* |
| * Initialize malloc site table. |
| * Hashtable entry is malloc'd, so it can cause infinite recursion. |
| * To avoid above problem, we pre-initialize a hash entry for |
| * this allocation site. |
| * The method is called during C runtime static variable initialization |
| * time, it is in single-threaded mode from JVM perspective. |
| */ |
| bool MallocSiteTable::initialize() { |
| |
| ALLOW_C_FUNCTION(::calloc, |
| _table = (MallocSiteHashtableEntry**)::calloc(table_size, sizeof(MallocSiteHashtableEntry*));) |
| if (_table == nullptr) { |
| return false; |
| } |
| |
| // Fake the call stack for hashtable entry allocation |
| assert(NMT_TrackingStackDepth > 1, "At least one tracking stack"); |
| |
| // Create pseudo call stack for hashtable entry allocation |
| address pc[3]; |
| if (NMT_TrackingStackDepth >= 3) { |
| uintx *fp = (uintx*)MallocSiteTable::allocation_at; |
| // On ppc64, 'fp' is a pointer to a function descriptor which is a struct of |
| // three native pointers where the first pointer is the real function address. |
| // See: http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html#FUNC-DES |
| pc[2] = (address)(fp PPC64_ONLY(BIG_ENDIAN_ONLY([0]))); |
| } |
| if (NMT_TrackingStackDepth >= 2) { |
| uintx *fp = (uintx*)MallocSiteTable::lookup_or_add; |
| pc[1] = (address)(fp PPC64_ONLY(BIG_ENDIAN_ONLY([0]))); |
| } |
| uintx *fp = (uintx*)MallocSiteTable::new_entry; |
| pc[0] = (address)(fp PPC64_ONLY(BIG_ENDIAN_ONLY([0]))); |
| |
| static const NativeCallStack stack(pc, MIN2(((int)(sizeof(pc) / sizeof(address))), ((int)NMT_TrackingStackDepth))); |
| static const MallocSiteHashtableEntry entry(stack, mtNMT); |
| |
| assert(_hash_entry_allocation_stack == nullptr && |
| _hash_entry_allocation_site == nullptr, |
| "Already initialized"); |
| |
| _hash_entry_allocation_stack = &stack; |
| _hash_entry_allocation_site = &entry; |
| |
| // Add the allocation site to hashtable. |
| int index = hash_to_index(entry.hash()); |
| _table[index] = const_cast<MallocSiteHashtableEntry*>(&entry); |
| |
| return true; |
| } |
| |
| // Walks entries in the hashtable. |
| // It stops walk if the walker returns false. |
| bool MallocSiteTable::walk(MallocSiteWalker* walker) { |
| MallocSiteHashtableEntry* head; |
| for (int index = 0; index < table_size; index ++) { |
| head = _table[index]; |
| while (head != nullptr) { |
| if (!walker->do_malloc_site(head->peek())) { |
| return false; |
| } |
| head = (MallocSiteHashtableEntry*)head->next(); |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * The hashtable does not have deletion policy on individual entry, |
| * and each linked list node is inserted via compare-and-swap, |
| * so each linked list is stable, the contention only happens |
| * at the end of linked list. |
| * This method should not return nullptr under normal circumstance. |
| * If nullptr is returned, it indicates: |
| * 1. Out of memory, it cannot allocate new hash entry. |
| * 2. Overflow hash bucket. |
| * Under any of above circumstances, caller should handle the situation. |
| */ |
| MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t* marker, MEMFLAGS flags) { |
| assert(flags != mtNone, "Should have a real memory type"); |
| const unsigned int hash = key.calculate_hash(); |
| const unsigned int index = hash_to_index(hash); |
| *marker = 0; |
| |
| // First entry for this hash bucket |
| if (_table[index] == nullptr) { |
| MallocSiteHashtableEntry* entry = new_entry(key, flags); |
| // OOM check |
| if (entry == nullptr) return nullptr; |
| |
| // swap in the head |
| if (Atomic::replace_if_null(&_table[index], entry)) { |
| *marker = build_marker(index, 0); |
| return entry->data(); |
| } |
| |
| delete entry; |
| } |
| |
| unsigned pos_idx = 0; |
| MallocSiteHashtableEntry* head = _table[index]; |
| while (head != nullptr && pos_idx < MAX_BUCKET_LENGTH) { |
| if (head->hash() == hash) { |
| MallocSite* site = head->data(); |
| if (site->flag() == flags && site->equals(key)) { |
| *marker = build_marker(index, pos_idx); |
| return head->data(); |
| } |
| } |
| |
| if (head->next() == nullptr && pos_idx < (MAX_BUCKET_LENGTH - 1)) { |
| MallocSiteHashtableEntry* entry = new_entry(key, flags); |
| // OOM check |
| if (entry == nullptr) return nullptr; |
| if (head->atomic_insert(entry)) { |
| pos_idx ++; |
| *marker = build_marker(index, pos_idx); |
| return entry->data(); |
| } |
| // contended, other thread won |
| delete entry; |
| } |
| head = (MallocSiteHashtableEntry*)head->next(); |
| pos_idx ++; |
| } |
| return nullptr; |
| } |
| |
| // Access malloc site |
| MallocSite* MallocSiteTable::malloc_site(uint32_t marker) { |
| uint16_t bucket_idx = bucket_idx_from_marker(marker); |
| assert(bucket_idx < table_size, "Invalid bucket index"); |
| const uint16_t pos_idx = pos_idx_from_marker(marker); |
| MallocSiteHashtableEntry* head = _table[bucket_idx]; |
| for (size_t index = 0; |
| index < pos_idx && head != nullptr; |
| index++, head = (MallocSiteHashtableEntry*)head->next()) {} |
| assert(head != nullptr, "Invalid position index"); |
| return head->data(); |
| } |
| |
| // Allocates MallocSiteHashtableEntry object. Special call stack |
| // (pre-installed allocation site) has to be used to avoid infinite |
| // recursion. |
| MallocSiteHashtableEntry* MallocSiteTable::new_entry(const NativeCallStack& key, MEMFLAGS flags) { |
| void* p = AllocateHeap(sizeof(MallocSiteHashtableEntry), mtNMT, |
| *hash_entry_allocation_stack(), AllocFailStrategy::RETURN_NULL); |
| return ::new (p) MallocSiteHashtableEntry(key, flags); |
| } |
| |
| bool MallocSiteTable::walk_malloc_site(MallocSiteWalker* walker) { |
| assert(walker != nullptr, "NuLL walker"); |
| return walk(walker); |
| } |
| |
| static int qsort_helper(const void* a, const void* b) { |
| return *((uint16_t*)a) - *((uint16_t*)b); |
| } |
| |
| void MallocSiteTable::print_tuning_statistics(outputStream* st) { |
| // Total number of allocation sites, include empty sites |
| int total_entries = 0; |
| // Number of allocation sites that have all memory freed |
| int empty_entries = 0; |
| // Number of captured call stack distribution |
| int stack_depth_distribution[NMT_TrackingStackDepth + 1] = { 0 }; |
| // Chain lengths |
| uint16_t lengths[table_size] = { 0 }; |
| // Unused buckets |
| int unused_buckets = 0; |
| |
| for (int i = 0; i < table_size; i ++) { |
| int this_chain_length = 0; |
| const MallocSiteHashtableEntry* head = _table[i]; |
| if (head == nullptr) { |
| unused_buckets ++; |
| } |
| while (head != nullptr) { |
| total_entries ++; |
| this_chain_length ++; |
| if (head->size() == 0) { |
| empty_entries ++; |
| } |
| const int callstack_depth = head->peek()->call_stack()->frames(); |
| assert(callstack_depth >= 0 && callstack_depth <= NMT_TrackingStackDepth, |
| "Sanity (%d)", callstack_depth); |
| stack_depth_distribution[callstack_depth] ++; |
| head = head->next(); |
| } |
| lengths[i] = (uint16_t)MIN2(this_chain_length, USHRT_MAX); |
| } |
| |
| st->print_cr("Malloc allocation site table:"); |
| st->print_cr("\tTotal entries: %d", total_entries); |
| st->print_cr("\tEmpty entries (no outstanding mallocs): %d (%2.2f%%)", |
| empty_entries, ((float)empty_entries * 100) / total_entries); |
| st->cr(); |
| |
| qsort(lengths, table_size, sizeof(uint16_t), qsort_helper); |
| |
| st->print_cr("Bucket chain length distribution:"); |
| st->print_cr("unused: %d", unused_buckets); |
| st->print_cr("longest: %d", lengths[table_size - 1]); |
| st->print_cr("median: %d", lengths[table_size / 2]); |
| st->cr(); |
| |
| st->print_cr("Call stack depth distribution:"); |
| for (int i = 0; i <= NMT_TrackingStackDepth; i ++) { |
| st->print_cr("\t%d: %d", i, stack_depth_distribution[i]); |
| } |
| st->cr(); |
| } |
| |
| bool MallocSiteHashtableEntry::atomic_insert(MallocSiteHashtableEntry* entry) { |
| return Atomic::replace_if_null(&_next, entry); |
| } |