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