Skip to content

Commit d8f9b18

Browse files
committed
8268406: Deallocate jmethodID native memory
Reviewed-by: dholmes, sspitsyn, dcubed, eosterlund, aboldtch
1 parent aa26ced commit d8f9b18

File tree

16 files changed

+456
-295
lines changed

16 files changed

+456
-295
lines changed

src/hotspot/share/classfile/classLoaderData.cpp

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#include "memory/resourceArea.hpp"
6666
#include "memory/universe.hpp"
6767
#include "oops/access.inline.hpp"
68+
#include "oops/jmethodIDTable.hpp"
6869
#include "oops/klass.inline.hpp"
6970
#include "oops/oop.inline.hpp"
7071
#include "oops/oopHandle.inline.hpp"
@@ -578,6 +579,33 @@ void ClassLoaderData::remove_class(Klass* scratch_class) {
578579
ShouldNotReachHere(); // should have found this class!!
579580
}
580581

582+
void ClassLoaderData::add_jmethod_id(jmethodID mid) {
583+
MutexLocker m1(metaspace_lock(), Mutex::_no_safepoint_check_flag);
584+
if (_jmethod_ids == nullptr) {
585+
_jmethod_ids = new (mtClass) GrowableArray<jmethodID>(32, mtClass);
586+
}
587+
_jmethod_ids->push(mid);
588+
}
589+
590+
// Method::remove_jmethod_ids removes jmethodID entries from the table which
591+
// releases memory.
592+
// Because native code (e.g., JVMTI agent) holding jmethod_ids may access them
593+
// after the associated classes and class loader are unloaded, subsequent lookups
594+
// for these ids will return null since they are no longer found in the table.
595+
// The Java Native Interface Specification says "method ID
596+
// does not prevent the VM from unloading the class from which the ID has
597+
// been derived. After the class is unloaded, the method or field ID becomes
598+
// invalid".
599+
void ClassLoaderData::remove_jmethod_ids() {
600+
MutexLocker ml(JmethodIdCreation_lock, Mutex::_no_safepoint_check_flag);
601+
for (int i = 0; i < _jmethod_ids->length(); i++) {
602+
jmethodID mid = _jmethod_ids->at(i);
603+
JmethodIDTable::remove(mid);
604+
}
605+
delete _jmethod_ids;
606+
_jmethod_ids = nullptr;
607+
}
608+
581609
void ClassLoaderData::unload() {
582610
_unloading = true;
583611

@@ -599,19 +627,8 @@ void ClassLoaderData::unload() {
599627
// after erroneous classes are released.
600628
classes_do(InstanceKlass::unload_class);
601629

602-
// Method::clear_jmethod_ids only sets the jmethod_ids to null without
603-
// releasing the memory for related JNIMethodBlocks and JNIMethodBlockNodes.
604-
// This is done intentionally because native code (e.g. JVMTI agent) holding
605-
// jmethod_ids may access them after the associated classes and class loader
606-
// are unloaded. The Java Native Interface Specification says "method ID
607-
// does not prevent the VM from unloading the class from which the ID has
608-
// been derived. After the class is unloaded, the method or field ID becomes
609-
// invalid". In real world usages, the native code may rely on jmethod_ids
610-
// being null after class unloading. Hence, it is unsafe to free the memory
611-
// from the VM side without knowing when native code is going to stop using
612-
// them.
613630
if (_jmethod_ids != nullptr) {
614-
Method::clear_jmethod_ids(this);
631+
remove_jmethod_ids();
615632
}
616633
}
617634

@@ -1037,9 +1054,7 @@ void ClassLoaderData::print_on(outputStream* out) const {
10371054
out->print_cr(" - dictionary " INTPTR_FORMAT, p2i(_dictionary));
10381055
}
10391056
if (_jmethod_ids != nullptr) {
1040-
out->print (" - jmethod count ");
1041-
Method::print_jmethod_ids_count(this, out);
1042-
out->print_cr("");
1057+
out->print_cr(" - jmethod count %d", _jmethod_ids->length());
10431058
}
10441059
out->print_cr(" - deallocate list " INTPTR_FORMAT, p2i(_deallocate_list));
10451060
out->print_cr(" - next CLD " INTPTR_FORMAT, p2i(_next));

src/hotspot/share/classfile/classLoaderData.hpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -53,7 +53,6 @@
5353
// and provides iterators for root tracing and other GC operations.
5454

5555
class ClassLoaderDataGraph;
56-
class JNIMethodBlock;
5756
class ModuleEntry;
5857
class PackageEntry;
5958
class ModuleEntryTable;
@@ -143,10 +142,9 @@ class ClassLoaderData : public CHeapObj<mtClass> {
143142
ModuleEntry* _unnamed_module; // This class loader's unnamed module.
144143
Dictionary* _dictionary; // The loaded InstanceKlasses, including initiated by this class loader
145144

146-
// These method IDs are created for the class loader and set to null when the
147-
// class loader is unloaded. They are rarely freed, only for redefine classes
148-
// and if they lose a data race in InstanceKlass.
149-
JNIMethodBlock* _jmethod_ids;
145+
// These method IDs are created for the class loader and removed when the
146+
// class loader is unloaded.
147+
GrowableArray<jmethodID>* _jmethod_ids;
150148

151149
// Metadata to be deallocated when it's safe at class unloading, when
152150
// this class loader isn't unloaded itself.
@@ -316,8 +314,9 @@ class ClassLoaderData : public CHeapObj<mtClass> {
316314
void classes_do(KlassClosure* klass_closure);
317315
Klass* klasses() { return _klasses; }
318316

319-
JNIMethodBlock* jmethod_ids() const { return _jmethod_ids; }
320-
void set_jmethod_ids(JNIMethodBlock* new_block) { _jmethod_ids = new_block; }
317+
void add_jmethod_id(jmethodID id);
318+
void remove_jmethod_ids();
319+
GrowableArray<jmethodID>* jmethod_ids() const { return _jmethod_ids; }
321320

322321
void print() const;
323322
void print_on(outputStream* out) const PRODUCT_RETURN;

src/hotspot/share/memory/universe.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "oops/compressedOops.hpp"
6161
#include "oops/instanceKlass.hpp"
6262
#include "oops/instanceMirrorKlass.hpp"
63+
#include "oops/jmethodIDTable.hpp"
6364
#include "oops/klass.inline.hpp"
6465
#include "oops/objArrayOop.inline.hpp"
6566
#include "oops/objLayout.hpp"
@@ -436,6 +437,9 @@ void Universe::genesis(TRAPS) {
436437

437438
vmSymbols::initialize();
438439

440+
// Initialize table for matching jmethodID, before SystemDictionary.
441+
JmethodIDTable::initialize();
442+
439443
SystemDictionary::initialize(CHECK);
440444

441445
// Create string constants
@@ -444,7 +448,6 @@ void Universe::genesis(TRAPS) {
444448
s = StringTable::intern("-2147483648", CHECK);
445449
_the_min_jint_string = OopHandle(vm_global(), s);
446450

447-
448451
#if INCLUDE_CDS
449452
if (CDSConfig::is_using_archive()) {
450453
// Verify shared interfaces array.

src/hotspot/share/nmt/memTag.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -58,6 +58,7 @@
5858
f(mtMetaspace, "Metaspace") \
5959
f(mtStringDedup, "String Deduplication") \
6060
f(mtObjectMonitor, "Object Monitors") \
61+
f(mtJNI, "JNI") \
6162
f(mtNone, "Unknown") \
6263
//end
6364

src/hotspot/share/oops/instanceKlass.cpp

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,13 +2395,26 @@ jmethodID InstanceKlass::update_jmethod_id(jmethodID* jmeths, Method* method, in
23952395
return new_id;
23962396
}
23972397

2398+
// Allocate the jmethodID cache.
2399+
static jmethodID* create_jmethod_id_cache(size_t size) {
2400+
jmethodID* jmeths = NEW_C_HEAP_ARRAY(jmethodID, size + 1, mtClass);
2401+
memset(jmeths, 0, (size + 1) * sizeof(jmethodID));
2402+
// cache size is stored in element[0], other elements offset by one
2403+
jmeths[0] = (jmethodID)size;
2404+
return jmeths;
2405+
}
2406+
2407+
// When reading outside a lock, use this.
2408+
jmethodID* InstanceKlass::methods_jmethod_ids_acquire() const {
2409+
return Atomic::load_acquire(&_methods_jmethod_ids);
2410+
}
2411+
2412+
void InstanceKlass::release_set_methods_jmethod_ids(jmethodID* jmeths) {
2413+
Atomic::release_store(&_methods_jmethod_ids, jmeths);
2414+
}
2415+
23982416
// Lookup or create a jmethodID.
2399-
// This code is called by the VMThread and JavaThreads so the
2400-
// locking has to be done very carefully to avoid deadlocks
2401-
// and/or other cache consistency problems.
2402-
//
2403-
jmethodID InstanceKlass::get_jmethod_id(const methodHandle& method_h) {
2404-
Method* method = method_h();
2417+
jmethodID InstanceKlass::get_jmethod_id(Method* method) {
24052418
int idnum = method->method_idnum();
24062419
jmethodID* jmeths = methods_jmethod_ids_acquire();
24072420

@@ -2422,15 +2435,12 @@ jmethodID InstanceKlass::get_jmethod_id(const methodHandle& method_h) {
24222435

24232436
if (jmeths == nullptr) {
24242437
MutexLocker ml(JmethodIdCreation_lock, Mutex::_no_safepoint_check_flag);
2425-
jmeths = methods_jmethod_ids_acquire();
2438+
jmeths = _methods_jmethod_ids;
24262439
// Still null?
24272440
if (jmeths == nullptr) {
24282441
size_t size = idnum_allocated_count();
24292442
assert(size > (size_t)idnum, "should already have space");
2430-
jmeths = NEW_C_HEAP_ARRAY(jmethodID, size + 1, mtClass);
2431-
memset(jmeths, 0, (size + 1) * sizeof(jmethodID));
2432-
// cache size is stored in element[0], other elements offset by one
2433-
jmeths[0] = (jmethodID)size;
2443+
jmeths = create_jmethod_id_cache(size);
24342444
jmethodID new_id = update_jmethod_id(jmeths, method, idnum);
24352445

24362446
// publish jmeths
@@ -2460,10 +2470,7 @@ void InstanceKlass::update_methods_jmethod_cache() {
24602470
if (old_size < size + 1) {
24612471
// Allocate a larger one and copy entries to the new one.
24622472
// They've already been updated to point to new methods where applicable (i.e., not obsolete).
2463-
jmethodID* new_cache = NEW_C_HEAP_ARRAY(jmethodID, size + 1, mtClass);
2464-
memset(new_cache, 0, (size + 1) * sizeof(jmethodID));
2465-
// The cache size is stored in element[0]; the other elements are offset by one.
2466-
new_cache[0] = (jmethodID)size;
2473+
jmethodID* new_cache = create_jmethod_id_cache(size);
24672474

24682475
for (int i = 1; i <= (int)old_size; i++) {
24692476
new_cache[i] = cache[i];
@@ -2474,24 +2481,30 @@ void InstanceKlass::update_methods_jmethod_cache() {
24742481
}
24752482
}
24762483

2477-
// Figure out how many jmethodIDs haven't been allocated, and make
2478-
// sure space for them is pre-allocated. This makes getting all
2479-
// method ids much, much faster with classes with more than 8
2484+
// Make a jmethodID for all methods in this class. This makes getting all method
2485+
// ids much, much faster with classes with more than 8
24802486
// methods, and has a *substantial* effect on performance with jvmti
24812487
// code that loads all jmethodIDs for all classes.
2482-
void InstanceKlass::ensure_space_for_methodids(int start_offset) {
2483-
int new_jmeths = 0;
2488+
void InstanceKlass::make_methods_jmethod_ids() {
2489+
MutexLocker ml(JmethodIdCreation_lock, Mutex::_no_safepoint_check_flag);
2490+
jmethodID* jmeths = _methods_jmethod_ids;
2491+
if (jmeths == nullptr) {
2492+
jmeths = create_jmethod_id_cache(idnum_allocated_count());
2493+
release_set_methods_jmethod_ids(jmeths);
2494+
}
2495+
24842496
int length = methods()->length();
2485-
for (int index = start_offset; index < length; index++) {
2497+
for (int index = 0; index < length; index++) {
24862498
Method* m = methods()->at(index);
2487-
jmethodID id = m->find_jmethod_id_or_null();
2488-
if (id == nullptr) {
2489-
new_jmeths++;
2499+
int idnum = m->method_idnum();
2500+
assert(!m->is_old(), "should not have old methods or I'm confused");
2501+
jmethodID id = Atomic::load_acquire(&jmeths[idnum + 1]);
2502+
if (!m->is_overpass() && // skip overpasses
2503+
id == nullptr) {
2504+
id = Method::make_jmethod_id(class_loader_data(), m);
2505+
Atomic::release_store(&jmeths[idnum + 1], id);
24902506
}
24912507
}
2492-
if (new_jmeths != 0) {
2493-
Method::ensure_jmethod_ids(class_loader_data(), new_jmeths);
2494-
}
24952508
}
24962509

24972510
// Lookup a jmethodID, null if not found. Do no blocking, no allocations, no handles
@@ -2923,7 +2936,7 @@ void InstanceKlass::release_C_heap_structures(bool release_sub_metadata) {
29232936
JNIid::deallocate(jni_ids());
29242937
set_jni_ids(nullptr);
29252938

2926-
jmethodID* jmeths = methods_jmethod_ids_acquire();
2939+
jmethodID* jmeths = _methods_jmethod_ids;
29272940
if (jmeths != nullptr) {
29282941
release_set_methods_jmethod_ids(nullptr);
29292942
FreeHeap(jmeths);
@@ -4275,17 +4288,14 @@ bool InstanceKlass::should_clean_previous_versions_and_reset() {
42754288
return ret;
42764289
}
42774290

4278-
// This nulls out jmethodIDs for all methods in 'klass'
4279-
// It needs to be called explicitly for all previous versions of a class because these may not be cleaned up
4280-
// during class unloading.
4281-
// We can not use the jmethodID cache associated with klass directly because the 'previous' versions
4282-
// do not have the jmethodID cache filled in. Instead, we need to lookup jmethodID for each method and this
4283-
// is expensive - O(n) for one jmethodID lookup. For all contained methods it is O(n^2).
4284-
// The reason for expensive jmethodID lookup for each method is that there is no direct link between method and jmethodID.
4285-
void InstanceKlass::clear_jmethod_ids(InstanceKlass* klass) {
4291+
// This nulls out the jmethodID for all obsolete methods in the previous version of the 'klass'.
4292+
// These obsolete methods only exist in the previous version and we're about to delete the memory for them.
4293+
// The jmethodID for these are deallocated when we unload the class, so this doesn't remove them from the table.
4294+
void InstanceKlass::clear_obsolete_jmethod_ids(InstanceKlass* klass) {
42864295
Array<Method*>* method_refs = klass->methods();
42874296
for (int k = 0; k < method_refs->length(); k++) {
42884297
Method* method = method_refs->at(k);
4298+
// Only need to clear obsolete methods.
42894299
if (method != nullptr && method->is_obsolete()) {
42904300
method->clear_jmethod_id();
42914301
}
@@ -4335,7 +4345,7 @@ void InstanceKlass::purge_previous_version_list() {
43354345
// Unlink from previous version list.
43364346
assert(pv_node->class_loader_data() == loader_data, "wrong loader_data");
43374347
InstanceKlass* next = pv_node->previous_versions();
4338-
clear_jmethod_ids(pv_node); // jmethodID maintenance for the unloaded class
4348+
clear_obsolete_jmethod_ids(pv_node); // jmethodID maintenance for the unloaded class
43394349
pv_node->link_previous_versions(nullptr); // point next to null
43404350
last->link_previous_versions(next);
43414351
// Delete this node directly. Nothing is referring to it and we don't

src/hotspot/share/oops/instanceKlass.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -781,8 +781,8 @@ class InstanceKlass: public Klass {
781781
u2 method_index);
782782

783783
// jmethodID support
784-
jmethodID get_jmethod_id(const methodHandle& method_h);
785-
void ensure_space_for_methodids(int start_offset = 0);
784+
jmethodID get_jmethod_id(Method* method);
785+
void make_methods_jmethod_ids();
786786
jmethodID jmethod_id_or_null(Method* method);
787787
void update_methods_jmethod_cache();
788788

@@ -1056,10 +1056,10 @@ class InstanceKlass: public Klass {
10561056
Atomic::store(&_init_thread, thread);
10571057
}
10581058

1059-
inline jmethodID* methods_jmethod_ids_acquire() const;
1060-
inline void release_set_methods_jmethod_ids(jmethodID* jmeths);
1061-
// This nulls out jmethodIDs for all methods in 'klass'
1062-
static void clear_jmethod_ids(InstanceKlass* klass);
1059+
jmethodID* methods_jmethod_ids_acquire() const;
1060+
void release_set_methods_jmethod_ids(jmethodID* jmeths);
1061+
// This nulls out obsolete jmethodIDs for all methods in 'klass'.
1062+
static void clear_obsolete_jmethod_ids(InstanceKlass* klass);
10631063
jmethodID update_jmethod_id(jmethodID* jmeths, Method* method, int idnum);
10641064

10651065
public:

src/hotspot/share/oops/instanceKlass.inline.hpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -71,14 +71,6 @@ inline void InstanceKlass::release_set_array_klasses(ObjArrayKlass* k) {
7171
Atomic::release_store(&_array_klasses, k);
7272
}
7373

74-
inline jmethodID* InstanceKlass::methods_jmethod_ids_acquire() const {
75-
return Atomic::load_acquire(&_methods_jmethod_ids);
76-
}
77-
78-
inline void InstanceKlass::release_set_methods_jmethod_ids(jmethodID* jmeths) {
79-
Atomic::release_store(&_methods_jmethod_ids, jmeths);
80-
}
81-
8274
// The iteration over the oops in objects is a hot path in the GC code.
8375
// By force inlining the following functions, we get similar GC performance
8476
// as the previous macro based implementation.

0 commit comments

Comments
 (0)