byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

sun.misc.Unsafe deprecations in Java 24

Open eygraber opened this issue 8 months ago • 5 comments

I started getting a lot of deprecation warnings after updating to Java 24.

WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by net.bytebuddy.dynamic.loading.ClassInjector$UsingUnsafe$Dispatcher$CreationAction (file:/home/eli/.gradle/caches/8.14/transforms/80f1719e28957c8fbfa3503f6a13649f/transformed/byte-buddy-1.17.5.jar)
WARNING: Please consider reporting this to the maintainers of class net.bytebuddy.dynamic.loading.ClassInjector$UsingUnsafe$Dispatcher$CreationAction
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release

eygraber avatar Apr 25 '25 17:04 eygraber

This means that Byte Buddy is likely used in a way that uses Unsafe. Typically for loading classes. This can likely be substituted by using method handles, but this is responsibility of the user.

I'd suggest investigating the used ClassLoadingStrategy.

raphw avatar Apr 26 '25 09:04 raphw

Oh interesting, so it's being logged as coming from Byte Buddy, but it isn't actually?

eygraber avatar Apr 27 '25 02:04 eygraber

It's used by net.bytebuddy.dynamic.loading.ClassInjector$UsingUnsafe, but it can of course be resolved by using a different ClassInjector.

raphw avatar Apr 27 '25 17:04 raphw

Some of ByteBuddy's default configurations implicitly use UsingReflection, which can be seen from the following call path:

new AgentBuilder.Default() -> 
new InitializationStrategy.SelfInjection.Split() -> 
new NexusAccess() -> 
NexusAccess.<clinit> -> 
private static final Dispatcher DISPATCHER = doPrivileged(Dispatcher.CreationAction.INSTANCE) -> 
new ClassInjector.UsingReflection()

When users use new AgentBuilder.Default(), the API itself doesn’t make it obvious that it will eventually rely on Unsafe.

Even if the user tries to express intent to avoid those APIs (e.g., via disableClassFormats), the underlying methods are still called during object construction.

This leaves users with no choice but to dig into the internals and potentially extend AgentBuilder.Default and override its constructor just to work around it.

After reviewing the comments before, it seems most of the warnings point to the use of objectFieldOffset. Upon investigation, ByteBuddy uses this to determine the memory layout of the AccessibleObject.offset, and then sets it via Unsafe to bypass modern JVM restrictions on reflective access. From what I can tell, this logic was likely introduced due to JPMS. Because of module visibility constraints, ByteBuddy gave up on using Field.setAccessible altogether.

However, I think this behavior could be improved—perhaps ByteBuddy should attempt setAccessible first before pessimistically falling back to Unsafe. After all, users can make the necessary JPMS adjustments via --add-opens or at runtime using Instrumentation.redefineModule, which would allow setAccessible to work without error.

To go a step further: if ByteBuddy detects the presence of an Instrumentation instance, maybe it could silently make the required module visibility changes on its own to allow access to jdk.internal.misc.

Finally, even with the latest Java versions, I personally think it's a bit unfortunate to completely abandon UsingUnsafe. Compared to UsingLookup, which can only inject classes into existing packages, UsingUnsafe allows injecting into entirely new packages. In that sense, UsingUnsafe is still the more powerful option.

dogourd avatar May 06 '25 03:05 dogourd

You can set -Dnet.bytebuddy.safe=true already to avoid Byte Buddy making an attempt. Once the possibility goes away, Byte Buddy will simply fail upon attempting to use reflection, then you will have to override manually.

raphw avatar May 06 '25 09:05 raphw