How Fury Achieves 20‑200× Faster Java Serialization Than JDK, Hessian, and Kryo
Fury is a JIT‑compiled, multi‑language native serialization framework that fully implements JDK custom serialization, delivering 20‑200× speed improvements over JDK, Hessian, and Kryo, while preserving compatibility across Java, Python, Go, C++, and JavaScript, as demonstrated by detailed protocol analysis and performance benchmarks.
Preface
Fury is a high‑performance, JIT‑based native serialization framework that supports Java, Python, Go, C++, JavaScript and provides fully automatic cross‑language object serialization with performance up to 20‑200× higher than other frameworks.
JDK Serialization Principle Analysis
The JDK uses ObjectOutputStream and ObjectInputStream. Users can customize serialization via methods such as writeObject, readObject, writeReplace, readResolve, etc. When these methods are present, the default JDK serializer cannot be bypassed, forcing developers to use the slow built‑in JDK serialization.
Typical JDK serialization flow:
Custom Serialization Methods
If an object defines writeReplace, the method is invoked first; the returned object may replace the original in the reference table. If the returned type still defines writeReplace, the process repeats. When writeReplace is absent, the serializer proceeds to field serialization, using either Externalizable or the default field traversal.
When writeObject is defined, it may call defaultWriteObject or implement custom logic. Compatibility across JDK versions is handled via putFields / writeFields. Example from ThreadLocalRandom:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("rnd", U.getLong(Thread.currentThread(), SEED));
fields.put("initialized", true);
s.writeFields();
}Problems with Existing Frameworks
Hessian Issues
Hessian only supports writeReplace / readResolve. When writeReplace returns an object of the same type, a stack overflow occurs:
public static class CustomReplaceClass implements Serializable {
Object writeReplace() { return new CustomReplaceClass(); }
Object readResolve() { return new CustomReplaceClass(); }
}
Exception in thread "main" java.lang.StackOverflowError
at ... WriteReplaceSerializer.writeReplace(...)
...Hessian also ignores writeObject / readObject, causing data loss for many JDK classes.
Kryo Issues
Relies on JDK serialization for objects with custom methods, leading to severe performance degradation.
Produces large serialized payloads.
Fails to share reference tables for sub‑graphs, causing duplicate serialization or stack overflows.
Does not support parent‑child fields with the same name.
Other Framework Limitations
Jsonb does not support any JDK custom serialization methods.
Fst lacks forward/backward compatibility.
Fury Compatibility Implementation
Early Fury versions delegated to JDK serialization for custom methods, but since version 0.9.2 Fury provides a complete, JIT‑accelerated implementation of the entire JDK serialization protocol, achieving orders‑of‑magnitude speed gains.
ReplaceResolveSerializer
Implements the exact JDK replace/resolve behavior, handling cases where writeReplace returns a different type without causing stack overflows, and reduces serialized size by omitting redundant class metadata.
ObjectStreamSerializer
Fully supports JDK methods writeObject, readObject, writeReplace, readResolve, readObjectNoData, and registerValidation, ensuring 100% compatibility.
Serializer Initialization
Fury obtains a no‑arg constructor (or the first non‑serializable superclass constructor) and, for JDK 17+, may use Unsafe to bypass access restrictions.
Constructor constructor;
try {
constructor = type.getConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
} catch (Exception e) {
constructor = (Constructor) ReflectionUtils.getObjectFieldValue(
ObjectStreamClass.lookup(type), "cons");
}It then builds a list of SlotsInfo objects representing each serializable class in the hierarchy, creating JIT‑compatible serializers for default field handling and custom method handling.
List<SlotsInfo> slotsInfoList = new ArrayList<>();
Class<?> end = type;
while (end != null && Serializable.class.isAssignableFrom(end)) {
end = end.getSuperclass();
}
while (type != end) {
slotsInfoList.add(new SlotsInfo(fury, type));
type = type.getSuperclass();
}
Collections.reverse(slotsInfoList);
slotsInfos = slotsInfoList.toArray(new SlotsInfo[0]);Serialization Execution
For each SlotsInfo, Fury either invokes the JIT serializer for default fields or calls the user‑defined writeObject method via a temporary FuryObjectOutputStream:
for (SlotsInfo slotsInfo : slotsInfos) {
buffer.writeShort((short) slotsInfos.length);
classResolver.writeClassInternal(buffer, slotsInfo.cls);
Method writeObjectMethod = slotsInfo.writeObjectMethod;
if (writeObjectMethod == null) {
slotsInfo.slotsSerializer.write(buffer, value);
} else {
FuryObjectOutputStream objectOutputStream = slotsInfo.objectOutputStream;
Object oldObject = objectOutputStream.targetObject;
MemoryBuffer oldBuffer = objectOutputStream.buffer;
try {
objectOutputStream.targetObject = value;
objectOutputStream.buffer = buffer;
writeObjectMethod.invoke(value, objectOutputStream);
} finally {
objectOutputStream.targetObject = oldObject;
objectOutputStream.buffer = oldBuffer;
}
}
}Deserialization Execution
Fury creates the object using the appropriate constructor, reads the class hierarchy, and either invokes the JIT deserializer or the user‑defined readObject method. It also respects readObjectNoData, registerValidation, and readResolve callbacks.
Object obj = null;
if (constructor != null) {
obj = constructor.newInstance();
} else {
obj = Platform.newInstance(type);
}
int numClasses = buffer.readShort();
for (int i = 0; i < numClasses; i++) {
Class<?> currentClass = classResolver.readClassInternal(buffer);
SlotsInfo slotsInfo = slotsInfos[slotIndex++];
while (currentClass != slotsInfo.cls) {
Method readObjectNoData = slotsInfo.readObjectNoData;
if (readObjectNoData != null) {
readObjectNoData.invoke(obj);
}
slotsInfo = slotsInfos[slotIndex++];
}
Method readObjectMethod = slotsInfo.readObjectMethod;
if (readObjectMethod == null) {
slotsInfo.slotsSerializer.readAndSetFields(buffer, obj);
} else {
FuryObjectInputStream objectInputStream = slotsInfo.objectInputStream;
// invoke readObject with FuryObjectInputStream
readObjectMethod.invoke(obj, objectInputStream);
}
}
for (ObjectInputValidation validation : callbacks.values()) {
validation.validateObject();
}Performance Comparison
After implementing full JDK compatibility, Fury never falls back to JDK serialization. Benchmarks show a 10× speed advantage over Kryo and a 3× advantage over Hessian for typical string‑heavy workloads.
Flame graphs confirm that JDK serialization stacks are completely eliminated:
Conclusion
Since version 0.9.2, Fury provides a uniquely 100% compatible and high‑performance alternative to JDK, Kryo, and Hessian serialization, enabling developers to abandon slow JDK serialization without sacrificing correctness.
Fury will be open‑sourced soon; interested parties can contact the author via email.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
