Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate off Unsafe #6806

Closed
koundinyarvs opened this issue Oct 25, 2023 · 16 comments
Closed

Migrate off Unsafe #6806

koundinyarvs opened this issue Oct 25, 2023 · 16 comments

Comments

@koundinyarvs
Copy link

The following classes are using sun.misc.Unsafe

com.google.common.cache.Striped64
com.google.common.hash.LittleEndianByteArray
com.google.common.primitives.UnsignedBytes
com.google.common.util.concurrent.AbstractFuture

class sun.misc.Unsafe is a jdk internal class. package sun.misc is jdk internal after jdk 8. In jdk16, --illegal-access=deny was default. This option is ignored in jdk 17. usage context: run method declaration (return type) 

Refer http://openjdk.java.net/jeps/260

@cpovirk
Copy link
Member

cpovirk commented Oct 25, 2023

Can you provide some information about what problem you're seeing? We have included fallback implementations for cases in which Unsafe is not available—except, it appears, perhaps not for Striped64. I would expect for those normally to automatically kick in. Are you using some kind of static analyzer that flags the Unsafe usages?

@koundinyarvs
Copy link
Author

Hi @cpovirk

Yes we are using a static analyzer to see what are the packages that java 17 doesn't like to expose usage of CLASS sun.misc.Unsafe and it is identifying
Striped64
LittleEndianByteArray
UnsignedBytes
AbstractFuture
As some classes in guava which are using sun.misc.Unsafe and this needs to be avoided
The analyzer is complaining that

class sun.misc.Unsafe is a jdk internal class. package sun.misc is jdk internal after jdk 8. In jdk16, --illegal-access=deny was default. This option is ignored in jdk 17. usage context: sun.misc.Unsafe.getUnsafe call

@cpovirk
Copy link
Member

cpovirk commented Oct 28, 2023

Is there something that we can put on our code to suppress the static analyzer for the classes that have a fallback implementation? If we can do that without adding any dependencies, then we'd probably do it.

We probably should look at Striped64. (Note that it's part of common.cache, and as always, we encourage people to use Caffeine for caching nowadays.)

@cpovirk
Copy link
Member

cpovirk commented Nov 21, 2023

On the Striped64 front:

The ViewCVS frontend for the jsr166 repo has been offline for a while now, I believe. But the command-line instructions still work. The repo includes a copy of Striped64 that uses MethodHandle+VarHandle, so we could use that if we don't want to figure things out ourselves.

Or perhaps we just want to use LongAdder? As noted, that might cause trouble for GWT/J2CL, but we could use supersource. As also hinted there, it would cause trouble for Android, too, if it were the only approach (since the class isn't available there until API Level 24). But it would be fine with a fallback to Unsafe.

@marcphilipp
Copy link

JDK 24-ea+26 or later now issues this warning by default (see MNG-8399 for all of them):

WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/home/marc/.asdf/installs/maven/3.9.9/lib/guava-33.2.1-jre.jar)
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release 

For now, it can be suppressed using --sun-misc-unsafe-memory-access=allow but according to JEP 498 that's going to change in "JDK 26 or later".

@cpovirk
Copy link
Member

cpovirk commented Dec 4, 2024

Thanks. Our current JDK@head project is SecurityManager, which you've probably seen produce similar warnings :) I imagine that this one will be in our sights soon. We'll compare performance of the various AtomicHelper implementations under various JDKs and then see if we should reorder them, introduce a new one (VarHandle-based?), or what.

@cpovirk
Copy link
Member

cpovirk commented Dec 4, 2024

We have an existing benchmark (in Caliper, sadly, rather than JMH) for single-threaded use of AbstractFuture. The difference between the existing Unsafe implementation and the existing AtomicReferenceFieldUpdater version is mostly hard to detect in the noise, but Unsafe has a noticeable advantage for a benchmark that merely repeatedly creates, sets, and gets. (Both implementations are at least clearly better than the fallback synchronized-based implementation.)

The only immediate takeaway from that is that we can't just flip our default to the AtomicReferenceFieldUpdater implementation today. But we can still eventually see about using VarHandle, and we can see about making our AtomicReferenceFieldUpdater-typed fields static, which I believe should allow for better optimization.

@cpovirk
Copy link
Member

cpovirk commented Dec 4, 2024

static does appear to eliminate the performance difference in the simple test. Further benchmarking is of course needed. [edit: I wonder if I messed this up? Comments in the source suggest that I should have seen access problems that forced fallback to the synchronized implementation, but then I didn't see results that slow.... edit edit: Ah, I'll bet that I saw the problems only under external CI, since we still use -source 8 -target 8 there. Compare this comment.]

Another thing for us to remember is that the way we try to initialize the various helpers with try-catch is probably wrong. I would hope that defaulting to our AtomicReferenceFieldUpdater implementation would actually be the safest move there, since that implementation should never fail to load (aside from the old Samsung bug—though perhaps it could make things worse there?).

@cpovirk
Copy link
Member

cpovirk commented Dec 4, 2024

Initial benchmarks for VarHandle also look promising. However, they do make me worry more about the try-catch approach, since an attempt to use VarHandle under Java 8 would fail. We could probably still do it if we guard it with reflection to look at whether VarHandle exists.

@ben-manes
Copy link
Contributor

ben-manes commented Dec 4, 2024

AtomicReferenceFieldUpdater became much faster in OpenJDK's lead up to VarHandles, whereas in Java 5 it was slow due to reflection and allocations at every invocation, iirc. If that helps here is Shiplev's discussion on it using JHM:
Faster Atomic*FieldUpdaters for Everyone

copybara-service bot pushed a commit that referenced this issue Dec 12, 2024
(followup cl/705490132)

The method requires JDK 9+ to build, but our Maven build [always builds with JDK 23](#6549 (comment)). It also requires JDK 9+ to _run_, so I've added code to skip running the test under older versions.

Much of my goal here is to shake out any ways that we might cause problems if we begin conditionally using Java 9+ APIs more widely, such as [for `VarHandle`](#6806 (comment)).

RELNOTES=n/a
PiperOrigin-RevId: 705498400
copybara-service bot pushed a commit that referenced this issue Dec 12, 2024
(followup cl/705490132)

The method requires JDK 9+ to build, but our Maven build [always builds with JDK 23](#6549 (comment)). It also requires JDK 9+ to _run_, so I've added code to skip running the test under older versions.

Much of my goal here is to shake out any ways that we might cause problems if we begin conditionally using Java 9+ APIs more widely, such as [for `VarHandle`](#6806 (comment)).

RELNOTES=n/a
PiperOrigin-RevId: 705498400
copybara-service bot pushed a commit that referenced this issue Dec 12, 2024
(followup cl/705490132)

The method requires JDK 9+ to build, but our Maven build [always builds with JDK 23](#6549 (comment)). It also requires JDK 9+ to _run_, so I've added code to skip running the test under older versions.

Much of my goal here is to shake out any ways that we might cause problems if we begin conditionally using Java 9+ APIs more widely, such as [for `VarHandle`](#6806 (comment)).

RELNOTES=n/a
PiperOrigin-RevId: 705498400
copybara-service bot pushed a commit that referenced this issue Dec 12, 2024
(followup cl/705490132)

The method requires JDK 9+ to build, but our Maven build [always builds with JDK 23](#6549 (comment)). It also requires JDK 9+ to _run_, so I've added code to skip running the test under older versions.

Much of my goal here is to shake out any ways that we might cause problems if we begin conditionally using Java 9+ APIs more widely, such as [for `VarHandle`](#6806 (comment)).

RELNOTES=n/a
PiperOrigin-RevId: 705512728
copybara-service bot pushed a commit that referenced this issue Dec 13, 2024
See #6806 (comment).

Changes:

- "`SafeAtomicHelper`" is arguably already too generic a name for that class, given that we have a `SynchronizedAtomicHelper` that also avoids using `Unsafe`. It's going to become even more overly generic (and more overly scary) when we likely introduce a `VarHandle`-based alternative. (And maybe we'll even remove the `Unsafe`-based one entirely?) Rename it.
- Remove Javadoc from implementation classes, since it merely duplicates that from the superclass.
- Fix links in the (package-private) Javadoc.

I considered also renaming the `AtomicHelper` methods to match the terminology of `VarHandle`. This would mean only renaming `putThread`+`putNext` to... `setReleaseThread`? `setThreadReleasedly`? `setThreadUsingReleaseAccessMode`? I didn't find anything that I particularly liked.

RELNOTES=n/a
PiperOrigin-RevId: 705587524
copybara-service bot pushed a commit that referenced this issue Dec 13, 2024
See #6806 (comment).

Changes:

- "`SafeAtomicHelper`" is arguably already too generic a name for that class, given that we have a `SynchronizedAtomicHelper` that also avoids using `Unsafe`. It's going to become even more overly generic (and more overly scary) when we likely introduce a `VarHandle`-based alternative. (And maybe we'll even remove the `Unsafe`-based one entirely?) Rename it.
- Remove Javadoc from implementation classes, since it merely duplicates that from the superclass.
- Fix links in the (package-private) Javadoc.

I considered also renaming the `AtomicHelper` methods to match the terminology of `VarHandle`. This would mean only renaming `putThread`+`putNext` to... `setReleaseThread`? `setThreadReleasedly`? `setThreadUsingReleaseAccessMode`? I didn't find anything that I particularly liked.

RELNOTES=n/a
PiperOrigin-RevId: 705868797
copybara-service bot pushed a commit that referenced this issue Dec 17, 2024
For now, this is only for the JRE flavor, not for Android.

Our not entirely great benchmarks suggest that this may actually improve performance slightly over the current `Unsafe`-based implementation. This matches my sense that [alternatives to `Unsafe` have gotten faster](#6806 (comment)).

There are plenty of other [places in Guava that we use `Unsafe`](#6806), but this is a start.

Also: I'm going to consider this CL to "fix" #6549, even though:
- We already started requiring newer versions of Java to build our _tests_ in cl/705512728. (This CL is the first to require a newer JDK to build _prod_ code, again only to _build_ it, not to _run_ it.)
- This CL requires only Java 9, not Java 11.
- None of the changes so far should require people who _build our Maven project_ to do anything, since our build automatically downloads a new JDK to use for javac since cl/655647768.
RELNOTES=n/a
PiperOrigin-RevId: 702770479
copybara-service bot pushed a commit that referenced this issue Jan 8, 2025
…nally and (except when we need to support GWT/J2CL) directly instead of through our `LongAddable` interfaces.

It's available [as of Java 8](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/concurrent/atomic/LongAdder.html).

This eliminates our [usages of `Unsafe`](#6806) in `Striped64`, a part of the implementation of our `LongAdder`.

On the Android side, we'll have to wait [until we require API Level 24](https://developer.android.com/reference/java/util/concurrent/atomic/LongAdder).

RELNOTES=n/a
PiperOrigin-RevId: 712973075
copybara-service bot pushed a commit that referenced this issue Jan 8, 2025
…nally and (except when we need to support GWT/J2CL) directly instead of through our `LongAddable` interfaces.

It's available [as of Java 8](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/concurrent/atomic/LongAdder.html).

This eliminates our [usages of `Unsafe`](#6806) in `Striped64`, a part of the implementation of our `LongAdder`.

On the Android side, we'll have to wait [until we require API Level 24](https://developer.android.com/reference/java/util/concurrent/atomic/LongAdder).

RELNOTES=n/a
PiperOrigin-RevId: 713268966
copybara-service bot pushed a commit that referenced this issue Jan 10, 2025
…nsigned` when it's available.

This provides another [alternative to using `Unsafe`](#6806).

And port the benchmark to JMH, which for now means making it Google-internal. (Not that we have our _Caliper_ benchmarks actually _running_ externally, either, IIRC.)

The benchmarks suggest that the old, `Unsafe`-based implementation is faster up to 64 elements or so but that it's a matter of only about a nanosecond. The new implementation pulls ahead for larger arrays, including an advantage of about 5-10 ns for 1024 elements.

For now, I've included this implementation only in the JRE flavor of Guava. We could include it in the Android flavor, too, to see if it helps [under API Level 33+](https://developer.android.com/reference/java/util/Arrays#compareUnsigned(byte[],%20byte[])). But we really would want to do yet more benchmarking for that.

RELNOTES=n/a
PiperOrigin-RevId: 713020393
copybara-service bot pushed a commit that referenced this issue Jan 10, 2025
…nsigned` when it's available.

This provides another [alternative to using `Unsafe`](#6806).

And port the benchmark to JMH, which for now means making it Google-internal. (Not that we have our _Caliper_ benchmarks actually _running_ externally, either, IIRC.)

The benchmarks suggest that the old, `Unsafe`-based implementation is faster up to 64 elements or so but that it's a matter of only about a nanosecond. The new implementation pulls ahead for larger arrays, including an advantage of about 5-10 ns for 1024 elements.

For now, I've included this implementation only in the JRE flavor of Guava. We could include it in the Android flavor, too, to see if it helps [under API Level 33+](https://developer.android.com/reference/java/util/Arrays#compareUnsigned(byte[],%20byte[])). But we really would want to do yet more benchmarking for that.

RELNOTES=n/a
PiperOrigin-RevId: 714130759
@cpovirk
Copy link
Member

cpovirk commented Jan 10, 2025

(also: #7612. I thought I'd mentioned this issue from it, but I seem to have messed up somehow)

@cpovirk cpovirk added this to the NEXT milestone Jan 13, 2025
@cpovirk cpovirk added P2 and removed P3 no SLO P2 labels Jan 13, 2025
copybara-service bot pushed a commit that referenced this issue Jan 13, 2025
…possible.

RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+.
PiperOrigin-RevId: 714943678
copybara-service bot pushed a commit that referenced this issue Jan 13, 2025
…possible.

RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+.
PiperOrigin-RevId: 714943678
copybara-service bot pushed a commit that referenced this issue Jan 13, 2025
…possible.

RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+.
PiperOrigin-RevId: 714943678
copybara-service bot pushed a commit that referenced this issue Jan 13, 2025
…possible.

(One other note: I had some more fun with [Java 8 support](#6614) in `LittleEndianByteArray`.)

RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+.
PiperOrigin-RevId: 714943678
copybara-service bot pushed a commit that referenced this issue Jan 14, 2025
…possible.

(One other note: I had some more fun with [Java 8 support](#6614) in `LittleEndianByteArray`.)

RELNOTES=Changed various classes to stop using `sun.misc.Unsafe` under Java 9+.
PiperOrigin-RevId: 715182856
copybara-service bot pushed a commit that referenced this issue Jan 14, 2025
… endianness for reads.

I noticed this during work to [avoid using `Unsafe`](#6806) (though it applies even if we do use `Unsafe`, as you'd guess from the `BIG_ENDIAN` check there).

RELNOTES=n/a
PiperOrigin-RevId: 714958103
copybara-service bot pushed a commit that referenced this issue Jan 14, 2025
… endianness for reads.

I noticed this during work to [avoid using `Unsafe`](#6806) (though it applies even if we do use `Unsafe`, as you'd guess from the `BIG_ENDIAN` check there).

RELNOTES=n/a
PiperOrigin-RevId: 715368993
@cpovirk
Copy link
Member

cpovirk commented Jan 14, 2025

I'll need to publish a release with the recent changes here, but with that release, I'll consider this to be fixed: When running under Java 9+, guava-jre will no longer use Unsafe. The Unsafe code paths are still there to support Java 8 users, and it's possible that some tools will detect them, but I don't expect any more runtime warnings once users upgrade to the new version.

I assume that someday we'll have to make further changes, perhaps to remove Unsafe code paths entirely or to make similar changes in the Android flavor. But if anyone sees trouble with the new version in practice, let us know.

@cpovirk cpovirk closed this as completed Jan 14, 2025
@cpovirk
Copy link
Member

cpovirk commented Feb 5, 2025

It's possible that we'll want to do something in the Android flavor for users who use it under newer JVMs: Currently, the code would still try to use Unsafe there, and I don't think it would successfully fall back when Unsafe starts throwing exceptions. (And even when Unsafe "only" logs warnings, that's annoying, too.)

We of course recommend using the JRE flavor when running under a JVM. But it can be easy for the Android flavor to sneak in through cross-platform libraries that use it as a dependency. I don't expect it to be especially common, but it will come up.

This might actually be a case for a multi-release jar. But we'd want to see how that would affect Android users. (And I would expect to still find the occasional tool that doesn't understand multi-release jars, perhaps especially in the Android ecosystem, where they are presumably less used.) It's possible that the simpler course of action would be to perform a runtime version check. (We could also catch the exception that Unsafe will throw, but that wouldn't deal with the logging issue.)

@cpovirk
Copy link
Member

cpovirk commented Feb 21, 2025

Currently, the code would still try to use Unsafe there, and I don't think it would successfully fall back when Unsafe starts throwing exceptions.

My mistake: It does fall back successfully. I had been confused with a separate problem, in which an internal utility was trying to preinitialize essentially all the classes in Guava, including UnsafeAtomicHelper, even in environments in which it won't work. We might still change Guava to accommodate that, or we may change the utility.

@cpovirk
Copy link
Member

cpovirk commented Feb 21, 2025

Well... :)

OK, it's still worth noting that, if you use guava-android on a JRE (even after the upcoming release to avoid using Unsafe in guava-jre), recent JVMs would still show you warnings about using Unsafe, as already noted above. So maybe we'll want to do something more for guava-android, whether it involves multi-release jars or a runtime version check.

@cpovirk
Copy link
Member

cpovirk commented Feb 21, 2025

...or maybe including the VarHandle version in guava-android, too, while making sure not to use it from Android. (As I understand things, we'd want to avoid using it even if VarHandle is present: I don't think that VarHandle is actually usable unless the minimum(?) SDK set at build time is high enough to support it—or something like that. And (because of precisely this problem :)) we also haven't evaluated the performance of VarHandle under Android. So if we were to include the VarHandle code in guava-android, it would be (for now) purely for the benefit of JVM users.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants
@marcphilipp @ben-manes @cpovirk @chaoren @koundinyarvs and others