Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/hotspot/share/services/nmtPreInit.hpp
64440 views
1
/*
2
* Copyright (c) 2021 SAP SE. All rights reserved.
3
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
4
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5
*
6
* This code is free software; you can redistribute it and/or modify it
7
* under the terms of the GNU General Public License version 2 only, as
8
* published by the Free Software Foundation.
9
*
10
* This code is distributed in the hope that it will be useful, but WITHOUT
11
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13
* version 2 for more details (a copy is included in the LICENSE file that
14
* accompanied this code).
15
*
16
* You should have received a copy of the GNU General Public License version
17
* 2 along with this work; if not, write to the Free Software Foundation,
18
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19
*
20
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21
* or visit www.oracle.com if you need additional information or have any
22
* questions.
23
*
24
*/
25
26
#ifndef SHARE_SERVICES_NMT_PREINIT_HPP
27
#define SHARE_SERVICES_NMT_PREINIT_HPP
28
29
#include "memory/allStatic.hpp"
30
#ifdef ASSERT
31
#include "runtime/atomic.hpp"
32
#endif
33
#include "utilities/debug.hpp"
34
#include "utilities/globalDefinitions.hpp"
35
#include "utilities/macros.hpp"
36
37
#if INCLUDE_NMT
38
39
class outputStream;
40
41
// NMTPreInit is the solution to a specific problem:
42
//
43
// NMT tracks C-heap allocations (os::malloc and friends). Those can happen at all VM life stages,
44
// including very early during the dynamic C++ initialization of the hotspot, and in CreateJavaVM
45
// before argument parsing.
46
//
47
// However, before the VM parses NMT arguments, we do not know whether NMT is enabled or not. Can we
48
// just ignore early allocations? If the only problem were statistical correctness, sure: footprint-wise
49
// they are not really relevant.
50
//
51
// But there is one big problem: NMT uses malloc headers to keep meta information of malloced blocks.
52
// We have to consider those in os::free() when calling free(3).
53
//
54
// So:
55
// 1) NMT off:
56
// a) pre-NMT-init allocations have no header
57
// b) post-NMT-init allocations have no header
58
// 2) NMT on:
59
// a) pre-NMT-init allocations have no header
60
// b) post-NMT-init allocations do have a header
61
//
62
// The problem is that inside os::free(p), we only get an opaque void* p; we do not know if p had been
63
// allocated in (a) or (b) phase. Therefore, we do not know if p is preceded by an NMT header which we
64
// would need to subtract from the pointer before calling free(3). There is no safe way to "guess" here
65
// without risking C-heap corruption.
66
//
67
// To solve this, we need a way to quickly determine, at os::free(p), whether p was a pre-NMT-init
68
// allocation. There are several ways to do this, see discussion under JDK-8256844.
69
//
70
// One of the easiest and most elegant ways is to store early allocation pointers in a lookup table.
71
// This is what NMTPreInit does.
72
//
73
//////////////////////////////////////////////////////////////////////////
74
//
75
// VM initialization wrt NMT:
76
//
77
//---------------------------------------------------------------
78
//-> launcher dlopen's libjvm ^
79
// -> dynamic C++ initialization |
80
// of libjvm |
81
// |
82
//-> launcher starts new thread (maybe) NMT pre-init phase : store allocated pointers in lookup table
83
// |
84
//-> launcher invokes CreateJavaVM |
85
// -> VM initialization before arg parsing |
86
// -> VM argument parsing v
87
// -> NMT initialization -------------------------------------
88
// ^
89
// ... |
90
// -> VM life... NMT post-init phase : lookup table is read-only; use it in os::free() and os::realloc().
91
// ... |
92
// v
93
//----------------------------------------------------------------
94
//
95
//////////////////////////////////////////////////////////////////////////
96
//
97
// Notes:
98
// - The VM will malloc() and realloc() several thousand times before NMT initialization.
99
// Starting with a lot of arguments increases this number since argument parsing strdups
100
// around a lot.
101
// - However, *surviving* allocations (allocations not freed immediately) are much rarer:
102
// typically only about 300-500. Again, mainly depending on the number of VM args.
103
// - There are a few cases of pre-to-post-init reallocs where pre-init allocations get
104
// reallocated after NMT initialization. Those we need to handle with special care (see
105
// NMTPreInit::handle_realloc()). Because of them we need to store allocation size with
106
// every pre-init allocation.
107
108
// For the lookup table, design considerations are:
109
// - lookup speed is paramount since lookup is done for every os::free() call.
110
// - insert/delete speed only matters for VM startup - after NMT initialization the lookup
111
// table is readonly
112
// - memory consumption of the lookup table matters since we always pay for it, NMT on or off.
113
// - Obviously, nothing here can use *os::malloc*. Any dynamic allocations - if they cannot
114
// be avoided - should use raw malloc(3).
115
//
116
// We use a basic open hashmap, dimensioned generously - hash collisions should be very rare.
117
// The table is customized for holding malloced pointers. One main point of this map is that we do
118
// not allocate memory for the nodes themselves. Instead we piggy-back on the user allocation:
119
// the hashmap entry structure precedes, as a header, the malloced block. That way we avoid extra
120
// allocations just to hold the map nodes. This keeps runtime/memory overhead as small as possible.
121
122
struct NMTPreInitAllocation {
123
NMTPreInitAllocation* next;
124
const size_t size; // (inner) payload size without header
125
// <-- USER ALLOCATION (PAYLOAD) STARTS HERE -->
126
127
NMTPreInitAllocation(size_t size) : next(NULL), size(size) {};
128
129
// Returns start of the user data area
130
void* payload() { return this + 1; }
131
const void* payload() const { return this + 1; }
132
133
// These functions do raw-malloc/realloc/free a C-heap block of given payload size,
134
// preceded with a NMTPreInitAllocation header.
135
static NMTPreInitAllocation* do_alloc(size_t payload_size);
136
static NMTPreInitAllocation* do_reallocate(NMTPreInitAllocation* old, size_t new_payload_size);
137
static void do_free(NMTPreInitAllocation* p);
138
};
139
140
class NMTPreInitAllocationTable {
141
142
// Table_size: keep table size a prime and the hash function simple; this
143
// seems to give a good distribution for malloced pointers on all our libc variants.
144
// 8000ish is really plenty: normal VM runs have ~500 pre-init allocations to hold,
145
// VMs with insanely long command lines maybe ~700-1000. Which gives us an expected
146
// load factor of ~.1. Hash collisions should be very rare.
147
// ~8000 entries cost us ~64K for this table (64-bit), which is acceptable.
148
static const int table_size = 7919;
149
150
NMTPreInitAllocation* _entries[table_size];
151
152
typedef int index_t;
153
const index_t invalid_index = -1;
154
155
static unsigned calculate_hash(const void* p) {
156
uintptr_t tmp = p2i(p);
157
unsigned hash = (unsigned)tmp
158
LP64_ONLY( ^ (unsigned)(tmp >> 32));
159
return hash;
160
}
161
162
static index_t index_for_key(const void* p) {
163
const unsigned hash = calculate_hash(p);
164
return hash % table_size;
165
}
166
167
const NMTPreInitAllocation* const * find_entry(const void* p) const {
168
return const_cast<NMTPreInitAllocationTable*>(this)->find_entry(p);
169
}
170
171
NMTPreInitAllocation** find_entry(const void* p) {
172
const unsigned index = index_for_key(p);
173
NMTPreInitAllocation** aa = (&(_entries[index]));
174
while ((*aa) != NULL && (*aa)->payload() != p) {
175
aa = &((*aa)->next);
176
}
177
assert((*aa) == NULL || p == (*aa)->payload(),
178
"retrieve mismatch " PTR_FORMAT " vs " PTR_FORMAT ".",
179
p2i(p), p2i((*aa)->payload()));
180
return aa;
181
}
182
183
public:
184
185
NMTPreInitAllocationTable();
186
187
// Adds an entry to the table
188
void add(NMTPreInitAllocation* a) {
189
void* payload = a->payload();
190
const unsigned index = index_for_key(payload);
191
assert(a->next == NULL, "entry already in table?");
192
a->next = _entries[index]; // add to front
193
_entries[index] = a; // of list
194
assert(find(payload) == a, "add: reverse lookup error?");
195
}
196
197
// Find - but does not remove - an entry in this map.
198
// Returns NULL if not found.
199
const NMTPreInitAllocation* find(const void* p) const {
200
return *(find_entry(p));
201
}
202
203
// Find and removes an entry from the table. Asserts if not found.
204
NMTPreInitAllocation* find_and_remove(void* p) {
205
NMTPreInitAllocation** aa = find_entry(p);
206
assert((*aa) != NULL, "Entry not found: " PTR_FORMAT, p2i(p));
207
NMTPreInitAllocation* a = (*aa);
208
(*aa) = (*aa)->next; // remove from its list
209
DEBUG_ONLY(a->next = NULL;) // mark as removed
210
return a;
211
}
212
213
void print_state(outputStream* st) const;
214
DEBUG_ONLY(void print_map(outputStream* st) const;)
215
DEBUG_ONLY(void verify() const;)
216
};
217
218
// NMTPreInit is the outside interface to all of NMT preinit handling.
219
class NMTPreInit : public AllStatic {
220
221
static NMTPreInitAllocationTable* _table;
222
static bool _nmt_was_initialized;
223
224
// Some statistics
225
static unsigned _num_mallocs_pre; // Number of pre-init mallocs
226
static unsigned _num_reallocs_pre; // Number of pre-init reallocs
227
static unsigned _num_frees_pre; // Number of pre-init frees
228
229
static void create_table();
230
231
static void add_to_map(NMTPreInitAllocation* a) {
232
assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");
233
// Only on add, we create the table on demand. Only needed on add, since everything should start
234
// with a call to os::malloc().
235
if (_table == NULL) {
236
create_table();
237
}
238
return _table->add(a);
239
}
240
241
static const NMTPreInitAllocation* find_in_map(void* p) {
242
assert(_table != NULL, "stray allocation?");
243
return _table->find(p);
244
}
245
246
static NMTPreInitAllocation* find_and_remove_in_map(void* p) {
247
assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");
248
assert(_table != NULL, "stray allocation?");
249
return _table->find_and_remove(p);
250
}
251
252
// Just a wrapper for os::malloc to avoid including os.hpp here.
253
static void* do_os_malloc(size_t size);
254
255
public:
256
257
// Switches from NMT pre-init state to NMT post-init state;
258
// in post-init, no modifications to the lookup table are possible.
259
static void pre_to_post();
260
261
// Returns true if we are still in pre-init phase, false if post-init
262
static bool in_preinit_phase() { return _nmt_was_initialized == false; }
263
264
// Called from os::malloc.
265
// Returns true if allocation was handled here; in that case,
266
// *rc contains the return address.
267
static bool handle_malloc(void** rc, size_t size) {
268
size = MAX2((size_t)1, size); // malloc(0)
269
if (!_nmt_was_initialized) {
270
// pre-NMT-init:
271
// Allocate entry and add address to lookup table
272
NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(size);
273
add_to_map(a);
274
(*rc) = a->payload();
275
_num_mallocs_pre++;
276
return true;
277
}
278
return false;
279
}
280
281
// Called from os::realloc.
282
// Returns true if reallocation was handled here; in that case,
283
// *rc contains the return address.
284
static bool handle_realloc(void** rc, void* old_p, size_t new_size) {
285
if (old_p == NULL) { // realloc(NULL, n)
286
return handle_malloc(rc, new_size);
287
}
288
new_size = MAX2((size_t)1, new_size); // realloc(.., 0)
289
if (!_nmt_was_initialized) {
290
// pre-NMT-init:
291
// - the address must already be in the lookup table
292
// - find the old entry, remove from table, reallocate, add to table
293
NMTPreInitAllocation* a = find_and_remove_in_map(old_p);
294
a = NMTPreInitAllocation::do_reallocate(a, new_size);
295
add_to_map(a);
296
(*rc) = a->payload();
297
_num_reallocs_pre++;
298
return true;
299
} else {
300
// post-NMT-init:
301
// If the old block was allocated during pre-NMT-init, we must relocate it: the
302
// new block must be allocated with "normal" os::malloc.
303
// We do this by:
304
// - look up (but not remove! lu table is read-only here.) the old entry
305
// - allocate new memory via os::malloc()
306
// - manually copy the old content over
307
// - return the new memory
308
// - The lu table is readonly so we keep the old address in the table. And we leave
309
// the old block allocated too, to prevent the libc from returning the same address
310
// and confusing us.
311
const NMTPreInitAllocation* a = find_in_map(old_p);
312
if (a != NULL) { // this was originally a pre-init allocation
313
void* p_new = do_os_malloc(new_size);
314
::memcpy(p_new, a->payload(), MIN2(a->size, new_size));
315
(*rc) = p_new;
316
return true;
317
}
318
}
319
return false;
320
}
321
322
// Called from os::free.
323
// Returns true if free was handled here.
324
static bool handle_free(void* p) {
325
if (p == NULL) { // free(NULL)
326
return true;
327
}
328
if (!_nmt_was_initialized) {
329
// pre-NMT-init:
330
// - the allocation must be in the hash map, since all allocations went through
331
// NMTPreInit::handle_malloc()
332
// - find the old entry, unhang from map, free it
333
NMTPreInitAllocation* a = find_and_remove_in_map(p);
334
NMTPreInitAllocation::do_free(a);
335
_num_frees_pre++;
336
return true;
337
} else {
338
// post-NMT-init:
339
// - look up (but not remove! lu table is read-only here.) the entry
340
// - if found, we do nothing: the lu table is readonly, so we keep the old address
341
// in the table. We leave the block allocated to prevent the libc from returning
342
// the same address and confusing us.
343
// - if not found, we let regular os::free() handle this pointer
344
if (find_in_map(p) != NULL) {
345
return true;
346
}
347
}
348
return false;
349
}
350
351
static void print_state(outputStream* st);
352
static void print_map(outputStream* st);
353
DEBUG_ONLY(static void verify();)
354
};
355
356
#endif // INCLUDE_NMT
357
358
#endif // SHARE_SERVICES_NMT_PREINIT_HPP
359
360
361