Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2c03524
refactor(java): move buffer equality out of UnsafeOps
chaokunyang May 23, 2026
b9129ac
refactor(java): move direct buffer address access to MemoryBuffer
chaokunyang May 23, 2026
1fcdfea
remove unused api in UnsafeOps
chaokunyang May 23, 2026
83e1015
refactor(java): hide field offsets behind accessors
chaokunyang May 23, 2026
4179012
remove unused code
chaokunyang May 23, 2026
c3f76ef
remove UNSAFE.throwException usage
chaokunyang May 23, 2026
0add063
refactor(java): add Fory byte array streams
chaokunyang May 23, 2026
4603f53
refactor(java): route byte array stream wrapping through JDK access
chaokunyang May 23, 2026
8361bd4
remove unsafe fields sort
chaokunyang May 23, 2026
c563565
refactor(java): route private field access through accessors
chaokunyang May 23, 2026
1187a4a
refactor(java): move JDK access utilities to platform internal
chaokunyang May 23, 2026
26dec9d
fix(java): add JDK25 unsafe replacements
chaokunyang May 23, 2026
0947efd
feat(java): add JDK25 field accessor
chaokunyang May 23, 2026
1ed30dd
feat(java): add JDK25 zero-unsafe runtime path
chaokunyang May 24, 2026
b8ce0ab
chore(java): fix JDK25 memory buffer license header
chaokunyang May 24, 2026
287d5ef
fix(java): keep string coder lookup Java 8 safe
chaokunyang May 24, 2026
e947b5c
fix(ci): repair style and kotlin xlang peer jar
chaokunyang May 24, 2026
e9303f8
fix(ci): avoid android and graal field access failures
chaokunyang May 24, 2026
c8b8dda
style(ci): format java task helper
chaokunyang May 24, 2026
999fd92
fix(android): skip jdk string internals on android
chaokunyang May 24, 2026
e56a62d
fix(graalvm): use public string access in native images
chaokunyang May 24, 2026
607a180
fix(graalvm): avoid private string codegen in native images
chaokunyang May 24, 2026
047f712
perf(java): add JDK25 direct memory benchmark
chaokunyang May 24, 2026
139431b
perf(java): add JDK25 direct copy benchmark
chaokunyang May 24, 2026
79e7513
refactor(java): isolate string unsafe operations
chaokunyang May 24, 2026
d602cbf
refactor(java): remove versioned lambda serializer
chaokunyang May 24, 2026
a17ab3f
refactor(java): remove versioned serializers registry
chaokunyang May 24, 2026
1daa83e
docs(java): update final field mutation guidance
chaokunyang May 24, 2026
7272e5d
fix(java): restore final fields with method handles on JDK25
chaokunyang May 24, 2026
d5098bc
refactor(java): fold JDK25 codec builders into root
chaokunyang May 24, 2026
46f6324
feat(java): remove Unsafe for jdk25
chaokunyang May 24, 2026
f1f7b2e
fix(java): restore android and graal ci
chaokunyang May 24, 2026
0ff50fc
fix(java): open arrow memory on classpath
chaokunyang May 24, 2026
1517758
fix(java): restore graalvm array offset recompute
chaokunyang May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release-java-snapshot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ jobs:
- name: Set up Maven Central Repository
uses: actions/setup-java@v4
with:
java-version: "11"
distribution: "adopt"
java-version: "25"
distribution: "temurin"
architecture: x64
cache: maven
server-id: apache.snapshots.https
server-username: NEXUS_USERNAME
server-password: NEXUS_PASSWORD
- name: Publish Fory Java Snapshot
run: python ./ci/run_ci.py java --version 11 --release
run: python ./ci/run_ci.py java --version 25 --release
env:
NEXUS_USERNAME: ${{ secrets.NEXUS_USER }}
NEXUS_PASSWORD: ${{ secrets.NEXUS_PW }}
128 changes: 128 additions & 0 deletions benchmarks/java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,117 @@
</dependency>
</dependencies>
</profile>
<profile>
<id>jdk25-benchmark-mrjar-check</id>
<activation>
<jdk>[25,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>verify-benchmark-mrjar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<property
name="jdk25.benchmark.check.dir"
value="${project.build.directory}/jdk25-benchmark-check"/>
<delete dir="${jdk25.benchmark.check.dir}"/>
<mkdir dir="${jdk25.benchmark.check.dir}"/>
<unzip
src="${project.build.directory}/${uberjar.name}.jar"
dest="${jdk25.benchmark.check.dir}">
<patternset>
<include
name="org/apache/fory/platform/UnsafeOps.class"/>
<include
name="META-INF/versions/25/org/apache/fory/platform/UnsafeOps.class"/>
<include
name="META-INF/versions/25/org/apache/fory/memory/LittleEndian.class"/>
<include
name="META-INF/versions/25/org/apache/fory/memory/MemoryBuffer.class"/>
<include
name="META-INF/versions/25/org/apache/fory/platform/internal/_JDKAccess.class"/>
<include
name="META-INF/versions/25/org/apache/fory/reflect/FieldAccessor.class"/>
<include
name="META-INF/versions/25/org/apache/fory/reflect/HiddenFieldAccessorFactory.class"/>
<include
name="META-INF/versions/25/org/apache/fory/serializer/PlatformStringUtils.class"/>
</patternset>
</unzip>
<available
file="${jdk25.benchmark.check.dir}/org/apache/fory/platform/UnsafeOps.class"
property="jdk25.benchmark.rootunsafeops.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/platform/UnsafeOps.class"
property="jdk25.benchmark.unsafeops.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/memory/LittleEndian.class"
property="jdk25.benchmark.littleendian.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/memory/MemoryBuffer.class"
property="jdk25.benchmark.memorybuffer.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/platform/internal/_JDKAccess.class"
property="jdk25.benchmark.jdkaccess.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/reflect/FieldAccessor.class"
property="jdk25.benchmark.fieldaccessor.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/reflect/HiddenFieldAccessorFactory.class"
property="jdk25.benchmark.hiddenfieldaccessorfactory.present"/>
<available
file="${jdk25.benchmark.check.dir}/META-INF/versions/25/org/apache/fory/serializer/PlatformStringUtils.class"
property="jdk25.benchmark.platformstring.present"/>
<fail
if="jdk25.benchmark.rootunsafeops.present"
message="JDK25 benchmark jar must not contain root UnsafeOps class."/>
<fail
if="jdk25.benchmark.unsafeops.present"
message="JDK25 benchmark jar must not contain versioned UnsafeOps class."/>
<fail
unless="jdk25.benchmark.littleendian.present"
message="JDK25 benchmark jar is missing the versioned LittleEndian class."/>
<fail
unless="jdk25.benchmark.memorybuffer.present"
message="JDK25 benchmark jar is missing the versioned MemoryBuffer class."/>
<fail
unless="jdk25.benchmark.jdkaccess.present"
message="JDK25 benchmark jar is missing the versioned _JDKAccess class."/>
<fail
unless="jdk25.benchmark.fieldaccessor.present"
message="JDK25 benchmark jar is missing the versioned FieldAccessor class."/>
<fail
unless="jdk25.benchmark.hiddenfieldaccessorfactory.present"
message="JDK25 benchmark jar is missing the versioned HiddenFieldAccessorFactory class."/>
<fail
unless="jdk25.benchmark.platformstring.present"
message="JDK25 benchmark jar is missing the versioned PlatformStringUtils class."/>
<java
classname="org.apache.fory.benchmark.Jdk25MrJarCheck"
fork="true"
failonerror="true">
<classpath>
<pathelement location="${project.build.directory}/${uberjar.name}.jar"/>
</classpath>
<jvmarg value="--sun-misc-unsafe-memory-access=deny"/>
</java>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<build>
Expand Down Expand Up @@ -266,6 +377,7 @@
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
<Automatic-Module-Name>org.apache.fory.benchmark</Automatic-Module-Name>
</manifestEntries>
</archive>
Expand All @@ -287,6 +399,10 @@
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
<manifestEntries>
<Multi-Release>true</Multi-Release>
<Automatic-Module-Name>org.apache.fory.benchmark</Automatic-Module-Name>
</manifestEntries>
</transformer>
</transformers>
<filters>
Expand All @@ -298,6 +414,18 @@
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
<filter>
<artifact>org.apache.logging.log4j:*</artifact>
<excludes>
<exclude>META-INF/versions/**</exclude>
</excludes>
</filter>
<filter>
<artifact>com.fasterxml.jackson.core:jackson-core</artifact>
<excludes>
<exclude>META-INF/versions/**</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.nio.ByteBuffer;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.serializer.StringEncodingUtils;
import org.apache.fory.util.StringUtils;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.annotations.Benchmark;
Expand Down Expand Up @@ -99,7 +100,7 @@ public Object latinScalarCheck() {

@Benchmark
public Object latinSuperWordCheck() {
return StringUtils.isLatin(latinStrChars);
return StringEncodingUtils.isLatin(latinStrChars);
}

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.util.ArrayList;
import java.util.List;
import org.apache.fory.platform.UnsafeOps;

// Derived from
// https://github.com/RuedigerMoeller/fast-serialization/blob/e8da5591daa09452791dcd992ea4f83b20937be7/src/main/java/org/nustaq/serialization/util/FSTIdentity2IdMap.java.
Expand Down Expand Up @@ -405,20 +404,10 @@ public static void clear(int[] arr, int len) {
int count = 0;
final int emptyArrayLength = EMPTY_INT_ARRAY.length;
while (len - count > emptyArrayLength) {
UnsafeOps.copyMemory(
EMPTY_INT_ARRAY,
UnsafeOps.INT_ARRAY_OFFSET,
arr,
UnsafeOps.INT_ARRAY_OFFSET + count,
emptyArrayLength);
System.arraycopy(EMPTY_INT_ARRAY, 0, arr, count, emptyArrayLength);
count += emptyArrayLength;
}
UnsafeOps.copyMemory(
EMPTY_INT_ARRAY,
UnsafeOps.INT_ARRAY_OFFSET,
arr,
UnsafeOps.INT_ARRAY_OFFSET + count,
len - count);
System.arraycopy(EMPTY_INT_ARRAY, 0, arr, count, len - count);
}

public static void clear(Object[] arr, int len) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fory.benchmark;

import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.platform.internal._JDKAccess;
import org.apache.fory.reflect.FieldAccessor;

/** Runtime smoke check that JDK25 benchmark runs load the multi-release Fory classes. */
public final class Jdk25MrJarCheck {
private Jdk25MrJarCheck() {}

public static void main(String[] args) {
verifyClass(MemoryBuffer.class);
verifyMissing("org.apache.fory.platform.UnsafeOps");
verifyClass(_JDKAccess.class);
verifyClass(FieldAccessor.class);
verifyClass("org.apache.fory.serializer.PlatformStringUtils");
if (_JDKAccess.UNSAFE != null) {
throw new IllegalStateException("JDK25 benchmark jar loaded Unsafe-backed _JDKAccess");
}
}

private static void verifyMissing(String className) {
try {
Class.forName(className);
throw new IllegalStateException("JDK25 benchmark jar must not contain " + className);
} catch (ClassNotFoundException expected) {
// expected
}
}

private static void verifyClass(String className) {
try {
verifyClass(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new IllegalStateException("JDK25 benchmark jar is missing " + className, e);
}
}

private static void verifyClass(Class<?> cls) {
String resourceName = cls.getSimpleName() + ".class";
String resource = String.valueOf(cls.getResource(resourceName));
if (!resource.contains("benchmarks.jar!") || !resource.contains("!/META-INF/versions/25/")) {
throw new IllegalStateException("JDK25 benchmark jar loaded root class for " + cls);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.Random;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
import org.apache.fory.platform.UnsafeOps;
import org.apache.fory.util.StringUtils;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.annotations.BenchmarkMode;
Expand All @@ -33,10 +32,13 @@
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import sun.misc.Unsafe;

@BenchmarkMode(Mode.Throughput)
@CompilerControl(value = CompilerControl.Mode.INLINE)
public class MemorySuite {
private static final Unsafe UNSAFE = UnsafeAccess.load();
private static final int BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
static int arrLen = 32;

static {
Expand Down Expand Up @@ -138,12 +140,8 @@ public Object systemArrayCopy(MemoryState state) {

@org.openjdk.jmh.annotations.Benchmark
public Object unsafeCopy(MemoryState state) {
UnsafeOps.UNSAFE.copyMemory(
state.bytes,
UnsafeOps.BYTE_ARRAY_OFFSET,
target,
UnsafeOps.BYTE_ARRAY_OFFSET,
state.bytes.length);
UNSAFE.copyMemory(
state.bytes, BYTE_ARRAY_OFFSET, target, BYTE_ARRAY_OFFSET, state.bytes.length);
return target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,28 @@
import org.apache.fory.Fory;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.platform.JdkVersion;
import org.apache.fory.platform.UnsafeOps;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.serializer.StringSerializer;
import org.apache.fory.util.Preconditions;
import org.apache.fory.util.StringUtils;
import org.openjdk.jmh.Main;
import sun.misc.Unsafe;

public class NewJava11StringSuite {
private static final Unsafe UNSAFE = UnsafeAccess.load();

static String str = StringUtils.random(10);
static byte[] strBytes;
static byte coder;

static {
if (JdkVersion.MAJOR_VERSION > 8) {
strBytes =
(byte[]) UnsafeOps.getObject(str, ReflectionUtils.getFieldOffset(String.class, "value"));
coder = UnsafeOps.getByte(str, ReflectionUtils.getFieldOffset(String.class, "coder"));
strBytes = (byte[]) UNSAFE.getObject(str, fieldOffset(String.class, "value"));
coder = UNSAFE.getByte(str, fieldOffset(String.class, "coder"));
}
}

private static final long STRING_VALUE_FIELD_OFFSET =
ReflectionUtils.getFieldOffset(String.class, "value");
private static final long STRING_CODER_FIELD_OFFSET =
ReflectionUtils.getFieldOffset(String.class, "coder");
private static final long STRING_VALUE_FIELD_OFFSET = fieldOffset(String.class, "value");
private static final long STRING_CODER_FIELD_OFFSET = fieldOffset(String.class, "coder");

private static String stubStr = new String(new char[] {Character.MAX_VALUE, Character.MIN_VALUE});
private static Fory fory =
Expand All @@ -58,6 +55,14 @@ public class NewJava11StringSuite {
stringSerializer.writeString(buffer, str);
}

private static long fieldOffset(Class<?> type, String fieldName) {
try {
return UNSAFE.objectFieldOffset(type.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}

// @Benchmark
public Object createJDK11StringByCopyStr() {
return new String(str);
Expand All @@ -66,8 +71,8 @@ public Object createJDK11StringByCopyStr() {
// @Benchmark
public Object createJDK11StringByUnsafe() {
String str = new String(stubStr);
UnsafeOps.putObject(str, STRING_VALUE_FIELD_OFFSET, strBytes);
UnsafeOps.putObject(str, STRING_CODER_FIELD_OFFSET, coder);
UNSAFE.putObject(str, STRING_VALUE_FIELD_OFFSET, strBytes);
UNSAFE.putByte(str, STRING_CODER_FIELD_OFFSET, coder);
return str;
}

Expand Down
Loading
Loading