This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
The following commit(s) were added to refs/heads/main by this push:
new 78f0afbf6c π synced local 'docs/guide/' with remote 'docs/guide/'
78f0afbf6c is described below
commit 78f0afbf6c247548e7922c0a6a46c25461b45c2d
Author: chaokunyang <[email protected]>
AuthorDate: Mon Mar 30 15:03:53 2026 +0000
π synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/java/configuration.md | 2 +-
docs/guide/java/index.md | 91 ++++++++++++-------------
docs/guide/java/schema-evolution.md | 4 +-
docs/guide/java/type-registration.md | 4 +-
docs/guide/java/virtual-threads.md | 124 +++++++++++++++++++++++------------
5 files changed, 128 insertions(+), 97 deletions(-)
diff --git a/docs/guide/java/configuration.md b/docs/guide/java/configuration.md
index 18a9d6b640..ece66d5080 100644
--- a/docs/guide/java/configuration.md
+++ b/docs/guide/java/configuration.md
@@ -31,7 +31,7 @@ This page documents all configuration options available
through `ForyBuilder`.
| `compressIntArray` | Enables or disables SIMD-accelerated
compression for int arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
| `compressLongArray` | Enables or disables SIMD-accelerated
compression for long arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
| `compressString` | Enables or disables string compression
for smaller size.
[...]
-| `classLoader` | The classloader should not be updated;
Fory caches class metadata. Use `LoaderBinding` or `ThreadSafeFory` for
classloader updates.
[...]
+| `classLoader` | The classloader is fixed when the
`Fory` or `ThreadSafeFory` instance is built. If you need a different
classloader, build a different instance.
[...]
| `compatibleMode` | Type forward/backward compatibility
config. Also Related to `checkClassVersion` config. `SCHEMA_CONSISTENT`: Class
schema must be consistent between serialization peer and deserialization peer.
`COMPATIBLE`: Class schema can be different between serialization peer and
deserialization peer. They can add/delete fields independently. [See
more](schema-evolution.md).
[...]
| `checkClassVersion` | Determines whether to check the
consistency of the class schema. If enabled, Fory checks, writes, and checks
consistency using the `classVersionHash`. It will be automatically disabled
when `CompatibleMode#COMPATIBLE` is enabled. Disabling is not recommended
unless you can ensure the class won't evolve.
[...]
| `checkJdkClassSerializable` | Enables or disables checking of
`Serializable` interface for classes under `java.*`. If a class under `java.*`
is not `Serializable`, Fory will throw an `UnsupportedOperationException`.
[...]
diff --git a/docs/guide/java/index.md b/docs/guide/java/index.md
index dc8856d082..2ce65869a5 100644
--- a/docs/guide/java/index.md
+++ b/docs/guide/java/index.md
@@ -83,23 +83,16 @@ public class Example {
### Multi-Thread Usage
```java
-import java.util.List;
-import java.util.Arrays;
-
import org.apache.fory.*;
import org.apache.fory.config.*;
public class Example {
public static void main(String[] args) {
SomeClass object = new SomeClass();
- // Note that Fory instances should be reused between
- // multiple serializations of different objects.
- ThreadSafeFory fory = new ThreadLocalFory(classLoader -> {
- Fory f = Fory.builder().withLanguage(Language.JAVA)
- .withClassLoader(classLoader).build();
- f.register(SomeClass.class, 1);
- return f;
- });
+ ThreadSafeFory fory = Fory.builder()
+ .withLanguage(Language.JAVA)
+ .buildThreadSafeFory();
+ fory.register(SomeClass.class, 1);
byte[] bytes = fory.serialize(object);
System.out.println(fory.deserialize(bytes));
}
@@ -109,20 +102,17 @@ public class Example {
### Fory Instance Reuse Pattern
```java
-import java.util.List;
-import java.util.Arrays;
-
import org.apache.fory.*;
import org.apache.fory.config.*;
public class Example {
- // reuse fory.
- private static final ThreadSafeFory fory = new ThreadLocalFory(classLoader
-> {
- Fory f = Fory.builder().withLanguage(Language.JAVA)
- .withClassLoader(classLoader).build();
- f.register(SomeClass.class, 1);
- return f;
- });
+ private static final ThreadSafeFory fory = Fory.builder()
+ .withLanguage(Language.JAVA)
+ .buildThreadSafeFory();
+
+ static {
+ fory.register(SomeClass.class, 1);
+ }
public static void main(String[] args) {
SomeClass object = new SomeClass();
@@ -134,26 +124,12 @@ public class Example {
## Thread Safety
-Fory provides multiple options for thread-safe serialization:
-
-### ThreadLocalFory
-
-Uses thread-local storage to maintain separate Fory instances per thread:
-
-```java
-ThreadSafeFory fory = new ThreadLocalFory(classLoader -> {
- Fory f = Fory.builder().withLanguage(Language.JAVA)
- .withClassLoader(classLoader).build();
- f.register(SomeClass.class, 1);
- return f;
-});
-byte[] bytes = fory.serialize(object);
-System.out.println(fory.deserialize(bytes));
-```
+Fory provides two thread-safe runtime styles:
-### Virtual Threads
+### `buildThreadSafeFory`
-For JDK 21+ virtual-thread workloads, use `buildVirtualThreadSafeFory(...)`:
+This is the default choice. It uses a fixed-size shared `ThreadPoolFory` sized
to
+`4 * availableProcessors()` and is the preferred runtime for virtual-thread
workloads:
```java
ThreadSafeFory fory = Fory.builder()
@@ -161,15 +137,31 @@ ThreadSafeFory fory = Fory.builder()
.withRefTracking(false)
.withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT)
.withAsyncCompilation(true)
- .buildVirtualThreadSafeFory();
+ .buildThreadSafeFory();
```
See more details in [Virtual Threads](virtual-threads.md).
-### ThreadSafeForyPool
+### ThreadLocalFory
+
+Use `buildThreadLocalFory()` only when you explicitly want one `Fory` instance
per long-lived
+platform thread, or when you want to pin that choice regardless of JDK version:
+
+```java
+ThreadSafeFory fory = Fory.builder()
+ .withLanguage(Language.JAVA)
+ .buildThreadLocalFory();
+fory.register(SomeClass.class, 1);
+byte[] bytes = fory.serialize(object);
+System.out.println(fory.deserialize(bytes));
+```
+
+### `buildThreadSafeForyPool`
-For environments where thread-local storage is not appropriate and you need
the existing
-time-expiring pooled runtime, use `buildThreadSafeForyPool`:
+Use `buildThreadSafeForyPool(poolSize)` when you want to set that fixed shared
pool size
+explicitly. It eagerly creates `poolSize` `Fory` instances, keeps them in
shared fixed slots, and
+then lets any caller borrow one through a thread-agnostic fast path. Calls
only block when every
+pooled instance is already in use; the runtime does not key cached instances
by thread identity:
```java
ThreadSafeFory fory = Fory.builder()
@@ -177,13 +169,9 @@ ThreadSafeFory fory = Fory.builder()
.withRefTracking(false)
.withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT)
.withAsyncCompilation(true)
- .buildThreadSafeForyPool(minPoolSize, maxPoolSize);
+ .buildThreadSafeForyPool(poolSize);
```
-Note that calling `buildThreadSafeFory()` on `ForyBuilder` creates a
`ThreadLocalFory`. This is
-not a good default for virtual-thread workloads because each virtual thread
can create its own
-`Fory` instance. For virtual threads, prefer `buildVirtualThreadSafeFory(...)`.
-
### Builder Methods
```java
@@ -195,13 +183,18 @@ Fory fory = Fory.builder()
.withAsyncCompilation(true)
.build();
-// Thread-safe Fory (ThreadLocalFory)
+// Thread-safe Fory (thread-safe Fory backed by a pool of Fory instances)
ThreadSafeFory fory = Fory.builder()
.withLanguage(Language.JAVA)
.withRefTracking(false)
.withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT)
.withAsyncCompilation(true)
.buildThreadSafeFory();
+
+// Explicit thread-local runtime
+ThreadSafeFory threadLocalFory = Fory.builder()
+ .withLanguage(Language.JAVA)
+ .buildThreadLocalFory();
```
## Next Steps
diff --git a/docs/guide/java/schema-evolution.md
b/docs/guide/java/schema-evolution.md
index 8dce5824c9..c03b52d9c2 100644
--- a/docs/guide/java/schema-evolution.md
+++ b/docs/guide/java/schema-evolution.md
@@ -90,7 +90,6 @@ fory.deserialize(bytes);
```java
// Thread-safe fory
-fory.setClassLoader(beanA.getClass().getClassLoader());
byte[] serialized = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
@@ -99,7 +98,6 @@ byte[] serialized = fory.execute(
);
// Thread-safe fory
-fory.setClassLoader(beanA.getClass().getClassLoader());
Object newObj = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
@@ -108,7 +106,7 @@ Object newObj = fory.execute(
);
```
-**Note**: `MetaContext` is not thread-safe and cannot be reused across
instances of Fory or multiple threads. In cases of multi-threading, a separate
`MetaContext` must be created for each Fory instance.
+**Note**: `MetaContext` is not thread-safe and cannot be reused across
instances of Fory or multiple threads. `buildThreadSafeFory()` is pooled, so
create a fresh `MetaContext` for each borrow unless you keep working with the
same raw `Fory` instance inside one `execute(...)` call. If you need to reuse
one `MetaContext` across multiple calls on the same worker thread, prefer
`buildThreadLocalFory()`.
For more details, please refer to the [Meta Sharing
specification](https://fory.apache.org/docs/specification/fory_java_serialization_spec#meta-share).
diff --git a/docs/guide/java/type-registration.md
b/docs/guide/java/type-registration.md
index fdfa61774c..d24cbda5bd 100644
--- a/docs/guide/java/type-registration.md
+++ b/docs/guide/java/type-registration.md
@@ -76,8 +76,8 @@ Fory provides a `org.apache.fory.resolver.AllowListChecker`
which is an allowed/
```java
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
-ThreadSafeFory fory = new ThreadLocalFory(classLoader -> {
- Fory f =
Fory.builder().requireClassRegistration(true).withClassLoader(classLoader).build();
+ThreadSafeFory fory = new ThreadLocalFory(builder -> {
+ Fory f = builder.requireClassRegistration(true).build();
f.getTypeResolver().setTypeChecker(checker);
checker.addListener((ClassResolver) f.getTypeResolver());
return f;
diff --git a/docs/guide/java/virtual-threads.md
b/docs/guide/java/virtual-threads.md
index b42b2a2f58..5db05a951e 100644
--- a/docs/guide/java/virtual-threads.md
+++ b/docs/guide/java/virtual-threads.md
@@ -19,69 +19,109 @@ license: |
limitations under the License.
---
-Apache Foryβ’ Java provides `buildVirtualThreadSafeFory(...)` for JDK 21+
virtual-thread workloads.
+Apache Fory Java uses `buildThreadSafeFory()` for virtual-thread workloads. It
builds a fixed-size
+shared `ThreadPoolFory` sized to `4 * availableProcessors()`. If you need a
different fixed pool
+size, use `buildThreadSafeForyPool(poolSize)`.
-## When `buildVirtualThreadSafeFory` Is Enough
+## Use Binary Input/Output APIs
-If your code uses the byte-array or `MemoryBuffer` APIs directly,
`buildVirtualThreadSafeFory(...)`
-is usually enough:
+When you use virtual threads, always use Fory's binary input/output APIs:
+
+- `serialize(Object)` or `serialize(MemoryBuffer, Object)`
+- `deserialize(byte[])` or `deserialize(MemoryBuffer)`
+
+Typical usage:
```java
ThreadSafeFory fory = Fory.builder()
.requireClassRegistration(false)
- .buildVirtualThreadSafeFory();
-```
-
-This is the preferred choice when serialization work is CPU-bound and each
virtual thread only
-needs a `Fory` instance while it is actively encoding or decoding data.
-
-Examples:
+ .buildThreadSafeFory();
-- `serialize(Object)`
-- `serialize(MemoryBuffer, Object)`
-- `deserialize(byte[])`
-- `deserialize(MemoryBuffer)`
-- `copy(Object)`
+byte[] bytes = fory.serialize(request);
+Object value = fory.deserialize(bytes);
+```
-## When Blocking I/O Changes The Tradeoff
+## Do Not Use Stream APIs For Large Virtual-Thread Counts
-If your code uses Java I/O or NIO based APIs such as:
+Do not use stream or channel based APIs for virtual-thread-heavy workloads:
- `serialize(OutputStream, Object)`
- `deserialize(ForyInputStream)`
- `deserialize(ForyReadableChannel)`
-then virtual threads can yield while blocked on I/O.
+Those APIs keep a pooled `Fory` instance occupied for the whole blocking call.
With many virtual
+threads, that means many `Fory` instances stay busy while waiting on I/O. Each
`Fory` instance
+typically uses around `30~50 KB` of memory, so holding many of them during
blocking I/O adds up
+quickly.
+
+Use stream APIs with virtual threads only when you have at most several
hundred virtual threads and
+the extra retained `Fory` memory is still acceptable.
+
+## Why Binary APIs Are The Right Fit
+
+Serialization and deserialization are CPU work. Fory is fast, so this CPU time
is usually short
+compared with network transfer time.
+
+In most cases, you do not need to overlap network transfer with Fory
deserialization. Fory
+deserialization is usually less than `1/10` of network transfer time, so
optimizing the transport
+path matters much more than trying to stream one object graph through Fory.
-That matters because the borrowed `Fory` instance stays occupied for the whole
operation, including
-the time spent waiting on the stream or channel. In that case, many virtual
threads can require
-many `Fory` instances at the same time.
+Most RPC systems also already work with framed byte messages instead of Java
object streams. For
+example, gRPC uses length-delimited frames, which matches Fory's binary APIs
naturally.
-Each `Fory` instance uses about `30+ KB` of memory. As a rough estimate, `100
MB` of memory can
-hold `3000+` `Fory` instances.
+A good virtual-thread pattern is:
-Before using `buildVirtualThreadSafeFory(...)` for stream or channel heavy
workloads, evaluate:
+1. Read one framed message into bytes.
+2. Call `fory.deserialize(bytes)`.
+3. Produce the response object.
+4. Call `fory.serialize(response)`.
+5. Write the response bytes as the next framed chunk.
-- whether you may have too many concurrent virtual threads blocked on I/O
-- whether you have enough memory for the needed number of `Fory` instances
-- whether a lower pool cap will cause extra waiting
+## Recommended Pattern
-## Pool Exhaustion Behavior
+```java
+byte[] requestBytes = readOneFrame(channel);
+Request request = (Request) fory.deserialize(requestBytes);
+
+Response response = handle(request);
+byte[] responseBytes = fory.serialize(response);
+writeOneFrame(channel, responseBytes);
+```
+
+This keeps Fory on the fast CPU-bound part and keeps blocking I/O outside the
serializer.
+
+## Huge Payloads: Chunked Length-Delimited Streaming
-`buildVirtualThreadSafeFory(int maxPoolSize)` limits how many idle `Fory`
instances are retained
-for reuse after operations complete.
+For most cases, the normal framed-byte pattern above is enough. Only consider
chunked streaming for
+very large payloads when you want to overlap transport with serialization and
deserialization.
-If stream or channel operations cause many virtual threads to block while
holding pooled `Fory`
-instances, and there are not enough available instances for the ready virtual
threads, those ready
-virtual threads may still need to yield again while waiting for another `Fory`
instance to become
-free.
+Even in that case, do not use Fory's stream APIs. Instead, split one large
payload into multiple
+sub object graphs, serialize each sub object graph to a `byte[]`, then write:
-In other words:
+1. frame length
+2. chunk bytes
-- blocked I/O can keep `Fory` instances occupied for longer
-- ready virtual threads may wait for a free `Fory` even after their stream
becomes ready
-- too small a retained pool can add extra scheduling delay under heavy
blocking I/O
+On deserialization in virtual threads:
+
+1. read the frame length
+2. read exactly that many bytes
+3. call `fory.deserialize(chunkBytes)`
+
+This lets the transport move data chunk by chunk while Fory still works on
complete binary frames.
+
+```java
+for (Object chunk : splitIntoSubGraphs(largePayload)) {
+ byte[] bytes = fory.serialize(chunk);
+ writeFrame(output, bytes);
+}
+
+while (hasMoreFrames(input)) {
+ int length = readLength(input);
+ byte[] bytes = readBytes(input, length);
+ Object chunk = fory.deserialize(bytes);
+ consumeChunk(chunk);
+}
+```
-If that is not acceptable, prefer the first approach: use
`buildVirtualThreadSafeFory(...)` only
-for non-stream, non-channel serialization paths, or provision enough memory
for the number of
-`Fory` instances your workload can keep active.
+Length-delimited framing is common, and gRPC also uses length-delimited frames
instead of Java
+object streams, so this pattern fits typical RPC and virtual-thread transports
well.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]