Skip to content

Latest commit

 

History

History
329 lines (251 loc) · 9.35 KB

File metadata and controls

329 lines (251 loc) · 9.35 KB
title GraalVM Guide
sidebar_position 19
id serialization
license 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.

GraalVM Native Image

GraalVM native image compiles Java code into native executables ahead-of-time, resulting in faster startup and lower memory usage. However, native images don't support runtime JIT compilation or reflection without explicit configuration.

Apache Fory™ works excellently with GraalVM native image by using codegen instead of reflection. All serializer code is generated at build time, eliminating the need for reflection configuration files in most cases.

How It Works

Fory generates serialization code at GraalVM build time when you:

  1. Create Fory as a static field
  2. Register all classes in a static initializer
  3. Call fory.ensureSerializersCompiled() to compile serializers
  4. Configure the class to initialize at build time via native-image.properties

The main benefit: You don't need to configure reflection json or serialization json for most serializable classes.

Note: Fory's asyncCompilationEnabled option is automatically disabled for GraalVM native image since runtime JIT is not supported.

Basic Usage

Step 0: Add the GraalVM Support Dependency

Add fory-graalvm-feature to your application dependencies when building a native image:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-graalvm-feature</artifactId>
  <version>${fory.version}</version>
</dependency>

This dependency already ships GraalVM feature metadata in META-INF/native-image, so adding it automatically enables org.apache.fory.graalvm.feature.ForyGraalVMFeature during native-image builds.

Step 1: Create Fory and Register Classes

import org.apache.fory.Fory;

public class Example {
  // Must be static field
  static Fory fory;

  static {
    fory = Fory.builder().build();
    fory.register(MyClass.class);
    fory.register(AnotherClass.class);
    // Compile all serializers at build time
    fory.ensureSerializersCompiled();
  }

  public static void main(String[] args) {
    byte[] bytes = fory.serialize(new MyClass());
    MyClass obj = (MyClass) fory.deserialize(bytes);
  }
}

Step 2: Configure Build-Time Initialization

Create resources/META-INF/native-image/your-group/your-artifact/native-image.properties:

Args = --initialize-at-build-time=com.example.Example

What fory-graalvm-feature Handles

After you add the fory-graalvm-feature dependency, Fory automatically registers the extra GraalVM metadata needed by advanced cases such as:

  • Private constructors (classes without accessible no-arg constructor)
  • Private inner classes/records
  • Dynamic proxy serialization

This removes the need for manual reflect-config.json in most applications. Your own native-image.properties still only needs to configure your build-time initialized bootstrap class, for example:

Args = --initialize-at-build-time=com.example.Example
Scenario Without Feature With Feature
Public classes with no-arg ctor ✅ Works ✅ Works
Private constructors ❌ Needs reflect-config.json ✅ Auto-registered
Private inner records ❌ Needs reflect-config.json ✅ Auto-registered
Dynamic proxies ❌ Needs manual config ✅ Auto-registered

Example with Private Record

public class Example {
  // Private inner record - requires ForyGraalVMFeature
  private record PrivateRecord(int id, String name) {}

  static Fory fory;

  static {
    fory = Fory.builder().build();
    fory.register(PrivateRecord.class);
    fory.ensureSerializersCompiled();
  }
}

Example with Dynamic Proxy

import org.apache.fory.util.GraalvmSupport;

public class ProxyExample {
  public interface MyService {
    String execute();
  }

  public interface Audited {
    String traceId();
  }

  static Fory fory;

  static {
    fory = Fory.builder().build();
    // Register the exact interface list used by Proxy.newProxyInstance(...)
    GraalvmSupport.registerProxySupport(MyService.class, Audited.class);
    fory.ensureSerializersCompiled();
  }
}

Use registerProxySupport(MyService.class) for a single-interface proxy. For proxies that implement multiple interfaces, pass the full interface list in the same order used to create the proxy. With fory-graalvm-feature on the classpath, this replaces manual proxy-config.json entries for those registered proxy shapes.

Thread-Safe Fory

For multi-threaded applications, use ThreadLocalFory:

import org.apache.fory.Fory;
import org.apache.fory.ThreadLocalFory;
import org.apache.fory.ThreadSafeFory;

public class ThreadSafeExample {
  public record Foo(int f1, String f2, List<String> f3) {}

  static ThreadSafeFory fory;

  static {
    fory = new ThreadLocalFory(builder -> {
      Fory f = builder.build();
      f.register(Foo.class);
      f.ensureSerializersCompiled();
      return f;
    });
  }

  public static void main(String[] args) {
    Foo foo = new Foo(10, "abc", List.of("str1", "str2"));
    byte[] bytes = fory.serialize(foo);
    Foo result = (Foo) fory.deserialize(bytes);
  }
}

Troubleshooting

"Type is instantiated reflectively but was never registered"

If you see this error:

Type com.example.MyClass is instantiated reflectively but was never registered

Solution: Register the class with Fory (don't add to reflect-config.json):

fory.register(MyClass.class);
fory.ensureSerializersCompiled();

If the class has a private constructor, either:

  1. Make sure fory-graalvm-feature is already on the native-image classpath, or
  2. Create a reflect-config.json for that specific class

Framework Integration

For framework developers integrating Fory:

  1. Provide a configuration file for users to list serializable classes
  2. Load those classes and call fory.register(Class<?>) for each
  3. Call fory.ensureSerializersCompiled() after all registrations
  4. Configure your integration class for build-time initialization

Benchmark

Performance comparison between Fory and GraalVM JDK Serialization:

Type Compression Speed Size
Struct Off 46x faster 43%
Struct On 24x faster 31%
Pojo Off 12x faster 56%
Pojo On 12x faster 48%

See Benchmark.java for benchmark code.

Struct Benchmark

Class Fields

public class Struct implements Serializable {
  public int f1;
  public long f2;
  public float f3;
  public double f4;
  public int f5;
  public long f6;
  public float f7;
  public double f8;
  public int f9;
  public long f10;
  public float f11;
  public double f12;
}

Benchmark Results

No compression:

Benchmark repeat number: 400000
Object type: class org.apache.fory.graalvm.Struct
Compress number: false
Fory size: 76.0
JDK size: 178.0
Fory serialization took mills: 49
JDK serialization took mills: 2254
Compare speed: Fory is 45.70x speed of JDK
Compare size: Fory is 0.43x size of JDK

Compress number:

Benchmark repeat number: 400000
Object type: class org.apache.fory.graalvm.Struct
Compress number: true
Fory size: 55.0
JDK size: 178.0
Fory serialization took mills: 130
JDK serialization took mills: 3161
Compare speed: Fory is 24.16x speed of JDK
Compare size: Fory is 0.31x size of JDK

Pojo Benchmark

Class Fields

public class Foo implements Serializable {
  int f1;
  String f2;
  List<String> f3;
  Map<String, Long> f4;
}

Benchmark Results

No compression:

Benchmark repeat number: 400000
Object type: class org.apache.fory.graalvm.Foo
Compress number: false
Fory size: 541.0
JDK size: 964.0
Fory serialization took mills: 1663
JDK serialization took mills: 16266
Compare speed: Fory is 12.19x speed of JDK
Compare size: Fory is 0.56x size of JDK

Compress number:

Benchmark repeat number: 400000
Object type: class org.apache.fory.graalvm.Foo
Compress number: true
Fory size: 459.0
JDK size: 964.0
Fory serialization took mills: 1289
JDK serialization took mills: 15069
Compare speed: Fory is 12.11x speed of JDK
Compare size: Fory is 0.48x size of JDK