Clarifying jmethodID Usage and Potential JVM Crashes
daniel.daugherty at oracle.com
daniel.daugherty at oracle.com
Thu Jun 1 15:49:37 UTC 2023
Hmmm... seems like concurrent class unloading has revealed a situation
in some
code where class refs should have been held, but were not held because
it's very
difficult to do, and the code (mostly) got away with it for a long time...
David's quote from the JNI spec makes things very, very clear:
> "A field or method ID does not prevent the VM from unloading the
class from
> which the ID has been derived. After the class is unloaded, the method or
> field ID becomes invalid and may not be passed to any function taking
such
> an ID. The native code, therefore, must make sure to:
> - keep a live reference to the underlying class, or
> - recompute the method or field ID
>
> if it intends to use a method or field ID for an extended period of
time."
and the JVM/TI spec does depend on the JNI spec for certain pieces like the
semantics of jmethodID.
I agree with David that fixing this is going to be difficult and a major
effort which is why we've all only chipped at this boulder before...
Dan
On 6/1/23 4:28 AM, David Holmes wrote:
> On 1/06/2023 5:16 pm, Jaroslav Bachorík wrote:
>> Hi David,
>>
>> On Thu, Jun 1, 2023 at 3:56 AM David Holmes <david.holmes at oracle.com
>> <mailto:david.holmes at oracle.com>> wrote:
>>
>> Hi Jaroslav,
>>
>> On 31/05/2023 9:12 pm, Jaroslav Bachorík wrote:
>> > Dear Team,
>> >
>> > I've been investigating the unusual JVM crashes occurring in
>> JVMTI calls
>> > on a J9 JVM. During my investigation, I scrutinized the
>> `jmethodID`
>> > definition closely, available here: [jmethodID
>> >
>> definition](https://docs.oracle.com/en/java/javase/17/docs/specs/jvmti.html#jmethodID
>> <https://docs.oracle.com/en/java/javase/17/docs/specs/jvmti.html#jmethodID>
>> <https://docs.oracle.com/en/java/javase/17/docs/specs/jvmti.html#jmethodID
>> <https://docs.oracle.com/en/java/javase/17/docs/specs/jvmti.html#jmethodID>>).
>>
>> >
>> > To paraphrase, the definition suggests that `jmethodID`
>> identifies a
>> > Java method, initializer, or constructor. These identifiers, once
>> > returned by JVM TI functions and events, can be safely stored.
>> However,
>> > when the class is unloaded, they become invalid, rendering them
>> > unsuitable for use.
>> >
>> > My interpretation is that the JVMTI user should verify the
>> validity of a
>> > `jmethodID` value before using it to prevent potential crashes.
>> Would
>> > you agree with this interpretation?
>>
>> Not quite - as you note you can't verify the jmethodID validity.
>> What
>> the user needs to do, in line with what Dan was saying, is ensure
>> that
>> they keep track of the classes to which the methods belong and keep
>> them
>> alive if necessary. Now that may be easier said than done, but
>> that is
>> the gist of it. This comes from the JNI spec:
>>
>> "A field or method ID does not prevent the VM from unloading the
>> class
>> from which the ID has been derived. After the class is unloaded, the
>> method or field ID becomes invalid and may not be passed to any
>> function
>> taking such an ID. The native code, therefore, must make sure to:
>>
>> keep a live reference to the underlying class, or
>> recompute the method or field ID
>>
>> if it intends to use a method or field ID for an extended period of
>> time."
>>
>> > This sounds like a sensible requirement, but its practical
>> application
>> > remains unclear. As far as I know, methods can be unloaded
>> concurrently
>> > to the native code executing JVMTI functions. This introduces a
>> > potential race condition where the JVM unloads the methods during
>> the
>> > check->use flow, making it only a partial solution. To complicate
>> > matters further, no method exists to confirm whether a
>> `jmethodID` is valid.
>> >
>> > Theoretically, we could monitor the `CompiledMethodUnload`
>> event to
>> > track the validity state, creating a constantly expanding set of
>> > unloaded `jmethodID` values or a bloom filter, if one does not
>> care
>> > about few potential false positives. This strategy, however,
>> doesn't
>> > address the potential race condition, and it could even
>> exacerbate it
>> > due to possible event delays. This delay might mistakenly
>> validate a
>> > `jmethodID` value that has already been unloaded, but for
>> which the
>> > event hasn't been delivered yet.
>> >
>> > Honestly, I don't see a way to use `jmethodID` safely unless the
>> code
>> > using it suspends the entire JVM and doesn't resume until it's
>> finished
>> > with that `jmethodID`. Any other approach might lead to JVM
>> crashes, as
>> > we've observed with J9.
>> >
>> > Lastly, it's noteworthy that Hotspot takes meticulous measures to
>> ensure
>> > that using jmethodIDs for unloaded methods doesn't crash the
>> JVM and
>> > even provides useful information. This observation has led me to
>> > question whether the documentation aligns with the Hotspot
>> > implementation, especially given that following closely the
>> > documentation appears to increase the risk associated with the
>> use of
>> > `jmethodID` values.
>>
>> There have been folk who wanted to make this area more user-friendly
>> but
>> that shouldn't be mistaken for moving towards a world where
>> jmethodIDs
>> are always safe to use.
>>
>>
>> Yes, I see your point. Unfortunately, this confirms my worries that
>> using AsyncGetCallTrace (ASGCT) on a system strictly adhering to the
>> JVMTI spec of jmethoID is not really possible without risking random
>> and quite frequent crashes on systems with concurrent class unloading
>> enabled.
>> FTR, ASGCT will record the stack trace as a list of frames, each one
>> containing the corresponding jmethodID value. Considering that the
>> most common usage of ASGCT is in a signal handler it makes it
>> impossible to use JVMTI calls to resolve the holder class and create
>> a strong reference to prevent it from being unloaded.
>> And even if this would be possible we would need to figure out when
>> to release the class reference when it is no more needed - and it is
>> not really clear how we could do that reliably, leaving us with the
>> option of holding the class references indefinitely or risking
>> crashing JVM.
>
> I would have to agree you cannot use jmethodID's for that purpose, not
> without also recording (and keeping-alive) the associated classes. As
> to when you would clear such references I can't say as I don't know
> how the ASGCT stack record would be used.
>
>> I want to emphasize that not being able to resolve additional details
>> for a jmethodID pointing to a method of an unloaded class is not an
>> issue, as long as the JVMTI call does not crash. I think that
>> https://bugs.openjdk.org/browse/JDK-8268364
>> <https://bugs.openjdk.org/browse/JDK-8268364> did address exactly the
>> problem of concurrent class unloading causing races in the code that
>> is checking for validity of jmethodID and then using it.
>
> Yes but internal to the JVMTI implementation. The basic null check
> that was used was found to be insufficient when used with ZGC and so
> we would crash more often than was considered reasonable. There is a
> quoted comment in the code in
> https://bugs.openjdk.org/browse/JDK-8268088:
>
> // These aren't deallocated and are going to look like a leak, but
> that's
> // needed because we can't really get rid of jmethodIDs because we
> don't
> // know when native code is going to stop using them. The spec says
> that
> // they're "invalid" but existing programs likely rely on their being
> // NULL after class unloading.
>
> that kind of sums up the position of trying to accommodate "bad code"
> in a reasonable way.
>
>> Can this be summarize in a way that the user is not guaranteed to get
>> any additional information for an invalid jmethodID but it would be
>> really nice for JVM not to crash when jmethodID becomes invalid as
>> there is no way for the user to check for its validity in an atomic
>> manner
>> - and yes, even calling GetMethodDeclaringClass in order to obtain
>> the class one could create a strong reference is a subject to racy
>> behaviour so it really can not be used as a workaround.
>
> The only real solution IMO would be to encode jmethodIDs in a way that
> validity can be tracked and then have all JVMTI methods be able to
> check that validity and guarantee atomicity of the method such that
> the id can't become invalid whilst in use. Whether that is at all
> feasible/practical I do not know - but it sounds like a major effort
> to me.
>
> Cheers,
> David
>
>> Cheers,
>>
>> -JB-
>>
>>
>> Cheers,
>> David
>>
>> > I welcome your thoughts and perspectives on this matter.
>> >
>> > Best regards,
>> >
>> > Jaroslav
>>
More information about the serviceability-dev
mailing list