New posts
This commit is contained in:
@@ -75,6 +75,7 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T
|
||||
| [`kdf/`](kdf) | Key Derivation Function API in Java (JEP 510): HKDF Explained with Real Examples | `javax.crypto.KDF` final in JDK 25, `HKDF-SHA256/384/512`, extract vs expand, RFC 5869 vectors asserted, a hand-written HKDF and 300 generated cases compared with Python `cryptography` and `openssl kdf`, per-tenant keys with length-prefixed `info`, and the mistakes reproduced (same `info`, ambiguous concatenation, PIN through HKDF vs PBKDF2) |
|
||||
| [`classfile-api/`](classfile-api) | The Java ClassFile API (JEP 484): Read, Generate and Transform Bytecode Without ASM | `java.lang.classfile` final since JDK 24: `parse` and `ClassModel` traversal, `ClassFile.build` with `defineClass` and `defineHiddenClass`, five deliberately broken classes (including swapped exception-table labels that `verify()` misses), timing inserted with `ClassTransform`/`MethodTransform`/`CodeTransform`, the same three jobs in ASM 9.11.1 asserted equal, class-file version limits, a JDK 24/25/26 API diff and a look inside `spring-core` 7.0.10 |
|
||||
| [`jextract/`](jextract) | Calling C from Java with jextract and the FFM API: A Real Library, No JNI | jextract (early-access build for JDK 25) bindings for SQLite 3.45.1, the four arena kinds used wrongly on purpose, `C_POINTER` vs `ADDRESS` (zero-length pointers, and a `SIGSEGV` from `getString` on a NULL pointer), `sqlite3_open` out-parameter, return codes to exceptions, a `sqlite3_exec` upcall (and the JVM exit when it throws), the JDK 25 native-access warning and `--illegal-native-access=deny`, and a JMH comparison of FFM, hand-written JNI and plain Java |
|
||||
| [`unsafe-migration/`](unsafe-migration) | sun.misc.Unsafe Memory Access Is Going Away: Find and Fix It in Your Dependencies (JEP 471 / 498) | what `--sun-misc-unsafe-memory-access` allow/warn/debug/deny do on JDK 25 (and 21, 26, 27), `javac` removal warnings, jdeps and a bytecode scan of 17 real jars, Netty and Guava at run time, a CAS field to `VarHandle`, an off-heap array to `MemorySegment`, a before/after equivalence check |
|
||||
|
||||
## Captured output (`docs/output/`)
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# unsafe-migration — leaving sun.misc.Unsafe (JEP 471 / JEP 498)
|
||||
|
||||
Companion code for the ankurm.com article **sun.misc.Unsafe Memory Access Is Going Away: Find and Fix It in Your Dependencies**
|
||||
(<https://ankurm.com/sun-misc-unsafe-memory-access-deprecation-migration/>). The explanation lives in the article; this folder holds the sources and the transcripts they produced.
|
||||
|
||||
```bash
|
||||
JDK25=/path/to/jdk-25 JDK21=/path/to/jdk-21 [JDK26=/path JDK27=/path] ./run.sh # regenerates output/*.txt (a few minutes; steps 06-08 need Maven Central)
|
||||
```
|
||||
|
||||
Tested on Temurin 25.0.5+8 (the baseline), Ubuntu OpenJDK 21.0.12.1 (the "before"), Temurin 26.0.2.1+1 and 27.0.1.0.2+1 (default-behaviour check only). Plain `javac`/`java`, no Maven.
|
||||
|
||||
| Path | What it is |
|
||||
|---|---|
|
||||
| `src/UnsafeFieldCounter.java`, `VarHandleFieldCounter.java`, `FieldCounter.java` | a CAS field: Unsafe before, VarHandle after |
|
||||
| `src/UnsafeOffHeapLongs.java`, `FfmOffHeapLongs.java`, `OffHeapLongs.java` | an off-heap `long` array: `allocateMemory` before, `Arena`/`MemorySegment` after |
|
||||
| `src/UnsafeBytes.java`, `ViewBytes.java` | long access inside a `byte[]`: bimodal `getLong(Object,long)` before, byte-array-view VarHandle after |
|
||||
| `src/Equivalence.java` | the before/after check (170 checks; 67 with `--new-only` under `deny`) |
|
||||
| `broken/Probe.java` | the smallest Unsafe memory access; what every flag value does to it |
|
||||
| `tools/ScanJar.java` | bytecode scan for removal-deprecated Unsafe calls (java.lang.classfile, JDK 24+) |
|
||||
| `tools/NettyProbe.java`, `GuavaProbe.java`, `ListUnsafe.java`, `Prop.java` | one real Netty buffer; three Guava features; the JDK's own method list; the flag as a system property |
|
||||
| `scripts/scan-libs.sh`, `scripts/run-all.sh` | download and scan 17 jars (sha1-checked; retries because the proxy here truncated transfers now and then); regenerate everything |
|
||||
| `output/01`..`12` | captured transcripts: methods per JDK, javac warnings, flag values, other JDKs, jdeps, library scan, Netty, equivalence, JFR, scan callers, Guava |
|
||||
|
||||
Timing is not measured anywhere in this module; there is no benchmark.
|
||||
@@ -0,0 +1,29 @@
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/**
|
||||
* Smallest possible Unsafe memory access: allocate 8 bytes off-heap, write a long, read it back, free it.
|
||||
* Run it under each value of --sun-misc-unsafe-memory-access to see what the JVM does.
|
||||
*/
|
||||
public class Probe {
|
||||
public static void main(String[] args) {
|
||||
Unsafe u = UnsafeAccess.get();
|
||||
System.out.println("got Unsafe instance");
|
||||
try {
|
||||
long addr = u.allocateMemory(8);
|
||||
u.putLong(addr, 42L);
|
||||
System.out.println("read back " + u.getLong(addr));
|
||||
u.freeMemory(addr);
|
||||
System.out.println("allocate/put/get/free all succeeded");
|
||||
} catch (Throwable t) {
|
||||
System.out.println("FAILED: " + t);
|
||||
}
|
||||
try {
|
||||
long addr2 = u.allocateMemory(8);
|
||||
u.putLong(addr2, 7L);
|
||||
System.out.println("second use read " + u.getLong(addr2));
|
||||
u.freeMemory(addr2);
|
||||
} catch (Throwable t) {
|
||||
System.out.println("second use FAILED: " + t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
== openjdk version "21.0.12.1" 2026-08-18
|
||||
java.version=21.0.12.1
|
||||
public methods declared by sun.misc.Unsafe: 88
|
||||
deprecated for removal: 2
|
||||
not deprecated for removal: 86
|
||||
addressSize
|
||||
allocateInstance
|
||||
allocateMemory
|
||||
arrayBaseOffset
|
||||
arrayIndexScale
|
||||
compareAndSwapInt
|
||||
compareAndSwapLong
|
||||
compareAndSwapObject
|
||||
copyMemory
|
||||
copyMemory
|
||||
freeMemory
|
||||
fullFence
|
||||
getAddress
|
||||
getAndAddInt
|
||||
getAndAddLong
|
||||
getAndSetInt
|
||||
getAndSetLong
|
||||
getAndSetObject
|
||||
getBoolean
|
||||
getBooleanVolatile
|
||||
getByte
|
||||
getByte
|
||||
getByteVolatile
|
||||
getChar
|
||||
getChar
|
||||
getCharVolatile
|
||||
getDouble
|
||||
getDouble
|
||||
getDoubleVolatile
|
||||
getFloat
|
||||
getFloat
|
||||
getFloatVolatile
|
||||
getInt
|
||||
getInt
|
||||
getIntVolatile
|
||||
getLoadAverage
|
||||
getLong
|
||||
getLong
|
||||
getLongVolatile
|
||||
getObject
|
||||
getObjectVolatile
|
||||
getShort
|
||||
getShort
|
||||
getShortVolatile
|
||||
invokeCleaner
|
||||
loadFence
|
||||
objectFieldOffset (deprecated, not for removal)
|
||||
pageSize
|
||||
park
|
||||
putAddress
|
||||
putBoolean
|
||||
putBooleanVolatile
|
||||
putByte
|
||||
putByte
|
||||
putByteVolatile
|
||||
putChar
|
||||
putChar
|
||||
putCharVolatile
|
||||
putDouble
|
||||
putDouble
|
||||
putDoubleVolatile
|
||||
putFloat
|
||||
putFloat
|
||||
putFloatVolatile
|
||||
putInt
|
||||
putInt
|
||||
putIntVolatile
|
||||
putLong
|
||||
putLong
|
||||
putLongVolatile
|
||||
putObject
|
||||
putObjectVolatile
|
||||
putOrderedInt
|
||||
putOrderedLong
|
||||
putOrderedObject
|
||||
putShort
|
||||
putShort
|
||||
putShortVolatile
|
||||
reallocateMemory
|
||||
setMemory
|
||||
setMemory
|
||||
staticFieldBase (deprecated, not for removal)
|
||||
staticFieldOffset (deprecated, not for removal)
|
||||
storeFence
|
||||
throwException
|
||||
unpark
|
||||
|
||||
== openjdk version "25.0.5" 2026-10-20 LTS
|
||||
java.version=25.0.5
|
||||
public methods declared by sun.misc.Unsafe: 86
|
||||
deprecated for removal: 83
|
||||
not deprecated for removal: 3
|
||||
allocateInstance
|
||||
pageSize
|
||||
throwException
|
||||
|
||||
== openjdk version "26.0.2.1" 2026-08-18
|
||||
java.version=26.0.2.1
|
||||
public methods declared by sun.misc.Unsafe: 86
|
||||
deprecated for removal: 83
|
||||
not deprecated for removal: 3
|
||||
allocateInstance
|
||||
pageSize
|
||||
throwException
|
||||
|
||||
== openjdk version "27.0.1.0.2" 2026-10-21
|
||||
java.version=27.0.1.0.2
|
||||
public methods declared by sun.misc.Unsafe: 86
|
||||
deprecated for removal: 83
|
||||
not deprecated for removal: 3
|
||||
allocateInstance
|
||||
pageSize
|
||||
throwException
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
$ javac src/UnsafeAccess.java broken/Probe.java (openjdk version "25.0.5" 2026-10-20 LTS)
|
||||
src/UnsafeAccess.java:2: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
broken/Probe.java:1: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:8: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:10: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:12: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
broken/Probe.java:9: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
broken/Probe.java:12: warning: [removal] allocateMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:13: warning: [removal] putLong(long,long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:14: warning: [removal] getLong(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:15: warning: [removal] freeMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:21: warning: [removal] allocateMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:22: warning: [removal] putLong(long,long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:23: warning: [removal] getLong(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:24: warning: [removal] freeMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
14 warnings
|
||||
|
||||
$ javac src/UnsafeAccess.java broken/Probe.java (openjdk version "21.0.12.1" 2026-08-18)
|
||||
src/UnsafeAccess.java:2: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
broken/Probe.java:1: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:8: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:10: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
src/UnsafeAccess.java:12: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
broken/Probe.java:9: warning: Unsafe is internal proprietary API and may be removed in a future release
|
||||
6 warnings
|
||||
|
||||
$ javac -Xlint:removal -Werror src/UnsafeAccess.java broken/Probe.java (openjdk version "25.0.5" 2026-10-20 LTS)
|
||||
error: warnings found and -Werror specified
|
||||
broken/Probe.java:12: warning: [removal] allocateMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:13: warning: [removal] putLong(long,long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:14: warning: [removal] getLong(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:15: warning: [removal] freeMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
broken/Probe.java:21: warning: [removal] allocateMemory(long) in Unsafe has been deprecated and marked for removal
|
||||
exit=1
|
||||
@@ -0,0 +1,64 @@
|
||||
$ java -cp build Probe (JDK 25 default)
|
||||
got Unsafe instance
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::allocateMemory has been called by Probe (file:<build>/c25/)
|
||||
WARNING: Please consider reporting this to the maintainers of class Probe
|
||||
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
|
||||
read back 42
|
||||
allocate/put/get/free all succeeded
|
||||
second use read 7
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=allow -cp build Probe
|
||||
got Unsafe instance
|
||||
read back 42
|
||||
allocate/put/get/free all succeeded
|
||||
second use read 7
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=warn -cp build Probe
|
||||
got Unsafe instance
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::allocateMemory has been called by Probe (file:<build>/c25/)
|
||||
WARNING: Please consider reporting this to the maintainers of class Probe
|
||||
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
|
||||
read back 42
|
||||
allocate/put/get/free all succeeded
|
||||
second use read 7
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=debug -cp build Probe
|
||||
got Unsafe instance
|
||||
WARNING: sun.misc.Unsafe::allocateMemory called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:12)
|
||||
WARNING: sun.misc.Unsafe::putLong called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:13)
|
||||
WARNING: sun.misc.Unsafe::getLong called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:14)
|
||||
read back 42
|
||||
WARNING: sun.misc.Unsafe::freeMemory called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:15)
|
||||
allocate/put/get/free all succeeded
|
||||
WARNING: sun.misc.Unsafe::allocateMemory called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:21)
|
||||
WARNING: sun.misc.Unsafe::putLong called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:22)
|
||||
WARNING: sun.misc.Unsafe::getLong called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:23)
|
||||
second use read 7
|
||||
WARNING: sun.misc.Unsafe::freeMemory called by Probe (file:<build>/c25/)
|
||||
at Probe.main(Probe.java:24)
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp build Probe
|
||||
got Unsafe instance
|
||||
FAILED: java.lang.UnsupportedOperationException: allocateMemory
|
||||
second use FAILED: java.lang.UnsupportedOperationException: allocateMemory
|
||||
|
||||
# the JVM also publishes the flag as a system property that libraries can read
|
||||
$ java tools/Prop.java (default)
|
||||
sun.misc.unsafe.memory.access=null
|
||||
$ java --sun-misc-unsafe-memory-access=allow tools/Prop.java
|
||||
sun.misc.unsafe.memory.access=allow
|
||||
$ java --sun-misc-unsafe-memory-access=warn tools/Prop.java
|
||||
sun.misc.unsafe.memory.access=warn
|
||||
$ java --sun-misc-unsafe-memory-access=debug tools/Prop.java
|
||||
sun.misc.unsafe.memory.access=debug
|
||||
$ java --sun-misc-unsafe-memory-access=deny tools/Prop.java
|
||||
sun.misc.unsafe.memory.access=deny
|
||||
@@ -0,0 +1,25 @@
|
||||
$ java -cp build Probe (openjdk version "21.0.12.1" 2026-08-18, class file compiled by JDK 21)
|
||||
got Unsafe instance
|
||||
read back 42
|
||||
allocate/put/get/free all succeeded
|
||||
second use read 7
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp build Probe (openjdk version "21.0.12.1" 2026-08-18)
|
||||
Unrecognized option: --sun-misc-unsafe-memory-access=deny
|
||||
Error: Could not create the Java Virtual Machine.
|
||||
Error: A fatal exception has occurred. Program will exit.
|
||||
|
||||
$ java -cp build Probe (openjdk version "26.0.2.1" 2026-08-18)
|
||||
got Unsafe instance
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::allocateMemory has been called by Probe (file:<build>/c25/)
|
||||
WARNING: Please consider reporting this to the maintainers of class Probe
|
||||
...
|
||||
|
||||
$ java -cp build Probe (openjdk version "27.0.1.0.2" 2026-10-21)
|
||||
got Unsafe instance
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::allocateMemory has been called by Probe (file:<build>/c25/)
|
||||
WARNING: Please consider reporting this to the maintainers of class Probe
|
||||
...
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
$ jdeps --jdk-internals build (openjdk version "25.0.5" 2026-10-20 LTS)
|
||||
c25 -> jdk.unsupported
|
||||
Probe -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
|
||||
UnsafeAccess -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
|
||||
|
||||
Warning: JDK internal APIs are unsupported and private to JDK implementation that are
|
||||
subject to be removed or changed incompatibly and could break your application.
|
||||
Please modify your code to eliminate dependence on any JDK internal APIs.
|
||||
For the most recent update on JDK internal API replacements, please check:
|
||||
https://wiki.openjdk.org/display/JDK8/Java+Dependency+Analysis+Tool
|
||||
|
||||
JDK Internal API Suggested Replacement
|
||||
---------------- ---------------------
|
||||
sun.misc.Unsafe See https://openjdk.org/jeps/260
|
||||
@@ -0,0 +1,109 @@
|
||||
sha1 ok io.netty:netty-common:4.2.20.Final
|
||||
sha1 ok io.netty:netty-common:4.1.140.Final
|
||||
sha1 ok io.netty:netty-common:4.1.100.Final
|
||||
sha1 ok com.google.guava:guava:33.8.0-jre
|
||||
sha1 ok com.google.guava:guava:32.1.3-jre
|
||||
sha1 ok org.apache.lucene:lucene-core:10.6.0
|
||||
sha1 ok org.apache.lucene:lucene-core:9.12.3
|
||||
sha1 ok com.esotericsoftware:kryo:5.7.1
|
||||
sha1 ok org.apache.hadoop:hadoop-common:3.5.0
|
||||
sha1 ok org.apache.hadoop:hadoop-common:3.3.6
|
||||
sha1 ok org.apache.cassandra:cassandra-all:5.0.9
|
||||
sha1 ok org.agrona:agrona:2.7.1
|
||||
sha1 ok org.jctools:jctools-core:4.0.7
|
||||
sha1 ok com.github.ben-manes.caffeine:caffeine:3.3.1
|
||||
sha1 ok com.google.protobuf:protobuf-java:4.36.2
|
||||
sha1 ok org.xerial.snappy:snappy-java:1.1.10.12
|
||||
sha1 ok org.apache.spark:spark-unsafe_2.13:4.3.0
|
||||
|
||||
this JDK: sun.misc.Unsafe has 86 public methods, 83 deprecated for removal
|
||||
|
||||
netty-common-4.2.20.Final.jar: 657 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 22
|
||||
classes calling a removal-deprecated Unsafe method: 21 (125 call sites, 34 distinct methods)
|
||||
methods: {addressSize=1, allocateMemory=4, arrayBaseOffset=5, arrayIndexScale=5, compareAndSwapInt=1, compareAndSwapLong=6, compareAndSwapObject=1, copyMemory=4, freeMemory=4, getAndAddInt=1, getBoolean=1, getByte=10, getChar=5, getInt=10, getIntVolatile=1, getLong=13, getLongVolatile=4, getObject=2, getObjectVolatile=2, getShort=3, objectFieldOffset=3, putByte=3, putInt=3, putLong=4, putObject=4, putOrderedInt=1, putOrderedLong=8, putOrderedObject=3, putShort=3, reallocateMemory=2, setMemory=2, staticFieldBase=2, staticFieldOffset=2, storeFence=2}
|
||||
|
||||
netty-common-4.1.140.Final.jar: 548 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 22
|
||||
classes calling a removal-deprecated Unsafe method: 21 (122 call sites, 33 distinct methods)
|
||||
methods: {addressSize=1, allocateMemory=4, arrayBaseOffset=5, arrayIndexScale=5, compareAndSwapLong=6, compareAndSwapObject=1, copyMemory=4, freeMemory=4, getBoolean=1, getByte=10, getChar=5, getInt=10, getIntVolatile=1, getLong=16, getLongVolatile=1, getObject=2, getObjectVolatile=2, getShort=3, objectFieldOffset=3, putByte=3, putInt=3, putIntVolatile=1, putLong=4, putObject=4, putOrderedInt=1, putOrderedLong=8, putOrderedObject=3, putShort=2, reallocateMemory=2, setMemory=2, staticFieldBase=2, staticFieldOffset=2, storeFence=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [throwException]
|
||||
|
||||
netty-common-4.1.100.Final.jar: 492 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 15
|
||||
classes calling a removal-deprecated Unsafe method: 14 (107 call sites, 32 distinct methods)
|
||||
methods: {addressSize=1, allocateMemory=3, arrayBaseOffset=4, arrayIndexScale=4, compareAndSwapLong=3, compareAndSwapObject=1, copyMemory=4, freeMemory=3, getBoolean=1, getByte=10, getChar=5, getInt=10, getIntVolatile=1, getLong=14, getObject=2, getObjectVolatile=2, getShort=3, objectFieldOffset=3, putByte=3, putInt=3, putIntVolatile=1, putLong=2, putObject=4, putOrderedInt=1, putOrderedLong=5, putOrderedObject=3, putShort=2, reallocateMemory=2, setMemory=2, staticFieldBase=2, staticFieldOffset=2, storeFence=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [throwException]
|
||||
|
||||
guava-33.8.0-jre.jar: 1972 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 5
|
||||
classes calling a removal-deprecated Unsafe method: 5 (22 call sites, 8 distinct methods)
|
||||
methods: {arrayBaseOffset=2, arrayIndexScale=2, compareAndSwapObject=3, getAndSetObject=2, getLong=4, objectFieldOffset=5, putLong=2, putObject=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [getUnsafe]
|
||||
|
||||
guava-32.1.3-jre.jar: 2020 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 9
|
||||
classes calling a removal-deprecated Unsafe method: 9 (34 call sites, 10 distinct methods)
|
||||
methods: {arrayBaseOffset=2, arrayIndexScale=2, compareAndSwapInt=2, compareAndSwapLong=4, compareAndSwapObject=3, getAndSetObject=2, getLong=4, objectFieldOffset=11, putLong=2, putObject=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [getUnsafe]
|
||||
|
||||
lucene-core-10.6.0.jar: 2758 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 0
|
||||
classes calling a removal-deprecated Unsafe method: 0 (0 call sites, 0 distinct methods)
|
||||
|
||||
lucene-core-9.12.3.jar: 2515 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 1
|
||||
classes calling a removal-deprecated Unsafe method: 0 (0 call sites, 0 distinct methods)
|
||||
|
||||
kryo-5.7.1.jar: 246 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 15
|
||||
classes calling a removal-deprecated Unsafe method: 15 (100 call sites, 20 distinct methods)
|
||||
methods: {copyMemory=4, getBoolean=2, getByte=10, getChar=4, getDouble=4, getFloat=4, getInt=5, getLong=5, getObject=5, getShort=4, objectFieldOffset=11, putBoolean=2, putByte=10, putChar=4, putDouble=4, putFloat=4, putInt=5, putLong=5, putObject=4, putShort=4}
|
||||
|
||||
hadoop-common-3.5.0.jar: 2633 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 4
|
||||
classes calling a removal-deprecated Unsafe method: 1 (4 call sites, 3 distinct methods)
|
||||
methods: {arrayBaseOffset=1, arrayIndexScale=1, getLong=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [pageSize]
|
||||
|
||||
hadoop-common-3.3.6.jar: 2568 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 4
|
||||
classes calling a removal-deprecated Unsafe method: 1 (4 call sites, 3 distinct methods)
|
||||
methods: {arrayBaseOffset=1, arrayIndexScale=1, getLong=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [pageSize]
|
||||
|
||||
cassandra-all-5.0.9.jar: 5770 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 7
|
||||
classes calling a removal-deprecated Unsafe method: 6 (152 call sites, 16 distinct methods)
|
||||
methods: {arrayBaseOffset=3, arrayIndexScale=1, copyMemory=7, getByte=46, getInt=6, getLong=15, getObject=2, getObjectVolatile=1, getShort=3, objectFieldOffset=7, putByte=43, putInt=9, putLong=4, putObject=1, putShort=2, setMemory=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [allocateInstance, getUnsafe, pageSize]
|
||||
|
||||
agrona-2.7.1.jar: 295 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 0
|
||||
classes calling a removal-deprecated Unsafe method: 0 (0 call sites, 0 distinct methods)
|
||||
|
||||
jctools-core-4.0.7.jar: 356 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 58
|
||||
classes calling a removal-deprecated Unsafe method: 57 (136 call sites, 17 distinct methods)
|
||||
methods: {arrayBaseOffset=9, arrayIndexScale=8, compareAndSwapLong=22, compareAndSwapObject=14, getAndAddLong=4, getAndSetLong=2, getAndSetObject=2, getInt=2, getLong=2, getLongVolatile=20, getObject=2, getObjectVolatile=3, objectFieldOffset=2, putLong=3, putObject=5, putOrderedLong=28, putOrderedObject=8}
|
||||
other (not deprecated for removal) Unsafe methods called: [pageSize]
|
||||
|
||||
caffeine-3.3.1.jar: 801 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 0
|
||||
classes calling a removal-deprecated Unsafe method: 0 (0 call sites, 0 distinct methods)
|
||||
|
||||
protobuf-java-4.36.2.jar: 731 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 4
|
||||
classes calling a removal-deprecated Unsafe method: 4 (210 call sites, 20 distinct methods)
|
||||
methods: {arrayBaseOffset=2, arrayIndexScale=1, copyMemory=2, getBoolean=1, getByte=2, getDouble=1, getFloat=1, getInt=16, getLong=10, getObject=95, objectFieldOffset=5, putBoolean=1, putByte=2, putDouble=1, putFloat=1, putInt=34, putLong=5, putObject=28, staticFieldBase=1, staticFieldOffset=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [allocateInstance]
|
||||
|
||||
snappy-java-1.1.10.12.jar: 40 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 1
|
||||
classes calling a removal-deprecated Unsafe method: 0 (0 call sites, 0 distinct methods)
|
||||
|
||||
spark-unsafe_2.13-4.3.0.jar: 66 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 1
|
||||
classes calling a removal-deprecated Unsafe method: 1 (35 call sites, 24 distinct methods)
|
||||
methods: {allocateMemory=2, arrayBaseOffset=7, copyMemory=2, freeMemory=1, getBoolean=2, getByte=1, getDouble=1, getFloat=1, getInt=1, getLong=2, getObjectVolatile=1, getShort=1, objectFieldOffset=1, putBoolean=1, putByte=1, putDouble=1, putFloat=1, putInt=1, putLong=1, putObjectVolatile=1, putShort=1, setMemory=2, staticFieldBase=1, staticFieldOffset=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [throwException]
|
||||
@@ -0,0 +1,11 @@
|
||||
$ jdeps --jdk-internals netty-common-4.1.140.Final.jar (excerpt)
|
||||
netty-common-4.1.140.Final.jar -> jdk.unsupported
|
||||
io.netty.util.internal.CleanerJava9 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
|
||||
io.netty.util.internal.CleanerJava9$1 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
|
||||
io.netty.util.internal.CleanerJava9$2 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
|
||||
JDK Internal API Suggested Replacement
|
||||
---------------- ---------------------
|
||||
sun.misc.Unsafe See https://openjdk.org/jeps/260
|
||||
|
||||
$ jdeps --jdk-internals netty-common-4.1.140.Final.jar | grep -c -- '-> sun.misc.Unsafe'
|
||||
27
|
||||
@@ -0,0 +1,48 @@
|
||||
$ java -cp netty-4.1.140.Final NettyProbe (JDK 25 default)
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:<lib>/netty-common-4.1.140.Final.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.PlatformDependent0$2
|
||||
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
|
||||
netty sees Unsafe: true
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=allow -cp netty-4.1.140.Final NettyProbe
|
||||
netty sees Unsafe: true
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp netty-4.1.140.Final NettyProbe
|
||||
netty sees Unsafe: false
|
||||
direct=true hasMemoryAddress=false readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=debug -cp netty-4.1.140.Final NettyProbe
|
||||
netty sees Unsafe: false
|
||||
direct=true hasMemoryAddress=false readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java -cp netty-4.2.20.Final NettyProbe (JDK 25 default)
|
||||
netty sees Unsafe: false
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=allow -cp netty-4.2.20.Final NettyProbe
|
||||
netty sees Unsafe: true
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp netty-4.2.20.Final NettyProbe
|
||||
netty sees Unsafe: false
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=debug -cp netty-4.2.20.Final NettyProbe
|
||||
netty sees Unsafe: false
|
||||
direct=true hasMemoryAddress=true readLong=cafebabe
|
||||
done
|
||||
|
||||
$ unzip -p netty-common-4.1.140.Final.jar io/netty/util/internal/PlatformDependent0.class | grep -a -o -F 'sun.misc.unsafe.memory.access'
|
||||
sun.misc.unsafe.memory.access
|
||||
$ unzip -p netty-common-4.2.20.Final.jar io/netty/util/internal/PlatformDependent0.class | grep -a -o -F 'sun.misc.unsafe.memory.access'
|
||||
sun.misc.unsafe.memory.access
|
||||
@@ -0,0 +1,42 @@
|
||||
$ java --sun-misc-unsafe-memory-access=allow -cp build Equivalence (Unsafe, VarHandle and FFM side by side)
|
||||
field CAS: Unsafe and the replacement gave the same 2000-step trace
|
||||
field getAndAdd: 4 threads x 100000 -> total 400000 on every implementation
|
||||
off-heap array: Unsafe and the replacement gave the same 2000-step trace
|
||||
off-heap getAndAdd: 4 threads x 100000 -> sum 400000 on every implementation
|
||||
copyFrom / clear: identical on every implementation
|
||||
byte[] long access at offsets 0..32: Unsafe and VarHandle view agree
|
||||
VarHandle view at offset 36 of a 40-byte array: ArrayIndexOutOfBoundsException
|
||||
FFM read of element 4 in a 4-element segment: IndexOutOfBoundsException
|
||||
FFM read after close: IllegalStateException: Already closed
|
||||
ALL 170 CHECKS PASSED
|
||||
exit=0
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp build Equivalence --new-only (replacements only)
|
||||
field CAS: the replacement alone gave the same 2000-step trace
|
||||
field getAndAdd: 4 threads x 100000 -> total 400000 on every implementation
|
||||
off-heap array: the replacement alone gave the same 2000-step trace
|
||||
off-heap getAndAdd: 4 threads x 100000 -> sum 400000 on every implementation
|
||||
copyFrom / clear: identical on every implementation
|
||||
byte[] long access at offsets 0..32: VarHandle view only
|
||||
VarHandle view at offset 36 of a 40-byte array: ArrayIndexOutOfBoundsException
|
||||
FFM read of element 4 in a 4-element segment: IndexOutOfBoundsException
|
||||
FFM read after close: IllegalStateException: Already closed
|
||||
ALL 67 CHECKS PASSED (new APIs only)
|
||||
exit=0
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp build Equivalence (the Unsafe version under deny)
|
||||
Exception in thread "main" java.lang.ExceptionInInitializerError
|
||||
at Equivalence.main(Equivalence.java:25)
|
||||
Caused by: java.lang.UnsupportedOperationException: objectFieldOffset
|
||||
at jdk.unsupported/sun.misc.Unsafe.beforeMemoryAccessSlow(Unsafe.java:1822)
|
||||
at jdk.unsupported/sun.misc.Unsafe.beforeMemoryAccess(Unsafe.java:1787)
|
||||
at jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unsafe.java:905)
|
||||
|
||||
$ java -cp build Equivalence (JDK 25 default: one warning, then the same results)
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by UnsafeFieldCounter (file:<build>/m/)
|
||||
WARNING: Please consider reporting this to the maintainers of class UnsafeFieldCounter
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
field CAS: Unsafe and the replacement gave the same 2000-step trace
|
||||
field getAndAdd: 4 threads x 100000 -> total 400000 on every implementation
|
||||
off-heap array: Unsafe and the replacement gave the same 2000-step trace
|
||||
@@ -0,0 +1,14 @@
|
||||
$ java -XX:StartFlightRecording:filename=probe.jfr --sun-misc-unsafe-memory-access=allow -cp build Probe (silent: no warnings)
|
||||
got Unsafe instance
|
||||
read back 42
|
||||
allocate/put/get/free all succeeded
|
||||
second use read 7
|
||||
$ jfr print --events jdk.DeprecatedInvocation probe.jfr | grep -E 'method = |line:' | sort | uniq -c
|
||||
1 Probe.main(String[]) line: 12
|
||||
1 Probe.main(String[]) line: 13
|
||||
1 Probe.main(String[]) line: 14
|
||||
1 Probe.main(String[]) line: 15
|
||||
1 method = sun.misc.Unsafe.allocateMemory(long)
|
||||
1 method = sun.misc.Unsafe.freeMemory(long)
|
||||
1 method = sun.misc.Unsafe.getLong(long)
|
||||
1 method = sun.misc.Unsafe.putLong(long, long)
|
||||
@@ -0,0 +1,75 @@
|
||||
sha1 ok com.google.guava:guava:33.8.0-jre
|
||||
sha1 ok com.esotericsoftware:kryo:5.7.1
|
||||
sha1 ok org.apache.hadoop:hadoop-common:3.5.0
|
||||
sha1 ok org.apache.cassandra:cassandra-all:5.0.9
|
||||
sha1 ok com.google.protobuf:protobuf-java:4.36.2
|
||||
sha1 ok org.apache.spark:spark-unsafe_2.13:4.3.0
|
||||
|
||||
this JDK: sun.misc.Unsafe has 86 public methods, 83 deprecated for removal
|
||||
|
||||
guava-33.8.0-jre.jar: 1972 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 5
|
||||
classes calling a removal-deprecated Unsafe method: 5 (22 call sites, 8 distinct methods)
|
||||
methods: {arrayBaseOffset=2, arrayIndexScale=2, compareAndSwapObject=3, getAndSetObject=2, getLong=4, objectFieldOffset=5, putLong=2, putObject=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [getUnsafe]
|
||||
com.google.common.hash.LittleEndianByteArray$UnsafeByteArray 2
|
||||
com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$1 2
|
||||
com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$2 2
|
||||
com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator 4
|
||||
com.google.common.util.concurrent.AbstractFutureState$UnsafeAtomicHelper 12
|
||||
|
||||
kryo-5.7.1.jar: 246 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 15
|
||||
classes calling a removal-deprecated Unsafe method: 15 (100 call sites, 20 distinct methods)
|
||||
methods: {copyMemory=4, getBoolean=2, getByte=10, getChar=4, getDouble=4, getFloat=4, getInt=5, getLong=5, getObject=5, getShort=4, objectFieldOffset=11, putBoolean=2, putByte=10, putChar=4, putDouble=4, putFloat=4, putInt=5, putLong=5, putObject=4, putShort=4}
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$BooleanUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$ByteUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$CharUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$DoubleUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$FloatUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$IntUnsafeField 7
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$LongUnsafeField 7
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$ShortUnsafeField 5
|
||||
com.esotericsoftware.kryo.serializers.UnsafeField$StringUnsafeField 5
|
||||
com.esotericsoftware.kryo.unsafe.UnsafeByteBufferInput 11
|
||||
com.esotericsoftware.kryo.unsafe.UnsafeByteBufferOutput 11
|
||||
com.esotericsoftware.kryo.unsafe.UnsafeInput 11
|
||||
com.esotericsoftware.kryo.unsafe.UnsafeOutput 11
|
||||
com.esotericsoftware.kryo.unsafe.UnsafeUtil 2
|
||||
|
||||
hadoop-common-3.5.0.jar: 2633 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 4
|
||||
classes calling a removal-deprecated Unsafe method: 1 (4 call sites, 3 distinct methods)
|
||||
methods: {arrayBaseOffset=1, arrayIndexScale=1, getLong=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [pageSize]
|
||||
org.apache.hadoop.io.FastByteComparisons$LexicographicalComparerHolder$UnsafeComparer 4
|
||||
|
||||
cassandra-all-5.0.9.jar: 5770 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 7
|
||||
classes calling a removal-deprecated Unsafe method: 6 (152 call sites, 16 distinct methods)
|
||||
methods: {arrayBaseOffset=3, arrayIndexScale=1, copyMemory=7, getByte=46, getInt=6, getLong=15, getObject=2, getObjectVolatile=1, getShort=3, objectFieldOffset=7, putByte=43, putInt=9, putLong=4, putObject=1, putShort=2, setMemory=2}
|
||||
other (not deprecated for removal) Unsafe methods called: [allocateInstance, getUnsafe, pageSize]
|
||||
org.apache.cassandra.io.util.Memory 5
|
||||
org.apache.cassandra.utils.FastByteOperations$UnsafeOperations 16
|
||||
org.apache.cassandra.utils.concurrent.Ref 3
|
||||
org.apache.cassandra.utils.memory.LittleEndianMemoryUtil 37
|
||||
org.apache.cassandra.utils.memory.MemoryUtil 29
|
||||
org.apache.cassandra.utils.memory.NativeEndianMemoryUtil 62
|
||||
|
||||
protobuf-java-4.36.2.jar: 731 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 4
|
||||
classes calling a removal-deprecated Unsafe method: 4 (210 call sites, 20 distinct methods)
|
||||
methods: {arrayBaseOffset=2, arrayIndexScale=1, copyMemory=2, getBoolean=1, getByte=2, getDouble=1, getFloat=1, getInt=16, getLong=10, getObject=95, objectFieldOffset=5, putBoolean=1, putByte=2, putDouble=1, putFloat=1, putInt=34, putLong=5, putObject=28, staticFieldBase=1, staticFieldOffset=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [allocateInstance]
|
||||
com.google.protobuf.MessageSchema 182
|
||||
com.google.protobuf.UnsafeUtil 1
|
||||
com.google.protobuf.UnsafeUtil$JvmMemoryAccessor 18
|
||||
com.google.protobuf.UnsafeUtil$MemoryAccessor 9
|
||||
|
||||
spark-unsafe_2.13-4.3.0.jar: 66 classes scanned
|
||||
classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): 1
|
||||
classes calling a removal-deprecated Unsafe method: 1 (35 call sites, 24 distinct methods)
|
||||
methods: {allocateMemory=2, arrayBaseOffset=7, copyMemory=2, freeMemory=1, getBoolean=2, getByte=1, getDouble=1, getFloat=1, getInt=1, getLong=2, getObjectVolatile=1, getShort=1, objectFieldOffset=1, putBoolean=1, putByte=1, putDouble=1, putFloat=1, putInt=1, putLong=1, putObjectVolatile=1, putShort=1, setMemory=2, staticFieldBase=1, staticFieldOffset=1}
|
||||
other (not deprecated for removal) Unsafe methods called: [throwException]
|
||||
org.apache.spark.unsafe.Platform 35
|
||||
@@ -0,0 +1,45 @@
|
||||
$ java -cp guava-32.1.3-jre GuavaProbe (JDK 25 default)
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::arrayBaseOffset has been called by com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator (file:<lib>/guava-32.1.3-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator
|
||||
WARNING: sun.misc.Unsafe::arrayBaseOffset will be removed in a future release
|
||||
comparator = com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator
|
||||
compare = -1
|
||||
future = ok
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=debug -cp guava-32.1.3-jre GuavaProbe
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
WARNING: sun.misc.Unsafe::arrayBaseOffset called by com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator (file:<lib>/guava-32.1.3-jre.jar)
|
||||
at com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator.<clinit>(UnsignedBytes.java:317)
|
||||
at java.base/java.lang.Class.forName0(Native Method)
|
||||
at java.base/java.lang.Class.forName(Class.java:467)
|
||||
at java.base/java.lang.Class.forName(Class.java:458)
|
||||
at com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder.getBestComparator(UnsignedBytes.java:437)
|
||||
at com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder.<clinit>(UnsignedBytes.java:290)
|
||||
at com.google.common.primitives.UnsignedBytes.lexicographicalComparator(UnsignedBytes.java:270)
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp guava-32.1.3-jre GuavaProbe
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
comparator = com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$PureJavaComparator
|
||||
compare = -1
|
||||
future = ok
|
||||
|
||||
$ java -cp guava-33.8.0-jre GuavaProbe (JDK 25 default)
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
comparator = com.google.common.primitives.UnsignedBytes$ArraysCompareUnsignedComparator
|
||||
compare = -1
|
||||
future = ok
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=debug -cp guava-33.8.0-jre GuavaProbe
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
comparator = com.google.common.primitives.UnsignedBytes$ArraysCompareUnsignedComparator
|
||||
compare = -1
|
||||
future = ok
|
||||
|
||||
$ java --sun-misc-unsafe-memory-access=deny -cp guava-33.8.0-jre GuavaProbe
|
||||
murmur3_128 = 1df72d8cb9e9c0755a1e5df367505d00
|
||||
comparator = com.google.common.primitives.UnsignedBytes$ArraysCompareUnsignedComparator
|
||||
compare = -1
|
||||
future = ok
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# unsafe-migration: regenerates output/*.txt. JDK25=/path JDK21=/path [JDK26=/path JDK27=/path] ./run.sh
|
||||
exec "$(cd "$(dirname "$0")" && pwd)/scripts/run-all.sh" "$@"
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file in output/.
|
||||
# JDK25=/path JDK21=/path [JDK26=/path JDK27=/path] ./scripts/run-all.sh
|
||||
# Steps 06 and 07 download jars from Maven Central (sha1-checked); everything else is offline.
|
||||
set -uo pipefail
|
||||
unset JAVA_TOOL_OPTIONS
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
JDK25="${JDK25:-/tmp/tools/jdk-25.0.5+8}"
|
||||
JDK21="${JDK21:-/usr/lib/jvm/java-21-openjdk-amd64}"
|
||||
JDK26="${JDK26:-}"; JDK27="${JDK27:-}"
|
||||
OUT="$HERE/output"; mkdir -p "$OUT"
|
||||
B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT
|
||||
cd "$HERE"
|
||||
F='--sun-misc-unsafe-memory-access'
|
||||
norm() { sed "s#$B#<build>#g"; } # keep temp-dir paths out of the transcripts
|
||||
ver() { "$1/bin/java" -version 2>&1 | sed -n 1p; }
|
||||
|
||||
# ---- 01 which JDK did what ----------------------------------------------------------------
|
||||
{ for j in "$JDK21" "$JDK25" "$JDK26" "$JDK27"; do [ -n "$j" ] && { echo "== $(ver "$j")"; "$j/bin/java" tools/ListUnsafe.java 2>&1; echo; }; done
|
||||
} > "$OUT/01-unsafe-methods-per-jdk.txt"
|
||||
|
||||
# ---- 02 compile-time warnings -------------------------------------------------------------
|
||||
mkdir -p "$B/c25" "$B/c21"
|
||||
{ echo "\$ javac src/UnsafeAccess.java broken/Probe.java ($(ver "$JDK25"))"
|
||||
"$JDK25/bin/javac" -d "$B/c25" src/UnsafeAccess.java broken/Probe.java 2>&1 | grep -E 'warning|^[0-9]+ warnings?'
|
||||
echo; echo "\$ javac src/UnsafeAccess.java broken/Probe.java ($(ver "$JDK21"))"
|
||||
"$JDK21/bin/javac" -d "$B/c21" src/UnsafeAccess.java broken/Probe.java 2>&1 | grep -E 'warning|^[0-9]+ warnings?'
|
||||
echo; echo "\$ javac -Xlint:removal -Werror src/UnsafeAccess.java broken/Probe.java ($(ver "$JDK25"))"
|
||||
"$JDK25/bin/javac" -Xlint:removal -Werror -d "$B/w" src/UnsafeAccess.java broken/Probe.java 2>&1 | grep -E 'removal|error|^[0-9]+ (error|warning)' | head -6; echo "exit=${PIPESTATUS[0]}"
|
||||
} > "$OUT/02-compile-warnings.txt"
|
||||
|
||||
# ---- 03 the four flag values on JDK 25 ----------------------------------------------------
|
||||
{ for a in "" allow warn debug deny; do
|
||||
[ -z "$a" ] && { echo "\$ java -cp build Probe (JDK 25 default)"; "$JDK25/bin/java" -cp "$B/c25" Probe 2>&1 | norm; } \
|
||||
|| { echo "\$ java $F=$a -cp build Probe"; "$JDK25/bin/java" "$F=$a" -cp "$B/c25" Probe 2>&1 | norm; }
|
||||
echo
|
||||
done
|
||||
echo "# the JVM also publishes the flag as a system property that libraries can read"
|
||||
for a in "" allow warn debug deny; do
|
||||
[ -z "$a" ] && { echo "\$ java tools/Prop.java (default)"; "$JDK25/bin/java" tools/Prop.java 2>&1; } \
|
||||
|| { echo "\$ java $F=$a tools/Prop.java"; "$JDK25/bin/java" "$F=$a" tools/Prop.java 2>&1; }
|
||||
done
|
||||
} > "$OUT/03-flag-values-jdk25.txt"
|
||||
|
||||
# ---- 04 same class, other JDKs ------------------------------------------------------------
|
||||
{ echo "\$ java -cp build Probe ($(ver "$JDK21"), class file compiled by JDK 21)"; "$JDK21/bin/java" -cp "$B/c21" Probe 2>&1 | norm; echo
|
||||
echo "\$ java $F=deny -cp build Probe ($(ver "$JDK21"))"; "$JDK21/bin/java" "$F=deny" -cp "$B/c21" Probe 2>&1 | norm; echo
|
||||
for j in "$JDK26" "$JDK27"; do [ -n "$j" ] && { echo "\$ java -cp build Probe ($(ver "$j"))"; "$j/bin/java" -cp "$B/c25" Probe 2>&1 | norm | head -4; echo "..."; echo; }; done
|
||||
} > "$OUT/04-same-class-other-jdks.txt"
|
||||
|
||||
# ---- 05 jdeps on our own classes ----------------------------------------------------------
|
||||
{ echo "\$ jdeps --jdk-internals build ($(ver "$JDK25"))"; "$JDK25/bin/jdeps" --jdk-internals "$B/c25" 2>&1 | norm
|
||||
} > "$OUT/05-jdeps-own-classes.txt"
|
||||
|
||||
# ---- 06/07 real libraries -----------------------------------------------------------------
|
||||
online=no; for i in 1 2 3 4 5; do curl -sf -o /dev/null https://repo1.maven.org/maven2/io/netty/netty-common/maven-metadata.xml && { online=yes; break; }; sleep 1; done
|
||||
if [ $online = yes ]; then
|
||||
JDK25="$JDK25" "$HERE/scripts/scan-libs.sh" > "$OUT/06-scan-libraries.txt" 2>&1
|
||||
ONLY='guava:33|hadoop-common:3.5|protobuf|cassandra|spark|kryo' JDK25="$JDK25" "$HERE/scripts/scan-libs.sh" --classes > "$OUT/11-scan-callers.txt" 2>&1
|
||||
L="$B/lib"; mkdir -p "$L"
|
||||
fetch() { for i in 1 2 3 4 5 6; do curl -sfL -o "$L/$(basename "$1")" "https://repo1.maven.org/maven2/$1" && return 0; sleep 1; done; return 1; }
|
||||
for v in 4.1.140.Final 4.2.20.Final; do fetch io/netty/netty-common/$v/netty-common-$v.jar; fetch io/netty/netty-buffer/$v/netty-buffer-$v.jar; done
|
||||
{ echo "\$ jdeps --jdk-internals netty-common-4.1.140.Final.jar (excerpt)"
|
||||
"$JDK25/bin/jdeps" --jdk-internals --multi-release 25 "$L/netty-common-4.1.140.Final.jar" 2>&1 | sed -n '1,4p;/^JDK Internal API/,$p'
|
||||
echo; echo "\$ jdeps --jdk-internals netty-common-4.1.140.Final.jar | grep -c -- '-> sun.misc.Unsafe'"
|
||||
"$JDK25/bin/jdeps" --jdk-internals --multi-release 25 "$L/netty-common-4.1.140.Final.jar" 2>&1 | grep -c -- '-> sun.misc.Unsafe'
|
||||
} > "$OUT/07-jdeps-netty.txt"
|
||||
{
|
||||
for v in 4.1.140.Final 4.2.20.Final; do
|
||||
CP="$L/netty-common-$v.jar:$L/netty-buffer-$v.jar"; mkdir -p "$B/n$v"; "$JDK25/bin/javac" -cp "$CP" -d "$B/n$v" tools/NettyProbe.java 2>&1
|
||||
for a in "" allow deny debug; do
|
||||
[ -z "$a" ] && echo "\$ java -cp netty-$v NettyProbe (JDK 25 default)" || echo "\$ java $F=$a -cp netty-$v NettyProbe"
|
||||
{ [ -z "$a" ] && "$JDK25/bin/java" -cp "$CP:$B/n$v" NettyProbe || "$JDK25/bin/java" "$F=$a" -cp "$CP:$B/n$v" NettyProbe; } 2>&1 | sed "s#$L#<lib>#g" | head -9
|
||||
echo
|
||||
done
|
||||
done
|
||||
for v in 4.1.140.Final 4.2.20.Final; do
|
||||
echo "\$ unzip -p netty-common-$v.jar io/netty/util/internal/PlatformDependent0.class | grep -a -o -F 'sun.misc.unsafe.memory.access'"
|
||||
unzip -p "$L/netty-common-$v.jar" io/netty/util/internal/PlatformDependent0.class | grep -a -o -F 'sun.misc.unsafe.memory.access' | sort -u
|
||||
done
|
||||
} > "$OUT/08-netty-runtime.txt"
|
||||
# Guava 32.1.3 vs 33.8.0 on JDK 25: same three calls, different behaviour
|
||||
fetch com/google/guava/failureaccess/1.0.3/failureaccess-1.0.3.jar
|
||||
for v in 32.1.3-jre 33.8.0-jre; do fetch com/google/guava/guava/$v/guava-$v.jar; done
|
||||
mkdir -p "$B/gp"; "$JDK25/bin/javac" -cp "$L/guava-33.8.0-jre.jar:$L/failureaccess-1.0.3.jar" -d "$B/gp" tools/GuavaProbe.java >/dev/null 2>&1
|
||||
{ for v in 32.1.3-jre 33.8.0-jre; do
|
||||
CP="$L/guava-$v.jar:$L/failureaccess-1.0.3.jar:$B/gp"
|
||||
for a in "" debug deny; do
|
||||
[ -z "$a" ] && echo "\$ java -cp guava-$v GuavaProbe (JDK 25 default)" || echo "\$ java $F=$a -cp guava-$v GuavaProbe"
|
||||
{ [ -z "$a" ] && "$JDK25/bin/java" -cp "$CP" GuavaProbe || "$JDK25/bin/java" "$F=$a" -cp "$CP" GuavaProbe; } 2>&1 | sed "s#$L#<lib>#g" | head -9
|
||||
echo
|
||||
done
|
||||
done
|
||||
} > "$OUT/12-guava-runtime.txt"
|
||||
else
|
||||
echo "no network: skipped 06-08"
|
||||
fi
|
||||
|
||||
# ---- 10 JFR: finds the callers even with =allow ------------------------------------------
|
||||
jfrsum() { "$JDK25/bin/jfr" print --events jdk.DeprecatedInvocation "$1" | grep -E 'method = |line:' | sed 's/^ *//' | sort | uniq -c | sed "s#^ *# #"; }
|
||||
{ echo "\$ java -XX:StartFlightRecording:filename=probe.jfr $F=allow -cp build Probe (silent: no warnings)"
|
||||
"$JDK25/bin/java" "-XX:StartFlightRecording:filename=$B/probe.jfr" "$F=allow" -cp "$B/c25" Probe 2>&1 | grep -v 'jfr,startup'
|
||||
echo "\$ jfr print --events jdk.DeprecatedInvocation probe.jfr | grep -E 'method = |line:' | sort | uniq -c"; jfrsum "$B/probe.jfr"
|
||||
} > "$OUT/10-jfr-deprecated-invocation.txt"
|
||||
|
||||
# ---- 09 the migration: before/after equivalence -------------------------------------------
|
||||
mkdir -p "$B/m"; "$JDK25/bin/javac" -nowarn -d "$B/m" src/*.java 2>&1 | grep -E 'error'
|
||||
{ echo "\$ java $F=allow -cp build Equivalence (Unsafe, VarHandle and FFM side by side)"; "$JDK25/bin/java" "$F=allow" -cp "$B/m" Equivalence 2>&1; echo "exit=$?"; echo
|
||||
echo "\$ java $F=deny -cp build Equivalence --new-only (replacements only)"; "$JDK25/bin/java" "$F=deny" -cp "$B/m" Equivalence --new-only 2>&1; echo "exit=$?"; echo
|
||||
echo "\$ java $F=deny -cp build Equivalence (the Unsafe version under deny)"; "$JDK25/bin/java" "$F=deny" -cp "$B/m" Equivalence 2>&1 | head -6 | norm; echo
|
||||
echo "\$ java -cp build Equivalence (JDK 25 default: one warning, then the same results)"; "$JDK25/bin/java" -cp "$B/m" Equivalence 2>&1 | norm | head -7
|
||||
} > "$OUT/09-equivalence.txt"
|
||||
echo "wrote $OUT"
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download real jars from Maven Central (sha1-checked) into a throw-away dir and scan them with tools/ScanJar.java.
|
||||
# JDK25=/path/to/jdk-25 ./scripts/scan-libs.sh [--classes] needs network; ONLY='regex' limits the coordinate list
|
||||
set -uo pipefail
|
||||
unset JAVA_TOOL_OPTIONS
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
JDK25="${JDK25:-/tmp/tools/jdk-25.0.5+8}"
|
||||
D="$(mktemp -d)"; trap 'rm -rf "$D"' EXIT
|
||||
COORDS="
|
||||
io.netty:netty-common:4.2.20.Final
|
||||
io.netty:netty-common:4.1.140.Final
|
||||
io.netty:netty-common:4.1.100.Final
|
||||
com.google.guava:guava:33.8.0-jre
|
||||
com.google.guava:guava:32.1.3-jre
|
||||
org.apache.lucene:lucene-core:10.6.0
|
||||
org.apache.lucene:lucene-core:9.12.3
|
||||
com.esotericsoftware:kryo:5.7.1
|
||||
org.apache.hadoop:hadoop-common:3.5.0
|
||||
org.apache.hadoop:hadoop-common:3.3.6
|
||||
org.apache.cassandra:cassandra-all:5.0.9
|
||||
org.agrona:agrona:2.7.1
|
||||
org.jctools:jctools-core:4.0.7
|
||||
com.github.ben-manes.caffeine:caffeine:3.3.1
|
||||
com.google.protobuf:protobuf-java:4.36.2
|
||||
org.xerial.snappy:snappy-java:1.1.10.12
|
||||
org.apache.spark:spark-unsafe_2.13:4.3.0
|
||||
"
|
||||
JARS=()
|
||||
for c in $(echo "$COORDS" | grep -E "${ONLY:-.}"); do
|
||||
IFS=: read -r g a v <<<"$c"
|
||||
url="https://repo1.maven.org/maven2/${g//.//}/$a/$v/$a-$v.jar"
|
||||
ok=no
|
||||
for try in 1 2 3 4 5 6 7 8; do sleep 1; # the sandbox proxy occasionally truncates a transfer; the sha1 check catches it
|
||||
curl -sfL -o "$D/$a-$v.jar" "$url" || continue
|
||||
want="$(curl -sfL "$url.sha1" | cut -c1-40)"; got="$(sha1sum "$D/$a-$v.jar" | cut -c1-40)"
|
||||
[ -n "$want" ] && [ "$want" = "$got" ] && { ok=yes; break; }
|
||||
done
|
||||
[ $ok = yes ] && echo "sha1 ok $c" || { echo "FAILED (download or sha1) $c"; continue; }
|
||||
JARS+=("$D/$a-$v.jar")
|
||||
done
|
||||
echo
|
||||
"$JDK25/bin/java" "$HERE/tools/ScanJar.java" "$@" "${JARS[@]}" 2>&1 | sed "s#$D/##g"
|
||||
@@ -0,0 +1,141 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Before/after equivalence check, no benchmarks: the same scripted operations run against the Unsafe version and the
|
||||
* replacement, and every observable result must match. Prints one line per check; exits 1 on the first mismatch.
|
||||
*
|
||||
* java Equivalence all three implementations (needs Unsafe memory access allowed or warned)
|
||||
* java Equivalence --new-only only VarHandle and FFM; run this one under --sun-misc-unsafe-memory-access=deny
|
||||
*/
|
||||
public class Equivalence {
|
||||
static int checks;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
boolean newOnly = args.length > 0 && args[0].equals("--new-only");
|
||||
|
||||
// ---- 1. field CAS: scripted single-thread ops ----
|
||||
List<Supplier<FieldCounter>> fields = new ArrayList<>();
|
||||
if (!newOnly) fields.add(UnsafeFieldCounter::new);
|
||||
fields.add(VarHandleFieldCounter::new);
|
||||
long[] ref = null;
|
||||
for (var f : fields) {
|
||||
long[] trace = fieldTrace(f.get());
|
||||
if (ref == null) ref = trace; else same("field trace", ref, trace);
|
||||
}
|
||||
System.out.println("field CAS: " + names(fields.size()) + " gave the same 2000-step trace");
|
||||
|
||||
// ---- 2. field CAS under contention: lost updates would show as a wrong total ----
|
||||
for (var f : fields) require("contended total", 400_000L, fieldTotal(f.get()));
|
||||
System.out.println("field getAndAdd: 4 threads x 100000 -> total 400000 on every implementation");
|
||||
|
||||
// ---- 3. off-heap array ----
|
||||
List<Supplier<OffHeapLongs>> offs = new ArrayList<>();
|
||||
if (!newOnly) offs.add(() -> new UnsafeOffHeapLongs(64));
|
||||
offs.add(() -> new FfmOffHeapLongs(64));
|
||||
long[] ref2 = null;
|
||||
for (var s : offs) {
|
||||
try (OffHeapLongs o = s.get()) {
|
||||
long[] trace = offHeapTrace(o);
|
||||
if (ref2 == null) ref2 = trace; else same("off-heap trace", ref2, trace);
|
||||
}
|
||||
}
|
||||
System.out.println("off-heap array: " + names(offs.size()) + " gave the same 2000-step trace");
|
||||
|
||||
for (var s : offs) {
|
||||
try (OffHeapLongs o = s.get()) {
|
||||
Thread[] ts = new Thread[4];
|
||||
for (int t = 0; t < ts.length; t++) { ts[t] = new Thread(() -> { for (int k = 0; k < 100_000; k++) o.getAndAdd(k % 4, 1); }); ts[t].start(); }
|
||||
for (Thread t : ts) t.join();
|
||||
long sum = 0; for (int i = 0; i < 4; i++) sum += o.get(i);
|
||||
require("off-heap contended sum", 400_000L, sum);
|
||||
}
|
||||
}
|
||||
System.out.println("off-heap getAndAdd: 4 threads x 100000 -> sum 400000 on every implementation");
|
||||
|
||||
// ---- 4. bulk copy ----
|
||||
for (var s : offs) {
|
||||
try (OffHeapLongs a = s.get(); OffHeapLongs b = s.get()) {
|
||||
for (int i = 0; i < a.length(); i++) a.set(i, i * 31L);
|
||||
b.copyFrom(a);
|
||||
for (int i = 0; i < a.length(); i++) require("copy[" + i + "]", i * 31L, b.get(i));
|
||||
a.clear();
|
||||
require("cleared", 0L, a.get(5));
|
||||
}
|
||||
}
|
||||
System.out.println("copyFrom / clear: identical on every implementation");
|
||||
|
||||
// ---- 5. byte[] views ----
|
||||
byte[] data = new byte[40];
|
||||
new Random(7).nextBytes(data);
|
||||
for (int off = 0; off <= 32; off++) {
|
||||
long viewed = ViewBytes.get(data, off);
|
||||
if (!newOnly) require("byte[] getLong @" + off, UnsafeBytes.get(data, off), viewed);
|
||||
}
|
||||
byte[] x = new byte[16], y = new byte[16];
|
||||
ViewBytes.put(x, 3, 0x0102030405060708L);
|
||||
if (!newOnly) { UnsafeBytes.put(y, 3, 0x0102030405060708L); require("byte[] put", java.util.Arrays.toString(y), java.util.Arrays.toString(x)); }
|
||||
System.out.println("byte[] long access at offsets 0..32: " + (newOnly ? "VarHandle view only" : "Unsafe and VarHandle view agree"));
|
||||
|
||||
// ---- 6. what the replacement adds: a bounds check instead of a read past the end ----
|
||||
try { ViewBytes.get(data, 36); System.out.println("view past end: NO EXCEPTION"); System.exit(1); }
|
||||
catch (IndexOutOfBoundsException e) { System.out.println("VarHandle view at offset 36 of a 40-byte array: " + e.getClass().getSimpleName()); }
|
||||
try (OffHeapLongs o = new FfmOffHeapLongs(4)) { o.get(4); System.out.println("ffm past end: NO EXCEPTION"); System.exit(1); }
|
||||
catch (IndexOutOfBoundsException e) { System.out.println("FFM read of element 4 in a 4-element segment: " + e.getClass().getSimpleName()); }
|
||||
OffHeapLongs closed = new FfmOffHeapLongs(4); closed.close();
|
||||
try { closed.get(0); System.out.println("ffm after close: NO EXCEPTION"); System.exit(1); }
|
||||
catch (IllegalStateException e) { System.out.println("FFM read after close: " + e.getClass().getSimpleName() + ": " + e.getMessage()); }
|
||||
|
||||
System.out.println("ALL " + checks + " CHECKS PASSED" + (newOnly ? " (new APIs only)" : ""));
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
static String names(int n) { return n == 2 ? "Unsafe and the replacement" : "the replacement alone"; }
|
||||
|
||||
static long fieldTotal(FieldCounter c) throws Exception {
|
||||
Thread[] ts = new Thread[4];
|
||||
for (int t = 0; t < ts.length; t++) { ts[t] = new Thread(() -> { for (int k = 0; k < 100_000; k++) c.getAndAdd(1); }); ts[t].start(); }
|
||||
for (Thread t : ts) t.join();
|
||||
return c.get();
|
||||
}
|
||||
|
||||
static long[] fieldTrace(FieldCounter c) {
|
||||
Random r = new Random(42);
|
||||
long[] out = new long[2000];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
switch (r.nextInt(3)) {
|
||||
case 0 -> c.set(r.nextInt(100));
|
||||
case 1 -> out[i] = c.cas(r.nextInt(100), r.nextInt(100)) ? 1 : 0;
|
||||
default -> out[i] = c.getAndAdd(r.nextInt(5));
|
||||
}
|
||||
out[i] = out[i] * 1_000_003L + c.get();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static long[] offHeapTrace(OffHeapLongs o) {
|
||||
Random r = new Random(99);
|
||||
long[] out = new long[2000];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
int idx = r.nextInt(o.length());
|
||||
switch (r.nextInt(3)) {
|
||||
case 0 -> o.set(idx, r.nextInt(1000));
|
||||
case 1 -> out[i] = o.cas(idx, r.nextInt(1000), r.nextInt(1000)) ? 1 : 0;
|
||||
default -> out[i] = o.getAndAdd(idx, r.nextInt(7));
|
||||
}
|
||||
out[i] = out[i] * 1_000_003L + o.get(idx);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void same(String what, long[] a, long[] b) {
|
||||
for (int i = 0; i < a.length; i++) if (a[i] != b[i]) { System.out.println("MISMATCH " + what + " at step " + i); System.exit(1); }
|
||||
checks++;
|
||||
}
|
||||
static void require(String what, Object expected, Object actual) {
|
||||
checks++;
|
||||
if (!expected.equals(actual)) { System.out.println("MISMATCH " + what + ": expected " + expected + " got " + actual); System.exit(1); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.foreign.ValueLayout;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
/** AFTER: the same array on java.lang.foreign (final in JDK 22). Bounds and lifetime are checked by the runtime. */
|
||||
public class FfmOffHeapLongs implements OffHeapLongs {
|
||||
// access coordinates: (MemorySegment, long byteOffset); this VarHandle exposes get/set/volatile/CAS/getAndAdd modes
|
||||
private static final VarHandle LONG = ValueLayout.JAVA_LONG.varHandle();
|
||||
private final Arena arena = Arena.ofShared(); // shared: other threads may touch the segment; closing is thread-safe
|
||||
private final MemorySegment seg;
|
||||
private final int length;
|
||||
|
||||
public FfmOffHeapLongs(int length) {
|
||||
this.length = length;
|
||||
this.seg = arena.allocate(8L * length, 8); // zero-initialised, 8-byte aligned
|
||||
}
|
||||
public int length() { return length; }
|
||||
public long get(int i) { return (long) LONG.getVolatile(seg, 8L * i); }
|
||||
public void set(int i, long v) { LONG.setVolatile(seg, 8L * i, v); }
|
||||
public boolean cas(int i, long e, long u) { return LONG.compareAndSet(seg, 8L * i, e, u); }
|
||||
public long getAndAdd(int i, long d) { return (long) LONG.getAndAdd(seg, 8L * i, d); }
|
||||
public void clear() { seg.fill((byte) 0); }
|
||||
public void copyFrom(OffHeapLongs o) {
|
||||
if (o instanceof FfmOffHeapLongs f) MemorySegment.copy(f.seg, 0, seg, 0, 8L * length); // one bulk copy, no loop
|
||||
else for (int i = 0; i < length; i++) set(i, o.get(i));
|
||||
}
|
||||
public void close() { arena.close(); }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/** A volatile long field that several threads bump with compare-and-swap. Three implementations live in sibling files. */
|
||||
public interface FieldCounter {
|
||||
long get();
|
||||
void set(long v);
|
||||
boolean cas(long expect, long update);
|
||||
long getAndAdd(long delta);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/** A fixed-size off-heap array of longs. Two implementations: Unsafe (before) and java.lang.foreign (after). */
|
||||
public interface OffHeapLongs extends AutoCloseable {
|
||||
int length();
|
||||
long get(int i);
|
||||
void set(int i, long v);
|
||||
boolean cas(int i, long expect, long update);
|
||||
long getAndAdd(int i, long delta);
|
||||
void clear();
|
||||
void copyFrom(OffHeapLongs other);
|
||||
@Override void close();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import java.lang.reflect.Field;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/** The classic way every library gets an Unsafe: read the private static field "theUnsafe". */
|
||||
final class UnsafeAccess {
|
||||
private UnsafeAccess() {}
|
||||
|
||||
static Unsafe get() {
|
||||
try {
|
||||
Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
f.setAccessible(true);
|
||||
return (Unsafe) f.get(null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/** BEFORE: reading and writing a long at any offset of a byte[] with the "bimodal" Unsafe.getLong(Object, long). No bounds check. */
|
||||
public final class UnsafeBytes {
|
||||
private UnsafeBytes() {}
|
||||
private static final Unsafe U = UnsafeAccess.get();
|
||||
private static final long BASE = U.arrayBaseOffset(byte[].class);
|
||||
|
||||
public static long get(byte[] a, int off) { return U.getLong(a, BASE + off); }
|
||||
public static void put(byte[] a, int off, long v) { U.putLong(a, BASE + off, v); }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/** BEFORE: a CAS field the way pre-JDK-9 code did it. objectFieldOffset + compareAndSwapLong are deprecated for removal (JEP 471). */
|
||||
public class UnsafeFieldCounter implements FieldCounter {
|
||||
private static final Unsafe U = UnsafeAccess.get();
|
||||
private static final long VALUE;
|
||||
static {
|
||||
try { VALUE = U.objectFieldOffset(UnsafeFieldCounter.class.getDeclaredField("value")); }
|
||||
catch (NoSuchFieldException e) { throw new ExceptionInInitializerError(e); }
|
||||
}
|
||||
private volatile long value;
|
||||
|
||||
public long get() { return value; }
|
||||
public void set(long v) { value = v; }
|
||||
public boolean cas(long expect, long update) { return U.compareAndSwapLong(this, VALUE, expect, update); }
|
||||
public long getAndAdd(long delta) { return U.getAndAddLong(this, VALUE, delta); }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
/** BEFORE: raw malloc through Unsafe. Every call below except the constructor's helper is deprecated for removal. */
|
||||
public class UnsafeOffHeapLongs implements OffHeapLongs {
|
||||
private static final Unsafe U = UnsafeAccess.get();
|
||||
private final long address;
|
||||
private final int length;
|
||||
private boolean closed;
|
||||
|
||||
public UnsafeOffHeapLongs(int length) {
|
||||
this.length = length;
|
||||
this.address = U.allocateMemory(8L * length);
|
||||
U.setMemory(address, 8L * length, (byte) 0);
|
||||
}
|
||||
private long at(int i) {
|
||||
if (closed) throw new IllegalStateException("closed"); // with Unsafe nobody checks this: a stale address is a JVM crash, not an exception
|
||||
if (i < 0 || i >= length) throw new IndexOutOfBoundsException("index " + i);
|
||||
return address + 8L * i;
|
||||
}
|
||||
public int length() { return length; }
|
||||
public long get(int i) { return U.getLongVolatile(null, at(i)); }
|
||||
public void set(int i, long v) { U.putLongVolatile(null, at(i), v); }
|
||||
public boolean cas(int i, long e, long u) { return U.compareAndSwapLong(null, at(i), e, u); }
|
||||
public long getAndAdd(int i, long d) { return U.getAndAddLong(null, at(i), d); }
|
||||
public void clear() { U.setMemory(address, 8L * length, (byte) 0); }
|
||||
public void copyFrom(OffHeapLongs o) { for (int i = 0; i < length; i++) set(i, o.get(i)); }
|
||||
public void close() { if (!closed) { closed = true; U.freeMemory(address); } }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
/** AFTER: the same field with a VarHandle (JEP 193, JDK 9). No Unsafe anywhere. */
|
||||
public class VarHandleFieldCounter implements FieldCounter {
|
||||
private static final VarHandle VALUE;
|
||||
static {
|
||||
try { VALUE = MethodHandles.lookup().findVarHandle(VarHandleFieldCounter.class, "value", long.class); }
|
||||
catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); }
|
||||
}
|
||||
private volatile long value;
|
||||
|
||||
public long get() { return value; }
|
||||
public void set(long v) { value = v; }
|
||||
public boolean cas(long expect, long update) { return VALUE.compareAndSet(this, expect, update); }
|
||||
public long getAndAdd(long delta) { return (long) VALUE.getAndAdd(this, delta); }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/** AFTER: the same access through a byte-array-view VarHandle (JDK 9). Bounds-checked; native byte order to match Unsafe. */
|
||||
public final class ViewBytes {
|
||||
private ViewBytes() {}
|
||||
private static final VarHandle VIEW = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.nativeOrder());
|
||||
|
||||
public static long get(byte[] a, int off) { return (long) VIEW.get(a, off); }
|
||||
public static void put(byte[] a, int off, long v) { VIEW.set(a, off, v); }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.primitives.UnsignedBytes;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import java.util.Comparator;
|
||||
|
||||
/** Three Guava features whose implementations have an Unsafe fast path (see output/11): hashing, byte comparison, futures. Needs guava on the class path. */
|
||||
public class GuavaProbe {
|
||||
public static void main(String[] args) throws Exception {
|
||||
byte[] data = "unsafe-migration".getBytes();
|
||||
System.out.println("murmur3_128 = " + Hashing.murmur3_128().hashBytes(data));
|
||||
Comparator<byte[]> cmp = UnsignedBytes.lexicographicalComparator();
|
||||
System.out.println("comparator = " + cmp.getClass().getName());
|
||||
System.out.println("compare = " + Integer.signum(cmp.compare(new byte[] {1, 2, 3}, new byte[] {1, 2, 4})));
|
||||
SettableFuture<String> f = SettableFuture.create();
|
||||
f.set("ok");
|
||||
System.out.println("future = " + f.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
/** Asks the running JDK which public sun.misc.Unsafe methods are deprecated for removal. Run: java tools/ListUnsafe.java */
|
||||
public class ListUnsafe {
|
||||
public static void main(String[] args) throws Exception {
|
||||
List<String> kept = new ArrayList<>();
|
||||
int removal = 0, total = 0;
|
||||
for (Method m : Class.forName("sun.misc.Unsafe").getDeclaredMethods()) {
|
||||
if (!Modifier.isPublic(m.getModifiers())) continue;
|
||||
total++;
|
||||
Deprecated d = m.getAnnotation(Deprecated.class);
|
||||
if (d != null && d.forRemoval()) removal++;
|
||||
else kept.add(m.getName() + (d != null ? " (deprecated, not for removal)" : ""));
|
||||
}
|
||||
Collections.sort(kept);
|
||||
System.out.println("java.version=" + System.getProperty("java.version"));
|
||||
System.out.println("public methods declared by sun.misc.Unsafe: " + total);
|
||||
System.out.println("deprecated for removal: " + removal);
|
||||
System.out.println("not deprecated for removal: " + kept.size());
|
||||
kept.forEach(k -> System.out.println(" " + k));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
/** A real third-party library that uses Unsafe: allocate one direct Netty buffer and use it. Compile and run with netty-common + netty-buffer on the class path. */
|
||||
public class NettyProbe {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("netty sees Unsafe: " + PlatformDependent.hasUnsafe());
|
||||
ByteBuf buf = UnpooledByteBufAllocator.DEFAULT.directBuffer(16);
|
||||
buf.writeLong(0xCAFEBABEL);
|
||||
System.out.println("direct=" + buf.isDirect() + " hasMemoryAddress=" + buf.hasMemoryAddress() + " readLong=" + Long.toHexString(buf.readLong()));
|
||||
buf.release();
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Prints the system property the JVM sets from --sun-misc-unsafe-memory-access (libraries can read it). */
|
||||
public class Prop { public static void main(String[] a){ System.out.println("sun.misc.unsafe.memory.access=" + System.getProperty("sun.misc.unsafe.memory.access")); } }
|
||||
@@ -0,0 +1,97 @@
|
||||
import java.io.InputStream;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.CodeModel;
|
||||
import java.lang.classfile.MethodModel;
|
||||
import java.lang.classfile.instruction.InvokeInstruction;
|
||||
import java.lang.classfile.instruction.ConstantInstruction;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* Static scan of jars for call sites of the sun.misc.Unsafe methods that are deprecated for removal (JEP 471).
|
||||
*
|
||||
* It reads bytecode with the java.lang.classfile API (final in JDK 24). It does NOT run anything, so it reports
|
||||
* "this jar contains a call to X", never "this call is reached at run time". Which methods count as
|
||||
* "memory-access" is decided by asking the running JDK: a method is counted when its own Unsafe overload carries
|
||||
* @Deprecated(forRemoval = true).
|
||||
*
|
||||
* usage: java ScanJar.java [--classes] jar [jar ...]
|
||||
*/
|
||||
public class ScanJar {
|
||||
static final String UNSAFE = "sun/misc/Unsafe";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
boolean perClass = false;
|
||||
List<String> jars = new ArrayList<>();
|
||||
for (String a : args) { if (a.equals("--classes")) perClass = true; else jars.add(a); }
|
||||
|
||||
Set<String> removal = new HashSet<>(); // name + descriptor of every forRemoval overload
|
||||
int total = 0;
|
||||
for (Method m : Class.forName("sun.misc.Unsafe").getDeclaredMethods()) {
|
||||
if (!java.lang.reflect.Modifier.isPublic(m.getModifiers())) continue;
|
||||
total++;
|
||||
Deprecated d = m.getAnnotation(Deprecated.class);
|
||||
if (d != null && d.forRemoval()) {
|
||||
removal.add(m.getName() + MethodType.methodType(m.getReturnType(), m.getParameterTypes()).descriptorString());
|
||||
}
|
||||
}
|
||||
System.out.println("this JDK: sun.misc.Unsafe has " + total + " public methods, " + removal.size() + " deprecated for removal");
|
||||
|
||||
for (String jar : jars) scan(jar, removal, perClass);
|
||||
}
|
||||
|
||||
static void scan(String jar, Set<String> removal, boolean perClass) throws Exception {
|
||||
int classes = 0, refClasses = 0, callSites = 0, mrClasses = 0, stringRefs = 0;
|
||||
Map<String, Integer> methods = new TreeMap<>();
|
||||
Map<String, Integer> perClassCalls = new TreeMap<>();
|
||||
Set<String> otherUnsafeCalls = new TreeSet<>();
|
||||
Set<String> mrCallers = new TreeSet<>();
|
||||
try (JarFile jf = new JarFile(jar)) {
|
||||
for (JarEntry e : Collections.list(jf.entries())) {
|
||||
String n = e.getName();
|
||||
if (!n.endsWith(".class") || n.endsWith("module-info.class")) continue;
|
||||
boolean mr = n.startsWith("META-INF/versions/");
|
||||
byte[] bytes;
|
||||
try (InputStream in = jf.getInputStream(e)) { bytes = in.readAllBytes(); }
|
||||
var cm = ClassFile.of().parse(bytes);
|
||||
classes++;
|
||||
boolean ref = false; int calls = 0;
|
||||
for (MethodModel mm : cm.methods()) {
|
||||
CodeModel code = mm.code().orElse(null);
|
||||
if (code == null) continue;
|
||||
for (var el : code) {
|
||||
if (el instanceof InvokeInstruction ii && ii.owner().asInternalName().equals(UNSAFE)) {
|
||||
ref = true;
|
||||
String key = ii.name().stringValue() + ii.typeSymbol().descriptorString();
|
||||
if (removal.contains(key)) { calls++; methods.merge(ii.name().stringValue(), 1, Integer::sum); }
|
||||
else otherUnsafeCalls.add(ii.name().stringValue());
|
||||
} else if (el instanceof ConstantInstruction.LoadConstantInstruction lc
|
||||
&& lc.constantEntry().constantValue() instanceof String s
|
||||
&& (s.equals("sun.misc.Unsafe") || s.equals("theUnsafe"))) {
|
||||
stringRefs++; ref = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ref) refClasses++;
|
||||
if (calls > 0) {
|
||||
callSites += calls;
|
||||
String cn = n.substring(0, n.length() - 6).replace('/', '.');
|
||||
perClassCalls.put(cn, calls);
|
||||
if (mr) { mrClasses++; mrCallers.add(cn); }
|
||||
}
|
||||
}
|
||||
}
|
||||
String name = jar.substring(jar.lastIndexOf('/') + 1);
|
||||
System.out.println();
|
||||
System.out.println(name + ": " + classes + " classes scanned");
|
||||
System.out.println(" classes touching sun.misc.Unsafe (call, or the strings 'sun.misc.Unsafe'/'theUnsafe'): " + refClasses);
|
||||
System.out.println(" classes calling a removal-deprecated Unsafe method: " + perClassCalls.size() + " (" + callSites + " call sites, " + methods.size() + " distinct methods)");
|
||||
if (!methods.isEmpty()) System.out.println(" methods: " + methods);
|
||||
if (!otherUnsafeCalls.isEmpty()) System.out.println(" other (not deprecated for removal) Unsafe methods called: " + otherUnsafeCalls);
|
||||
if (!mrCallers.isEmpty()) System.out.println(" of those classes, under META-INF/versions: " + mrCallers.size());
|
||||
if (perClass) perClassCalls.forEach((k, v) -> System.out.println(" " + k + " " + v));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user