66import com .esotericsoftware .kryo .util .DefaultInstantiatorStrategy ;
77import org .objenesis .strategy .StdInstantiatorStrategy ;
88
9- import java .io .ByteArrayOutputStream ;
109import java .io .InputStream ;
1110import java .io .OutputStream ;
1211import java .lang .reflect .Field ;
@@ -36,7 +35,11 @@ public final class Serializer {
3635 private static final int MAX_COLLECTION_SIZE = 1000 ;
3736 private static final int BUFFER_SIZE = 4096 ;
3837
39- // Thread-local Kryo instances (Kryo is not thread-safe)
38+ // Thread-local Kryo, Output, and IdentityHashMap instances for reuse
39+ private static final ThreadLocal <Output > OUTPUT = ThreadLocal .withInitial (() -> new Output (BUFFER_SIZE , -1 ));
40+ private static final ThreadLocal <IdentityHashMap <Object , Object >> SEEN =
41+ ThreadLocal .withInitial (IdentityHashMap ::new );
42+
4043 private static final ThreadLocal <Kryo > KRYO = ThreadLocal .withInitial (() -> {
4144 Kryo kryo = new Kryo ();
4245 kryo .setRegistrationRequired (false );
@@ -89,10 +92,78 @@ private Serializer() {
8992 * @return Serialized bytes (may contain KryoPlaceholder for unserializable parts)
9093 */
9194 public static byte [] serialize (Object obj ) {
92- Object processed = recursiveProcess (obj , new IdentityHashMap <>(), 0 , "" );
95+ // Fast path: if args are all safe types, skip recursive processing entirely
96+ if (obj instanceof Object [] && isSafeArgs ((Object []) obj )) {
97+ return directSerialize (obj );
98+ }
99+
100+ IdentityHashMap <Object , Object > seen = SEEN .get ();
101+ seen .clear ();
102+ Object processed = recursiveProcess (obj , seen , 0 , "" );
93103 return directSerialize (processed );
94104 }
95105
106+ /**
107+ * Attempt fast-path serialization for args that are all known-safe types.
108+ * Returns serialized bytes if all args are safe, or null if the slow path is needed.
109+ * Callers can use this to avoid executor submission overhead for simple arguments.
110+ */
111+ public static byte [] serializeFast (Object obj ) {
112+ if (obj instanceof Object [] && isSafeArgs ((Object []) obj )) {
113+ return directSerialize (obj );
114+ }
115+ return null ;
116+ }
117+
118+ /**
119+ * Check if all elements of an args array can be serialized directly without recursive processing.
120+ */
121+ private static boolean isSafeArgs (Object [] args ) {
122+ for (Object arg : args ) {
123+ if (!isSafeForDirectSerialization (arg )) {
124+ return false ;
125+ }
126+ }
127+ return true ;
128+ }
129+
130+ /**
131+ * Check if an object is safe to serialize directly without recursive processing.
132+ * Covers: null, simple types, primitive arrays, and safe containers (up to 3 levels deep).
133+ */
134+ private static boolean isSafeForDirectSerialization (Object obj ) {
135+ return isSafeForDirectSerialization (obj , 3 );
136+ }
137+
138+ private static boolean isSafeForDirectSerialization (Object obj , int depthLeft ) {
139+ if (obj == null || isSimpleType (obj )) {
140+ return true ;
141+ }
142+ if (depthLeft <= 0 ) {
143+ return false ;
144+ }
145+ Class <?> clazz = obj .getClass ();
146+ if (clazz .isArray () && clazz .getComponentType ().isPrimitive ()) {
147+ return true ;
148+ }
149+ if (isSafeContainerType (clazz )) {
150+ if (obj instanceof Collection ) {
151+ for (Object item : (Collection <?>) obj ) {
152+ if (!isSafeForDirectSerialization (item , depthLeft - 1 )) return false ;
153+ }
154+ return true ;
155+ }
156+ if (obj instanceof Map ) {
157+ for (Map .Entry <?, ?> e : ((Map <?, ?>) obj ).entrySet ()) {
158+ if (!isSafeForDirectSerialization (e .getKey (), depthLeft - 1 ) ||
159+ !isSafeForDirectSerialization (e .getValue (), depthLeft - 1 )) return false ;
160+ }
161+ return true ;
162+ }
163+ }
164+ return false ;
165+ }
166+
96167 /**
97168 * Deserialize bytes back to an object.
98169 * The returned object may contain KryoPlaceholder instances for parts
@@ -141,14 +212,15 @@ public static byte[] serializeException(Throwable error) {
141212
142213 /**
143214 * Direct serialization without recursive processing.
215+ * Reuses a ThreadLocal Output buffer to avoid per-call allocation.
144216 */
145217 private static byte [] directSerialize (Object obj ) {
146218 Kryo kryo = KRYO .get ();
147- ByteArrayOutputStream baos = new ByteArrayOutputStream ( BUFFER_SIZE );
148- try ( Output output = new Output ( baos )) {
149- kryo .writeClassAndObject (output , obj );
150- }
151- return baos . toByteArray ();
219+ Output output = OUTPUT . get ( );
220+ output . reset ();
221+ kryo .writeClassAndObject (output , obj );
222+ output . flush ();
223+ return output . toBytes ();
152224 }
153225
154226 /**
@@ -201,37 +273,23 @@ private static Object recursiveProcess(Object obj, IdentityHashMap<Object, Objec
201273 // unserializable types, recursively process to catch and replace unserializable objects.
202274 if (obj instanceof Map ) {
203275 Map <?, ?> map = (Map <?, ?>) obj ;
204- if (containsOnlySimpleTypes (map )) {
205- // Simple map - try direct serialization to preserve full size
206- byte [] serialized = tryDirectSerialize (obj );
207- if (serialized != null ) {
208- try {
209- deserialize (serialized );
210- return obj ; // Success - return original
211- } catch (Exception e ) {
212- // Fall through to recursive handling
213- }
214- }
276+ if (isSafeContainerType (clazz ) && containsOnlySimpleTypes (map )) {
277+ return obj ;
215278 }
216279 return handleMap (map , seen , depth , path );
217280 }
218281 if (obj instanceof Collection ) {
219282 Collection <?> collection = (Collection <?>) obj ;
220- if (containsOnlySimpleTypes (collection )) {
221- // Simple collection - try direct serialization to preserve full size
222- byte [] serialized = tryDirectSerialize (obj );
223- if (serialized != null ) {
224- try {
225- deserialize (serialized );
226- return obj ; // Success - return original
227- } catch (Exception e ) {
228- // Fall through to recursive handling
229- }
230- }
283+ if (isSafeContainerType (clazz ) && containsOnlySimpleTypes (collection )) {
284+ return obj ;
231285 }
232286 return handleCollection (collection , seen , depth , path );
233287 }
234288 if (clazz .isArray ()) {
289+ // Primitive arrays (int[], double[], etc.) are directly serializable by Kryo
290+ if (clazz .getComponentType ().isPrimitive ()) {
291+ return obj ;
292+ }
235293 return handleArray (obj , seen , depth , path );
236294 }
237295
@@ -255,6 +313,19 @@ private static Object recursiveProcess(Object obj, IdentityHashMap<Object, Objec
255313 }
256314 }
257315
316+ /**
317+ * Check if a container type is known to round-trip safely through Kryo without verification.
318+ * Only includes types registered with Kryo that are known to serialize/deserialize correctly.
319+ */
320+ private static boolean isSafeContainerType (Class <?> clazz ) {
321+ return clazz == ArrayList .class ||
322+ clazz == LinkedList .class ||
323+ clazz == HashMap .class ||
324+ clazz == LinkedHashMap .class ||
325+ clazz == HashSet .class ||
326+ clazz == LinkedHashSet .class ;
327+ }
328+
258329 /**
259330 * Check if a class is known to be unserializable.
260331 */
0 commit comments