New posts

This commit is contained in:
2026-11-06 21:43:48 +05:30
parent ecceaa8b32
commit f7b6c1efca
33 changed files with 1258 additions and 0 deletions
+1
View File
@@ -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/`)
+24
View File
@@ -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.
+29
View File
@@ -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
+3
View File
@@ -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" "$@"
+113
View File
@@ -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"
+42
View File
@@ -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"
+141
View File
@@ -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); }
}
}
+29
View File
@@ -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(); }
}
+7
View File
@@ -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);
}
+11
View File
@@ -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();
}
+17
View File
@@ -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);
}
}
}
+11
View File
@@ -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); }
}
+12
View File
@@ -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); }
}
+18
View File
@@ -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());
}
}
+24
View File
@@ -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));
}
}
+15
View File
@@ -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");
}
}
+2
View File
@@ -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")); } }
+97
View File
@@ -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));
}
}