Kryo v6 Ideas - EsotericSoftware/kryo GitHub Wiki
Compatibility
Kryo 6 should be serialization compatible with Kryo 5.
Kryo 6 reads data written by Kryo 5 with the Kryo 5 format, kryo.setFormat(Format.KRYO5) before registering classes, which makes Kryo's serializers and reference resolvers write and read the Kryo 5 format where the default format changed (https://github.com/EsotericSoftware/kryo/pull/1477, https://github.com/EsotericSoftware/kryo/pull/1478; replaces Kryo5Compatibility). See MIGRATION.md.
- The Kryo 5 format is tested against the released Kryo 5.7.1, a test dependency, in both directions: Kryo 6 reads what Kryo 5 writes, Kryo 5 reads what Kryo 6 writes with the Kryo 5 format, and both write the same bytes (https://github.com/EsotericSoftware/kryo/pull/1479)
✅ JDK 17 Baseline
We should move to JDK17 to be able to integrate records into FieldSerializer.
See https://github.com/EsotericSoftware/kryo/commit/20cc80579062e3f6dfe1e9e16eb7851a325e50b5
Done on the kryo-6 branch, see https://github.com/EsotericSoftware/kryo/pull/1313, https://github.com/EsotericSoftware/kryo/pull/1314 and https://github.com/EsotericSoftware/kryo/pull/1315
✅ FieldSerializer for records
We should move record serialization from RecordSerializer into FieldSerializer.
See https://github.com/EsotericSoftware/kryo/pull/931
✅ Enable additional JDK serializers
We should enable all additional safe serializers.
See https://github.com/EsotericSoftware/kryo/pull/1318
Disable advanced generics optimization
We should switch to a less aggressive version of generics optimization by default.
Superseded by "Fix generics" below: the bugs that motivated a less aggressive generics strategy are caused by how type variables are resolved, not by the optimization itself. https://github.com/EsotericSoftware/kryo/pull/879 was closed.
✅ Fix generics
DefaultGenerics assigned the type arguments of the declared type (eg the type of a field) by position to the type parameters of the class of the value. This is only correct if both have the same type parameters in the same order, and otherwise fails with a ClassCastException. The guard in pushTypeVariables only hid the cases where the number of type arguments differed. Map the type arguments through the super classes and interfaces of the class instead, and remove the guard.
See https://github.com/EsotericSoftware/kryo/pull/1329
✅ Disable unsafe by default
Unsafe should be disabled by default and users should opt-in to use it.
Done: FieldSerializer uses Unsafe only where Java doesn't warn about it (before Java 24 or with --sun-misc-unsafe-memory-access=allow), reflection on Android, and VarHandles otherwise. FieldSerializerConfig.setFieldAccess or the system property kryo.fieldAccess selects it explicitly. See https://github.com/EsotericSoftware/kryo/pull/1339 and https://github.com/EsotericSoftware/kryo/pull/1397
VarHandle field access is safe and warning free, but 10% to 21% slower than Kryo 5 with Unsafe on an object graph of 78 classes (and 60% faster for the chunked encoding, which has a new format). Unsafe field access is 20% to 27% faster than Kryo 5, code generation 2.1 to 3.6 times. The hidden classes that made VarHandles constants (https://github.com/EsotericSoftware/kryo/pull/1393) were removed again in favor of code generation, which generates one class per serialized class instead of one per field (https://github.com/EsotericSoftware/kryo/pull/1444). Final fields are read with VarHandles and set with reflection (https://github.com/EsotericSoftware/kryo/pull/1447). Chart: https://github.com/EsotericSoftware/kryo/blob/kryo-6/benchmarks/charts/kryo5VsKryo6.svg
✅ Migrate away from terminally deprecated Unsafe methods
See https://github.com/EsotericSoftware/kryo/issues/1246
Done for the default configuration: no Unsafe warnings on any Java version. The serializers for unmodifiable and synchronized collections use method handles if java.util is open to Kryo (https://github.com/EsotericSoftware/kryo/pull/1341). Final fields are set with reflection, which Java 26+ warns about; Kryo explains how to allow it (https://github.com/EsotericSoftware/kryo/pull/1340). ClosureSerializer no longer reads JDK internals, so it needs no --add-opens (https://github.com/EsotericSoftware/kryo/pull/1422). Unsafe buffers and the UNSAFE field access remain as opt-ins.
✅ Support GraalVM native image
Kryo should work in GraalVM native images without metadata for Kryo itself. Users only register their own serialized classes, eg with the tracing agent.
- Don't use ReflectASM in native images (https://github.com/EsotericSoftware/kryo/pull/1337)
- Create the default serializers without reflection (https://github.com/EsotericSoftware/kryo/pull/1342)
- Remove the obsolete checks for classes that are always available on Java 17 (https://github.com/EsotericSoftware/kryo/pull/1342)
- Document native image usage in the README (https://github.com/EsotericSoftware/kryo/pull/1342)
- Build and run a native image reproducer in CI (https://github.com/EsotericSoftware/kryo/pull/1342)
See https://github.com/EsotericSoftware/kryo/issues/936
✅ Support Android
Kryo 6 supports Android from API level 26 (Android 8.0), see the README section "Android". APIs that are only available on newer Android versions are guarded (https://github.com/EsotericSoftware/kryo/pull/1338), and fields are accessed with reflection on Android (https://github.com/EsotericSoftware/kryo/pull/1339). Verified once on an Android 16 emulator, there is no Android CI yet.
- The API level 26 is checked by the build and the tests run on emulators with API levels 26 and 33 on CI; the immutable collections keep their registration IDs on Android; wrapped collections, charsets and time zones are serialized portably, verified by a compatibility test between the JVM and Android (https://github.com/EsotericSoftware/kryo/pull/1454, https://github.com/EsotericSoftware/kryo/pull/1455, https://github.com/EsotericSoftware/kryo/pull/1456, https://github.com/EsotericSoftware/kryo/pull/1462)
✅ Provide built-in serializers for unmodifiable and synchronized collections
See https://github.com/EsotericSoftware/kryo/pull/1154
✅ Chunked encoding that doesn't lose data in skipped fields
With chunked encoding, skipping a field lost the class names, field names and references first written in it, so the rest of the data could not be read. Fields are now written with their length, and the class names and field names first written in an object before it. The new format is also smaller and faster. The lengths are variable length longs, so a future version can write fields larger than 2 GiB, which Kryo 6 buffers in memory (https://github.com/EsotericSoftware/kryo/pull/1438, https://github.com/EsotericSoftware/kryo/pull/1445).
See https://github.com/EsotericSoftware/kryo/issues/1247 and https://github.com/EsotericSoftware/kryo/pull/1382
✅ Performance
- Strings are written without references by default (https://github.com/EsotericSoftware/kryo/pull/1387)
- CompatibleFieldSerializer writes its field names as cached bytes and compares them when reading (https://github.com/EsotericSoftware/kryo/pull/1384)
-
Hidden classes for VarHandle field access (https://github.com/EsotericSoftware/kryo/pull/1393)Removed again, code generation replaces them (https://github.com/EsotericSoftware/kryo/pull/1444) - All field access types write the same data for String fields (https://github.com/EsotericSoftware/kryo/pull/1396)
- MapSerializer writes keys and values without a null marker if the map contains no null key or value (https://github.com/EsotericSoftware/kryo/pull/1399)
-
ObjectGraphBenchmarkfor an object graph of many classes (https://github.com/EsotericSoftware/kryo/pull/1398). Preliminary results: https://github.com/EsotericSoftware/kryo/pull/1321#issuecomment-5997083547 - The
Fieldobjects and generic types of the fields of a class are shared by all Kryo instances, which takes 43% less memory per Kryo instance for an application with many classes and pooled instances (https://github.com/EsotericSoftware/kryo/pull/1402, https://github.com/EsotericSoftware/kryo/pull/1403) - Benchmark charts are generated by a Python script and live in the repository, all in the same units (https://github.com/EsotericSoftware/kryo/pull/1400, https://github.com/EsotericSoftware/kryo/pull/1401, https://github.com/EsotericSoftware/kryo/issues/708)
- Opt-in code generation: FieldSerializer and its subclasses generate a hidden class per serialized class that writes, reads and copies the fields with straight-line code, and constructs records, 1.8 to 2.8 times faster than the loop over the cached fields and the same bytes (https://github.com/EsotericSoftware/kryo/pull/1412). On Java 17 to 23 with ASM, an optional dependency (https://github.com/EsotericSoftware/kryo/pull/1443), generated when a serializer is first used (https://github.com/EsotericSoftware/kryo/pull/1446), also for copying (https://github.com/EsotericSoftware/kryo/pull/1448) and for records (https://github.com/EsotericSoftware/kryo/pull/1449). Kryo 5 vs 6: https://github.com/EsotericSoftware/kryo/blob/kryo-6/benchmarks/charts/kryo5VsKryo6.svg, records: https://github.com/EsotericSoftware/kryo/blob/kryo-6/benchmarks/charts/records.svg
- All field access types write the same bytes, verified by a test (https://github.com/EsotericSoftware/kryo/pull/1440)
- The final field setter is resolved when a field is first set, so the warning of Java 26+ is not shown for writing (https://github.com/EsotericSoftware/kryo/pull/1441)
- MapSerializer scans the entries once instead of up to three times;
Map.ofandSet.ofare read into sized maps and sets;ByteBufferSerializerwrites and reads heap buffers without a temporary array; CompatibleFieldSerializer and TaggedFieldSerializer write the class of a field value without boxing or reflection (https://github.com/EsotericSoftware/kryo/pull/1469, https://github.com/EsotericSoftware/kryo/pull/1470, https://github.com/EsotericSoftware/kryo/pull/1471, https://github.com/EsotericSoftware/kryo/pull/1472) - The method handles of VarHandle fields are shared by all serializers and Kryo instances: an additional Kryo instance takes 78% less memory and half the time to set up for
ObjectGraphBenchmark, 81% less memory and 5x less time for 1,000 classes (https://github.com/EsotericSoftware/kryo/pull/1513)
✅ No required dependencies
- ReflectASM removed, field access uses Unsafe, VarHandles or reflection (https://github.com/EsotericSoftware/kryo/pull/1418)
- MinLog replaced by Kryo's own
Logclass with the same API (https://github.com/EsotericSoftware/kryo/pull/1419) - Objenesis is optional: objects are created with the JDK's serialization constructors or Unsafe, Objenesis is only needed on Android and other JVMs without them (https://github.com/EsotericSoftware/kryo/pull/1421)
- ASM is an optional dependency for code generation on Java 17 to 23, the versioned
kryo6jar includes it (https://github.com/EsotericSoftware/kryo/pull/1443)
✅ Final fields if Java denies setting them with reflection
Java will deny setting final fields with reflection by default (JEP 500). Set the final fields of serializable classes with the method handles that Java provides for deserialization.
Done: if setting final fields with reflection is denied, FieldSerializer sets the final fields of serializable classes with these method handles on Java 24+ (https://github.com/EsotericSoftware/kryo/pull/1383). The setter is resolved when a final field is first set, so a process that only writes sees no warning (https://github.com/EsotericSoftware/kryo/pull/1441), and final fields are read with VarHandles (https://github.com/EsotericSoftware/kryo/pull/1447).
Misc
- Delete
CuckooObjectMap(https://github.com/EsotericSoftware/kryo/pull/1319) -
setFieldsAsAccessible(false)serializes no fields, becauseCachedFieldschecksField.isAccessible(). It should serialize public fields of public classes. Changes the serialized format for users of this setting. (https://github.com/EsotericSoftware/kryo/pull/1330) -
@Bindalways setscanBeNull, so it overrides@NotNullon the same field. Changes the serialized format of fields with both annotations. (https://github.com/EsotericSoftware/kryo/pull/1331) - Allow
nullinJdkImmutableListSerializer(https://github.com/EsotericSoftware/kryo/issues/1239). Opt-in in Kryo 5.7.1 (https://github.com/EsotericSoftware/kryo/pull/1332), default in Kryo 6 (https://github.com/EsotericSoftware/kryo/pull/1333) - Fields can be removed in
FieldSerializer.initializeCachedFields, also with CompatibleFieldSerializer, TaggedFieldSerializer and VersionFieldSerializer (https://github.com/EsotericSoftware/kryo/pull/1395) - Test reflection and Unsafe field access and forced code generation in CI, on Java 17 and 21 with ASM (https://github.com/EsotericSoftware/kryo/pull/1397, https://github.com/EsotericSoftware/kryo/pull/1443)
-
The synthetic fields of anonymous, local and non-static member classes are serialized by default (https://github.com/EsotericSoftware/kryo/pull/1407)Reverted: synthetic fields are ignored by default like in Kryo 5, because they can pull large or sensitive object graphs into the data;setIgnoreSyntheticFields(false)serializes them (https://github.com/EsotericSoftware/kryo/pull/1433) - Subclasses of the atomic types with fields use the default serializer (https://github.com/EsotericSoftware/kryo/pull/1436)
- All files have LF line endings, enforced by
.gitattributesand the formatter check -
setOptimizeGenericsof CompatibleFieldSerializerConfig and TaggedFieldSerializerConfig is deprecated, it saves almost nothing and is only needed to read Kryo 5 data (https://github.com/EsotericSoftware/kryo/pull/1406) - References resolve to the object a serializer returns, eg an immutable collection (https://github.com/EsotericSoftware/kryo/pull/1475)
- Generics: an inner class of a generic class as a type argument, and nested objects of the same generic class no longer take the type variables of the enclosing object (https://github.com/EsotericSoftware/kryo/pull/1457, https://github.com/EsotericSoftware/kryo/pull/1473)
-
Classvalues of enum constants with bodies, enum sets, charsets and time zones are read as the base class; default serializer for the map entries ofMap.entry,AbstractMap.SimpleEntryandSimpleImmutableEntry(https://github.com/EsotericSoftware/kryo/pull/1464, https://github.com/EsotericSoftware/kryo/pull/1463) - Review of all changes of kryo-6 against master: 12 fix PRs, cleanup and documentation fixes (https://github.com/EsotericSoftware/kryo/pull/1457 to https://github.com/EsotericSoftware/kryo/pull/1468, https://github.com/EsotericSoftware/kryo/pull/1476)
- A corrupt negative collection or map size throws a
KryoExceptioninstead of anIllegalArgumentExceptionfrom the constructor or an empty collection (https://github.com/EsotericSoftware/kryo/pull/1504) -
ByteBufferOutput.setBuffer(byte[], int, int)allocatescountbytes instead of the whole array (https://github.com/EsotericSoftware/kryo/pull/1505) -
Util.isAssignableTo(void.class, X)returns false instead of throwing aNullPointerException, so corrupt data with a registeredvoid.classgets "Read type is incompatible" (https://github.com/EsotericSoftware/kryo/pull/1506) - Redundant
popGenericTypeinCollectionSerializer.readremoved (https://github.com/EsotericSoftware/kryo/pull/1507) - CI also runs the tests without the optional dependencies Objenesis and ASM, on JDK 17 (https://github.com/EsotericSoftware/kryo/pull/1512)
Open items found in the reviews of kryo-6 are in Kryo v6 Follow-ups.