RFR: 8268406: Deallocate jmethodID native memory
Daniel D. Daugherty
dcubed at openjdk.org
Fri Jun 13 20:44:43 UTC 2025
On Fri, 16 May 2025 12:18:42 GMT, Coleen Phillimore <coleenp at openjdk.org> wrote:
> This change uses a ConcurrentHashTable to associate Method* with jmethodID, instead of an indirection. JNI is deprecated in favor of using Panama to call methods, so I don't think we're concerned about JNI performance going forward. JVMTI uses a lot of jmethodIDs but there aren't any performance tests for JVMTI, but running vmTestbase/nsk/jvmti with in product build with and without this change had no difference in time.
>
> The purpose of this change is to remove the memory leak when you unload classes: we were leaving the jmethodID memory just in case JVMTI code still had references to that jmethodID and instead of crashing, should get nullptr. With this change, if JVMTI looks up a jmethodID, we've removed it from the table and will return nullptr. Redefinition and the InstanceKlass::_jmethod_method_ids is somewhat complicated. When a method becomes "obsolete" in redefinition, which means that the code in the method is changed, afterward creating a jmethodID from an "obsolete" method will create a new entry in the InstanceKlass table. This mechanism increases the method_idnum to do this. In the future maybe we could throw NoSuchMethodError if you try to create a jmethodID out of an obsolete method and remove all this code. But that's not in this change.
>
> Tested with tier1-4, 5-7.
Changes requested by dcubed (Reviewer).
src/hotspot/share/classfile/classLoaderData.cpp line 585:
> 583: MutexLocker m1(metaspace_lock(), Mutex::_no_safepoint_check_flag);
> 584: if (_jmethod_ids == nullptr) {
> 585: _jmethod_ids = new (mtClass) GrowableArray<jmethodID>(32, mtClass);
Do you want the literal `32` to be a tunable value?
src/hotspot/share/classfile/classLoaderData.cpp line 590:
> 588: }
> 589:
> 590: // Method::clear_jmethod_ids removes jmethodID entries from the table which
Perhaps the name changed during your development?
s/clear_jmethod_ids/remove_jmethod_ids/
src/hotspot/share/classfile/classLoaderData.cpp line 592:
> 590: // Method::clear_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
grammar: s/e.g./e.g.,/
src/hotspot/share/classfile/classLoaderData.cpp line 594:
> 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.
Perhaps: s/null/nullptr/
src/hotspot/share/classfile/classLoaderData.hpp line 319:
> 317: void add_jmethod_id(jmethodID id);
> 318: void remove_jmethod_ids();
> 319: GrowableArray<jmethodID>* jmethod_ids() { return _jmethod_ids; }
Should `jmethod_ids` still be `const`?
src/hotspot/share/oops/instanceKlass.cpp line 2394:
> 2392: }
> 2393:
> 2394: // Lookup or create a jmethodID.
The comment on L2394 appears wrong for `create_jmethod_id_cache`.
Perhaps move it to L2404 (above get_jmethod_id() function).
src/hotspot/share/oops/instanceKlass.cpp line 2397:
> 2395: static jmethodID* create_jmethod_id_cache(size_t size) {
> 2396: jmethodID* jmeths = NEW_C_HEAP_ARRAY(jmethodID, size+1, mtClass);
> 2397: memset(jmeths, 0, (size+1)*sizeof(jmethodID));
nit spacing: s/size+1/size + 1/
on two lines.
src/hotspot/share/oops/instanceKlass.cpp line 2402:
> 2400: return jmeths;
> 2401: }
> 2402:
nit spacing: delete extra blank line?
src/hotspot/share/oops/instanceKlass.cpp line 2404:
> 2402:
> 2403:
> 2404: jmethodID InstanceKlass::get_jmethod_id(Method* method) {
Should `method` be `const`?
src/hotspot/share/oops/instanceKlass.cpp line 2460:
> 2458: size_t size = idnum_allocated_count();
> 2459: size_t old_size = (size_t)cache[0];
> 2460: if (old_size < size+1) {
nit spacing: s/size+1/size + 1/
src/hotspot/share/oops/instanceKlass.cpp line 2461:
> 2459: size_t old_size = (size_t)cache[0];
> 2460: if (old_size < size+1) {
> 2461: // allocate a larger one and copy entries to the new one.
nit typo: s/allocate/Allocate/
src/hotspot/share/oops/instanceKlass.cpp line 2462:
> 2460: if (old_size < size+1) {
> 2461: // allocate a larger one and copy entries to the new one.
> 2462: // They've already been updated to point to new methods where applicable (ie. not obsolete)
nit typo: s/(ie. not obsolete)/(i.e., not obsolete)./
src/hotspot/share/oops/instanceKlass.cpp line 2495:
> 2493: id == nullptr) {
> 2494: id = Method::make_jmethod_id(class_loader_data(), m);
> 2495: Atomic::release_store(&jmeths[idnum+1], id);
nit spacing: s/size+1/size + 1/
src/hotspot/share/oops/instanceKlass.hpp line 1057:
> 1055: inline jmethodID* methods_jmethod_ids_acquire() const;
> 1056: inline void release_set_methods_jmethod_ids(jmethodID* jmeths);
> 1057: // This nulls out obsolete jmethodIDs for all methods in 'klass'
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.cpp line 29:
> 27: #include "memory/resourceArea.hpp"
> 28: #include "oops/method.hpp"
> 29: #include "oops/jmethodIDTable.hpp"
Please swap these two #includes into sort order.
src/hotspot/share/oops/jmethodIDTable.cpp line 35:
> 33: #include "utilities/macros.hpp"
> 34:
> 35: // Save (jmethod, Method*) in a hashtable to lookup Method
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.cpp line 36:
> 34:
> 35: // Save (jmethod, Method*) in a hashtable to lookup Method
> 36: // The CHT is for performance because it is has lock free lookup.
typo: s/because it is has lock free/because it has lock free/
src/hotspot/share/oops/jmethodIDTable.cpp line 73:
> 71: // 2^24 is max size
> 72: const size_t end_size = 24;
> 73: // If a chain gets to 32 something might be wrong
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.cpp line 98:
> 96:
> 97: static JmethodEntry* get_jmethod_entry(jmethodID mid) {
> 98: assert(mid != nullptr, "JNI method id should not be null");
Perhaps: s/null/nullptr/
I can't remember if assert failure text output is okay to be `null`.
src/hotspot/share/oops/jmethodIDTable.cpp line 104:
> 102: JmethodEntry* result = nullptr;
> 103: auto get = [&] (JmethodEntry* value) {
> 104: // function called if value is found so is never null
Perhaps: s/null/nullptr/
src/hotspot/share/oops/jmethodIDTable.cpp line 109:
> 107: bool needs_rehashing = false;
> 108: _jmethod_id_table->get(current, lookup, get, &needs_rehashing);
> 109: assert (!needs_rehashing, "should never need rehashing");
nit spacing: s/assert (/assert(/
src/hotspot/share/oops/jmethodIDTable.cpp line 129:
> 127: }
> 128:
> 129: // Add a method id to the jmethod_ids
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.cpp line 131:
> 129: // Add a method id to the jmethod_ids
> 130: jmethodID JmethodIDTable::make_jmethod_id(Method* m) {
> 131: bool grow_hint, clean_hint, created;
nit: sort local variables?
src/hotspot/share/oops/jmethodIDTable.cpp line 144:
> 142: log_debug(jmethod)("Inserted jmethod id " UINT64_FORMAT_X, _jmethodID_counter);
> 143:
> 144: // Resize table if it needs to grow. The _jmethod_id_table has a good distribution
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.cpp line 158:
> 156: JmethodEntry* result;
> 157: auto get = [&] (JmethodEntry* value) {
> 158: // function called if value is found so is never null
Perhaps: s/null/nullptr/
src/hotspot/share/oops/jmethodIDTable.cpp line 162:
> 160: };
> 161: bool removed = _jmethod_id_table->remove(current, lookup, get);
> 162: assert(removed, "should be");
"should be" or "must be"?
src/hotspot/share/oops/jmethodIDTable.cpp line 169:
> 167: assert_locked_or_safepoint(JmethodIdCreation_lock);
> 168: JmethodEntry* result = get_jmethod_entry(jmid);
> 169: // change to table to point to the new method
Perhaps: // Change to table entry to point to the new method.
src/hotspot/share/oops/jmethodIDTable.cpp line 180:
> 178: // We need to make sure that jmethodID actually resolves to this method
> 179: // - multiple redefined versions may share jmethodID slots and if a method
> 180: // has already been rewired to a newer version we could be removing reference
typo?: s/could be removing reference/could be clearing a reference/
src/hotspot/share/oops/jmethodIDTable.hpp line 31:
> 29: #include "memory/allocation.hpp"
> 30:
> 31: // Class for associating Method with jmethodID
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.hpp line 38:
> 36: static void initialize();
> 37:
> 38: // Given a Method return a jmethodID
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.hpp line 41:
> 39: static jmethodID make_jmethod_id(Method* m);
> 40:
> 41: // Given a jmethodID, return a Method
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/jmethodIDTable.hpp line 45:
> 43:
> 44: // Class unloading support, remove the associations from the tables. Stale jmethodID will
> 45: // not be found and return null
nit typo: please add an ending period to the comment.
Perhaps: s/null/nullptr/
src/hotspot/share/oops/method.cpp line 2063:
> 2061:
> 2062: // jmethodID handling
> 2063: // jmethodIDs are 64-bit integers that will never run out and are mapped in a table
Should we have a `guarantee` or `assert` somewhere that the counter never wraps?
src/hotspot/share/oops/method.cpp line 2065:
> 2063: // jmethodIDs are 64-bit integers that will never run out and are mapped in a table
> 2064: // to their Method and vice versa. If JNI code has access to stale jmethodID, this
> 2065: // wastes no memory but the Method* returned is null
Perhaps: s/null/nullptr/
src/hotspot/share/oops/method.cpp line 2076:
> 2074: assert(jmid != nullptr, "must be created");
> 2075:
> 2076: // Add to growable array in CLD
nit typo: please add an ending period to the comment.
src/hotspot/share/oops/method.cpp line 2088:
> 2086: // Get the Method out of the table given the method id.
> 2087: Method* Method::resolve_jmethod_id(jmethodID mid) {
> 2088: assert(mid != nullptr, "JNI method id should not be null");
Perhaps: s/null/nullptr/
src/hotspot/share/oops/method.hpp line 709:
> 707: // Use resolve_jmethod_id() in situations where the caller is expected
> 708: // to provide a valid jmethodID; the only sanity checks are in asserts;
> 709: // result guaranteed not to be null.
Perhaps: s/null/nullptr/
src/hotspot/share/oops/method.hpp line 713:
> 711:
> 712: // Use checked_resolve_jmethod_id() in situations where the caller
> 713: // should provide a valid jmethodID, but might not. Null is returned
Perhaps: s/Null/Nullptr/
src/hotspot/share/prims/jvmtiEnv.cpp line 2773:
> 2771: int skipped = 0; // skip overpass methods
> 2772:
> 2773: // Make jmethodIDs for all non-overpass methods
nit typo: please add an ending period to the comment.
src/hotspot/share/prims/jvmtiEnv.cpp line 2789:
> 2787: }
> 2788: jmethodID id = m->find_jmethod_id_or_null();
> 2789: assert (id != nullptr, "should be created above");
nit spacing: s/assert (/assert(/
src/hotspot/share/runtime/mutexLocker.cpp line 236:
> 234: MUTEX_DEFN(Notification_lock , PaddedMonitor, service); // used for notification thread operations
> 235:
> 236: MUTEX_DEFN(JmethodIdCreation_lock , PaddedMutex , nosafepoint-1); // used for creating jmethodIDs.
Interesting. Why change from `nosafepoint-2` to `nosafepoint-1`?
-------------
PR Review: https://git.openjdk.org/jdk/pull/25267#pullrequestreview-2926153300
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145955175
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145963559
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145960811
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145967055
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145984911
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145989746
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145993097
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145994055
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146005043
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145996873
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145997702
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2145998880
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146002943
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146006426
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146007219
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146007725
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146008289
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146011312
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146013418
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146029756
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146014609
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146018654
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146016370
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146017327
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146029142
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146019950
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146022181
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146024577
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146025893
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146026406
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146026860
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146029258
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146037800
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146031309
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146039166
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146040503
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146045442
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146047075
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146049138
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146050371
PR Review Comment: https://git.openjdk.org/jdk/pull/25267#discussion_r2146052800
More information about the hotspot-dev
mailing list