高级特性
本页介绍高级特性,包括零拷贝序列化、深拷贝、内存管理和日志记录。
零拷贝序列化
Fory 支持零拷贝序列化,以高效处理大型二进制数据:
import org.apache.fory.*;
import org.apache.fory.config.*;
import org.apache.fory.serializer.BufferObject;
import org.apache.fory.memory.MemoryBuffer;
import java.util.*;
import java.util.stream.Collectors;
public class ZeroCopyExample {
// 注意 fory 实例应该复用,而不是每次都创建。
static Fory fory = Fory.builder()
.withLanguage(Language.JAVA)
.build();
public static void main(String[] args) {
List<Object> list = Arrays.asList("str", new byte[1000], new int[100], new double[100]);
Collection<BufferObject> bufferObjects = new ArrayList<>();
byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e));
List<MemoryBuffer> buffers = bufferObjects.stream()
.map(BufferObject::toBuffer).collect(Collectors.toList());
System.out.println(fory.deserialize(bytes, buffers));
}
}
对象深拷贝
Fory 提供高效的深拷贝功能:
启用引用跟踪
Fory fory = Fory.builder().withRefCopy(true).build();
SomeClass a = xxx;
SomeClass copied = fory.copy(a);
禁用引用跟踪(更好的性能)
禁用时,深拷贝将忽略循环引用和共享引用。对象图的相同引用将在一次 Fory#copy 中被复制为不同的对象:
Fory fory = Fory.builder().withRefCopy(false).build();
SomeClass a = xxx;
SomeClass copied = fory.copy(a);
内存分配自定义
Fory 提供了 MemoryAllocator 接口,允许你自定义在序列化操作期间如何分配和增长内存缓冲区。这对于性能优化、内存池化或调试内存使用很有用。
MemoryAllocator 接口
MemoryAllocator 接口定义了两个关键方法:
public interface MemoryAllocator {
/**
* 分配具有指定初始容量的新 MemoryBuffer。
*/
MemoryBuffer allocate(int initialCapacity);
/**
* 增长现有缓冲区以容纳新容量。
* 实现必须通过修改现有缓冲区实例来原地增长缓冲区。
*/
MemoryBuffer grow(MemoryBuffer buffer, int newCapacity);
}
使用自定义内存分配器
你可以设置一个全局内存分配器,所有 MemoryBuffer 实例都将使用它:
// 创建自定义分配器
MemoryAllocator customAllocator = new MemoryAllocator() {
@Override
public MemoryBuffer allocate(int initialCapacity) {
// 为调试或池化添加额外容量
return MemoryBuffer.fromByteArray(new byte[initialCapacity + 100]);
}
@Override
public MemoryBuffer grow(MemoryBuffer buffer, int newCapacity) {
if (newCapacity <= buffer.size()) {
return buffer;
}
// 自定义增长策略 - 添加 100% 额外容量
int newSize = (int) (newCapacity * 2);
byte[] data = new byte[newSize];
buffer.copyToUnsafe(0, data, Platform.BYTE_ARRAY_OFFSET, buffer.size());
buffer.initHeapBuffer(data, 0, data.length);
return buffer;
}
};
// 全局设置自定义分配器
MemoryBuffer.setGlobalAllocator(customAllocator);
// 所有后续的 MemoryBuffer 分配都将使用你的自定义分配器
Fory fory = Fory.builder().withLanguage(Language.JAVA).build();
byte[] bytes = fory.serialize(someObject); // 使用自定义分配器
默认内存分配器行为
默认分配器使用以下增长策略:
- 对于小于
BUFFER_GROW_STEP_THRESHOLD(100MB) 的缓冲区:将容量乘以 2 - 对于较大的缓冲区:将容量乘以 1.5(上限为
Integer.MAX_VALUE - 8)
这在避免频繁重新分配和防止过度内存使用之间提供了平衡。
使用场景
自定义内存分配器适用于:
- 内存池化:重用分配的缓冲区以减少 GC 压力
- 性能调优:根据你的工作负载使用不同的增长策略
- 调试:添加日志记录或跟踪以监控内存使用
- 堆外内存:与堆外内存管理系统集成
日志记录
ForyLogger
默认情况下,Fory 使用自定义日志记录器 ForyLogger 来满足内部需求。它将生成的日志数据构建为单个字符串,并直接发送到 System.out。结果行布局类似于(Log4j 表示法):
%d{yyyy-MM-dd hh:mm:ss} %p %C:%L [%t] - %m%n
布局无法更改。
示例输出:
2025-11-07 08:49:59 INFO CompileUnit:55 [main] - Generate code for org.apache.fory.builder.SerializedLambdaForyCodec_0 took 35 ms.
2025-11-07 08:50:00 INFO JaninoUtils:121 [main] - Compile [SerializedLambdaForyCodec_0] take 144 ms
Slf4jLogger
如果需要更复杂的日志记录器,可以通过 LoggerFactory.useSlf4jLogging() 配置 Fory 使用 Slf4j。例如,在创建 Fory 之前启用 Slf4j:
public static final ThreadSafeFory FORY;
static {
LoggerFactory.useSlf4jLogging(true);
FORY = Fory.builder()
.buildThreadSafeFory();
}
注意:当应用程序在 GraalVM native image 中运行时,通过 useSlf4jLogging 启用 Slf4j 将被忽略。
抑制 Fory 日志
ForyLogger 和 Slf4jLogger 都允许控制日志输出级别或完全抑制日志。通过 LoggerFactory.setLogLevel() 配置 日志级别:
static {
// 只记录 WARN 及更高级别
LoggerFactory.setLogLevel(LogLevel.WARN_LEVEL);
// 完全禁用日志记录
LoggerFactory.disableLogging();
}
注意:所选的日志级别在 Slf4j 实现的日志级别之前应用。因此,如果你设置 WARN_LEVEL(如上例),即使在 Logback 中启用了 INFO,你也不会看到来自 Fory 的 INFO 消息。