Path: blob/master/src/hotspot/share/services/nmtPreInit.hpp
64440 views
/*1* Copyright (c) 2021 SAP SE. All rights reserved.2* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.3* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.4*5* This code is free software; you can redistribute it and/or modify it6* under the terms of the GNU General Public License version 2 only, as7* published by the Free Software Foundation.8*9* This code is distributed in the hope that it will be useful, but WITHOUT10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License12* version 2 for more details (a copy is included in the LICENSE file that13* accompanied this code).14*15* You should have received a copy of the GNU General Public License version16* 2 along with this work; if not, write to the Free Software Foundation,17* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.18*19* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA20* or visit www.oracle.com if you need additional information or have any21* questions.22*23*/2425#ifndef SHARE_SERVICES_NMT_PREINIT_HPP26#define SHARE_SERVICES_NMT_PREINIT_HPP2728#include "memory/allStatic.hpp"29#ifdef ASSERT30#include "runtime/atomic.hpp"31#endif32#include "utilities/debug.hpp"33#include "utilities/globalDefinitions.hpp"34#include "utilities/macros.hpp"3536#if INCLUDE_NMT3738class outputStream;3940// NMTPreInit is the solution to a specific problem:41//42// NMT tracks C-heap allocations (os::malloc and friends). Those can happen at all VM life stages,43// including very early during the dynamic C++ initialization of the hotspot, and in CreateJavaVM44// before argument parsing.45//46// However, before the VM parses NMT arguments, we do not know whether NMT is enabled or not. Can we47// just ignore early allocations? If the only problem were statistical correctness, sure: footprint-wise48// they are not really relevant.49//50// But there is one big problem: NMT uses malloc headers to keep meta information of malloced blocks.51// We have to consider those in os::free() when calling free(3).52//53// So:54// 1) NMT off:55// a) pre-NMT-init allocations have no header56// b) post-NMT-init allocations have no header57// 2) NMT on:58// a) pre-NMT-init allocations have no header59// b) post-NMT-init allocations do have a header60//61// The problem is that inside os::free(p), we only get an opaque void* p; we do not know if p had been62// allocated in (a) or (b) phase. Therefore, we do not know if p is preceded by an NMT header which we63// would need to subtract from the pointer before calling free(3). There is no safe way to "guess" here64// without risking C-heap corruption.65//66// To solve this, we need a way to quickly determine, at os::free(p), whether p was a pre-NMT-init67// allocation. There are several ways to do this, see discussion under JDK-8256844.68//69// One of the easiest and most elegant ways is to store early allocation pointers in a lookup table.70// This is what NMTPreInit does.71//72//////////////////////////////////////////////////////////////////////////73//74// VM initialization wrt NMT:75//76//---------------------------------------------------------------77//-> launcher dlopen's libjvm ^78// -> dynamic C++ initialization |79// of libjvm |80// |81//-> launcher starts new thread (maybe) NMT pre-init phase : store allocated pointers in lookup table82// |83//-> launcher invokes CreateJavaVM |84// -> VM initialization before arg parsing |85// -> VM argument parsing v86// -> NMT initialization -------------------------------------87// ^88// ... |89// -> VM life... NMT post-init phase : lookup table is read-only; use it in os::free() and os::realloc().90// ... |91// v92//----------------------------------------------------------------93//94//////////////////////////////////////////////////////////////////////////95//96// Notes:97// - The VM will malloc() and realloc() several thousand times before NMT initialization.98// Starting with a lot of arguments increases this number since argument parsing strdups99// around a lot.100// - However, *surviving* allocations (allocations not freed immediately) are much rarer:101// typically only about 300-500. Again, mainly depending on the number of VM args.102// - There are a few cases of pre-to-post-init reallocs where pre-init allocations get103// reallocated after NMT initialization. Those we need to handle with special care (see104// NMTPreInit::handle_realloc()). Because of them we need to store allocation size with105// every pre-init allocation.106107// For the lookup table, design considerations are:108// - lookup speed is paramount since lookup is done for every os::free() call.109// - insert/delete speed only matters for VM startup - after NMT initialization the lookup110// table is readonly111// - memory consumption of the lookup table matters since we always pay for it, NMT on or off.112// - Obviously, nothing here can use *os::malloc*. Any dynamic allocations - if they cannot113// be avoided - should use raw malloc(3).114//115// We use a basic open hashmap, dimensioned generously - hash collisions should be very rare.116// The table is customized for holding malloced pointers. One main point of this map is that we do117// not allocate memory for the nodes themselves. Instead we piggy-back on the user allocation:118// the hashmap entry structure precedes, as a header, the malloced block. That way we avoid extra119// allocations just to hold the map nodes. This keeps runtime/memory overhead as small as possible.120121struct NMTPreInitAllocation {122NMTPreInitAllocation* next;123const size_t size; // (inner) payload size without header124// <-- USER ALLOCATION (PAYLOAD) STARTS HERE -->125126NMTPreInitAllocation(size_t size) : next(NULL), size(size) {};127128// Returns start of the user data area129void* payload() { return this + 1; }130const void* payload() const { return this + 1; }131132// These functions do raw-malloc/realloc/free a C-heap block of given payload size,133// preceded with a NMTPreInitAllocation header.134static NMTPreInitAllocation* do_alloc(size_t payload_size);135static NMTPreInitAllocation* do_reallocate(NMTPreInitAllocation* old, size_t new_payload_size);136static void do_free(NMTPreInitAllocation* p);137};138139class NMTPreInitAllocationTable {140141// Table_size: keep table size a prime and the hash function simple; this142// seems to give a good distribution for malloced pointers on all our libc variants.143// 8000ish is really plenty: normal VM runs have ~500 pre-init allocations to hold,144// VMs with insanely long command lines maybe ~700-1000. Which gives us an expected145// load factor of ~.1. Hash collisions should be very rare.146// ~8000 entries cost us ~64K for this table (64-bit), which is acceptable.147static const int table_size = 7919;148149NMTPreInitAllocation* _entries[table_size];150151typedef int index_t;152const index_t invalid_index = -1;153154static unsigned calculate_hash(const void* p) {155uintptr_t tmp = p2i(p);156unsigned hash = (unsigned)tmp157LP64_ONLY( ^ (unsigned)(tmp >> 32));158return hash;159}160161static index_t index_for_key(const void* p) {162const unsigned hash = calculate_hash(p);163return hash % table_size;164}165166const NMTPreInitAllocation* const * find_entry(const void* p) const {167return const_cast<NMTPreInitAllocationTable*>(this)->find_entry(p);168}169170NMTPreInitAllocation** find_entry(const void* p) {171const unsigned index = index_for_key(p);172NMTPreInitAllocation** aa = (&(_entries[index]));173while ((*aa) != NULL && (*aa)->payload() != p) {174aa = &((*aa)->next);175}176assert((*aa) == NULL || p == (*aa)->payload(),177"retrieve mismatch " PTR_FORMAT " vs " PTR_FORMAT ".",178p2i(p), p2i((*aa)->payload()));179return aa;180}181182public:183184NMTPreInitAllocationTable();185186// Adds an entry to the table187void add(NMTPreInitAllocation* a) {188void* payload = a->payload();189const unsigned index = index_for_key(payload);190assert(a->next == NULL, "entry already in table?");191a->next = _entries[index]; // add to front192_entries[index] = a; // of list193assert(find(payload) == a, "add: reverse lookup error?");194}195196// Find - but does not remove - an entry in this map.197// Returns NULL if not found.198const NMTPreInitAllocation* find(const void* p) const {199return *(find_entry(p));200}201202// Find and removes an entry from the table. Asserts if not found.203NMTPreInitAllocation* find_and_remove(void* p) {204NMTPreInitAllocation** aa = find_entry(p);205assert((*aa) != NULL, "Entry not found: " PTR_FORMAT, p2i(p));206NMTPreInitAllocation* a = (*aa);207(*aa) = (*aa)->next; // remove from its list208DEBUG_ONLY(a->next = NULL;) // mark as removed209return a;210}211212void print_state(outputStream* st) const;213DEBUG_ONLY(void print_map(outputStream* st) const;)214DEBUG_ONLY(void verify() const;)215};216217// NMTPreInit is the outside interface to all of NMT preinit handling.218class NMTPreInit : public AllStatic {219220static NMTPreInitAllocationTable* _table;221static bool _nmt_was_initialized;222223// Some statistics224static unsigned _num_mallocs_pre; // Number of pre-init mallocs225static unsigned _num_reallocs_pre; // Number of pre-init reallocs226static unsigned _num_frees_pre; // Number of pre-init frees227228static void create_table();229230static void add_to_map(NMTPreInitAllocation* a) {231assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");232// Only on add, we create the table on demand. Only needed on add, since everything should start233// with a call to os::malloc().234if (_table == NULL) {235create_table();236}237return _table->add(a);238}239240static const NMTPreInitAllocation* find_in_map(void* p) {241assert(_table != NULL, "stray allocation?");242return _table->find(p);243}244245static NMTPreInitAllocation* find_and_remove_in_map(void* p) {246assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");247assert(_table != NULL, "stray allocation?");248return _table->find_and_remove(p);249}250251// Just a wrapper for os::malloc to avoid including os.hpp here.252static void* do_os_malloc(size_t size);253254public:255256// Switches from NMT pre-init state to NMT post-init state;257// in post-init, no modifications to the lookup table are possible.258static void pre_to_post();259260// Returns true if we are still in pre-init phase, false if post-init261static bool in_preinit_phase() { return _nmt_was_initialized == false; }262263// Called from os::malloc.264// Returns true if allocation was handled here; in that case,265// *rc contains the return address.266static bool handle_malloc(void** rc, size_t size) {267size = MAX2((size_t)1, size); // malloc(0)268if (!_nmt_was_initialized) {269// pre-NMT-init:270// Allocate entry and add address to lookup table271NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(size);272add_to_map(a);273(*rc) = a->payload();274_num_mallocs_pre++;275return true;276}277return false;278}279280// Called from os::realloc.281// Returns true if reallocation was handled here; in that case,282// *rc contains the return address.283static bool handle_realloc(void** rc, void* old_p, size_t new_size) {284if (old_p == NULL) { // realloc(NULL, n)285return handle_malloc(rc, new_size);286}287new_size = MAX2((size_t)1, new_size); // realloc(.., 0)288if (!_nmt_was_initialized) {289// pre-NMT-init:290// - the address must already be in the lookup table291// - find the old entry, remove from table, reallocate, add to table292NMTPreInitAllocation* a = find_and_remove_in_map(old_p);293a = NMTPreInitAllocation::do_reallocate(a, new_size);294add_to_map(a);295(*rc) = a->payload();296_num_reallocs_pre++;297return true;298} else {299// post-NMT-init:300// If the old block was allocated during pre-NMT-init, we must relocate it: the301// new block must be allocated with "normal" os::malloc.302// We do this by:303// - look up (but not remove! lu table is read-only here.) the old entry304// - allocate new memory via os::malloc()305// - manually copy the old content over306// - return the new memory307// - The lu table is readonly so we keep the old address in the table. And we leave308// the old block allocated too, to prevent the libc from returning the same address309// and confusing us.310const NMTPreInitAllocation* a = find_in_map(old_p);311if (a != NULL) { // this was originally a pre-init allocation312void* p_new = do_os_malloc(new_size);313::memcpy(p_new, a->payload(), MIN2(a->size, new_size));314(*rc) = p_new;315return true;316}317}318return false;319}320321// Called from os::free.322// Returns true if free was handled here.323static bool handle_free(void* p) {324if (p == NULL) { // free(NULL)325return true;326}327if (!_nmt_was_initialized) {328// pre-NMT-init:329// - the allocation must be in the hash map, since all allocations went through330// NMTPreInit::handle_malloc()331// - find the old entry, unhang from map, free it332NMTPreInitAllocation* a = find_and_remove_in_map(p);333NMTPreInitAllocation::do_free(a);334_num_frees_pre++;335return true;336} else {337// post-NMT-init:338// - look up (but not remove! lu table is read-only here.) the entry339// - if found, we do nothing: the lu table is readonly, so we keep the old address340// in the table. We leave the block allocated to prevent the libc from returning341// the same address and confusing us.342// - if not found, we let regular os::free() handle this pointer343if (find_in_map(p) != NULL) {344return true;345}346}347return false;348}349350static void print_state(outputStream* st);351static void print_map(outputStream* st);352DEBUG_ONLY(static void verify();)353};354355#endif // INCLUDE_NMT356357#endif // SHARE_SERVICES_NMT_PREINIT_HPP358359360361