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]

Reply via email to