From 2286646981811c818e7da61a806426d38481218b Mon Sep 17 00:00:00 2001 Date: Wed, 4 Jan 2023 20:45:23 +0800 Subject: Fast Serializer --- src/hotspot/share/prims/unsafe.cpp | 5 + src/hotspot/share/runtime/globals.hpp | 4 + .../classes/java/io/ObjectInputStream.java | 222 +++++++++++++++--- .../classes/java/io/ObjectOutputStream.java | 83 ++++++- .../classes/java/io/ObjectStreamClass.java | 81 +++++++ .../classes/jdk/internal/misc/Unsafe.java | 2 +- 6 files changed, 355 insertions(+), 42 deletions(-) diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index ee6c218f5..97f36a2db 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -870,6 +870,10 @@ UNSAFE_ENTRY(jint, Unsafe_GetLoadAverage0(JNIEnv *env, jobject unsafe, jdoubleAr return ret; } UNSAFE_END +UNSAFE_ENTRY(jboolean, Unsafe_GetUseFastSerializer(JNIEnv *env, jobject unsafe)) { + return UseFastSerializer; +} +UNSAFE_END /// JVM_RegisterUnsafeMethods @@ -951,6 +955,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "loadFence", CC "()V", FN_PTR(Unsafe_LoadFence)}, {CC "storeFence", CC "()V", FN_PTR(Unsafe_StoreFence)}, {CC "fullFence", CC "()V", FN_PTR(Unsafe_FullFence)}, + {CC "getUseFastSerializer", CC "()Z", FN_PTR(Unsafe_GetUseFastSerializer)}, }; #undef CC diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 8c61e6a05..6852f1ac3 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2071,6 +2071,10 @@ const intx ObjectAlignmentInBytes = 8; JFR_ONLY(product(ccstr, StartFlightRecording, NULL, \ "Start flight recording with options")) \ \ + product(bool, UseFastSerializer, false, EXPERIMENTAL, \ + "Cache-based serialization.It is extremely fast, but it" \ + "can only be effective in certain scenarios.") \ + \ product(bool, UseFastUnorderedTimeStamps, false, EXPERIMENTAL, \ "Use platform unstable time where supported for timestamps only") \ \ diff --git a/src/java.base/share/classes/java/io/ObjectInputStream.java b/src/java.base/share/classes/java/io/ObjectInputStream.java index b3e063b3d..1cf3a38c8 100644 --- a/src/java.base/share/classes/java/io/ObjectInputStream.java +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java @@ -41,6 +41,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import jdk.internal.access.SharedSecrets; import jdk.internal.event.DeserializationEvent; @@ -312,6 +313,23 @@ public class ObjectInputStream filterLogger = (filterLog.isLoggable(Logger.Level.DEBUG) || filterLog.isLoggable(Logger.Level.TRACE)) ? filterLog : null; } + + /* + * Logger for FastSerializer. + * Setup the FastSerializer logger if it is set to DEBUG. + * (Assuming it will not change). + */ + static final System.Logger fastSerLogger; + + static { + if (printFastSerializer) { + Logger fastSerLog = System.getLogger("fastSerializer"); + fastSerLogger = (fastSerLog.isLoggable(Logger.Level.DEBUG)) + ? fastSerLog : null; + } else { + fastSerLogger = null; + } + } } /** filter stream for handling block data conversion */ @@ -337,6 +355,9 @@ public class ObjectInputStream /** if true, invoke resolveObject() */ private boolean enableResolve; + /** Used to get the commandline option: useFastSerializer */ + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + /** * Context during upcalls to class-defined readObject methods; holds * object currently being deserialized and descriptor for current class. @@ -350,6 +371,40 @@ public class ObjectInputStream */ private ObjectInputFilter serialFilter; + /** + * value of "useFastSerializer" property + */ + private static final boolean defaultFastSerializer = UNSAFE.getUseFastSerializer(); + + /** + * true or false for open FastSerilizer + * May be changed in readStreamHeader + */ + private boolean useFastSerializer = defaultFastSerializer; + + /** + * Value of "fastSerializerEscapeMode" property. It can be turned on + * when useFastSerializer is true. + */ + @SuppressWarnings("removal") + private static final boolean fastSerializerEscapeMode = java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "fastSerializerEscapeMode")).booleanValue(); + + /** + * Magic number that is written to the stream header when using fastserilizer. + */ + private static final short STREAM_MAGIC_FAST = (short)0xdeca; + + /** + * value of "printFastSerializer" property, + * as true or false for printing FastSerializer logs. + */ + @SuppressWarnings("removal") + private static final boolean printFastSerializer = java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "printFastSerializer")).booleanValue(); + /** * True if the stream-specific filter has been set; initially false. */ @@ -437,6 +492,9 @@ public class ObjectInputStream * transitively so that a complete equivalent graph of objects is * reconstructed by readObject. * + * The difference between fastSerialzation and default serialization is the + * descriptor serialization. The data serialization is same with each other. + * *

The root object is completely restored when all of its fields and the * objects it references are completely restored. At this point the object * validation callbacks are executed in order based on their registered @@ -726,11 +784,20 @@ public class ObjectInputStream vlist.register(obj, prio); } + /** + * Cache the class meta during serialization. + * Only used in FastSerilizer. + */ + private static ConcurrentHashMap> nameToClass = new ConcurrentHashMap<>(); + /** * Load the local class equivalent of the specified stream class * description. Subclasses may implement this method to allow classes to * be fetched from an alternate source. * + * When fastSerializer is turned on, fields of desc will be null except + * name. When resolveClass is override, this may cause null pointer exception. + * *

The corresponding method in {@code ObjectOutputStream} is * {@code annotateClass}. This method will be invoked only once for * each unique class in the stream. This method can be implemented by @@ -769,16 +836,29 @@ public class ObjectInputStream throws IOException, ClassNotFoundException { String name = desc.getName(); - try { - return Class.forName(name, false, latestUserDefinedLoader()); - } catch (ClassNotFoundException ex) { - Class cl = primClasses.get(name); + Class cl = null; + + if (useFastSerializer) { + cl = nameToClass.get(name); if (cl != null) { return cl; - } else { + } + } + + try { + cl = Class.forName(name, false, latestUserDefinedLoader()); + } catch (ClassNotFoundException ex) { + cl = primClasses.get(name); + if (cl == null) { throw ex; } } + + if (useFastSerializer) { + nameToClass.put(name, cl); + } + + return cl; } /** @@ -953,9 +1033,33 @@ public class ObjectInputStream { short s0 = bin.readShort(); short s1 = bin.readShort(); - if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) { - throw new StreamCorruptedException( - String.format("invalid stream header: %04X%04X", s0, s1)); + if (useFastSerializer) { + if (s0 != STREAM_MAGIC_FAST || s1 != STREAM_VERSION) { + if (s0 != STREAM_MAGIC) { + throw new StreamCorruptedException( + String.format("invalid stream header: %04X%04X, and FastSerializer is activated", s0, s1)); + } + + if (!fastSerializerEscapeMode) { + throw new StreamCorruptedException( + String.format("invalid stream header: %04X%04X.Fast serialization does not support " + + "original serialized files", s0, s1)); + } + + // Escape to default serialization + useFastSerializer = false; + if (Logging.fastSerLogger != null) { + Logging.fastSerLogger.log(Logger.Level.DEBUG, "[Deserialize]: Escape and disable FastSerializer"); + } + } + } else if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) { + if (s0 == STREAM_MAGIC_FAST && s1 == STREAM_VERSION) { + throw new StreamCorruptedException( + String.format("invalid stream header: %04X%04X, and it is a FastSerializer stream", s0, s1)); + } else { + throw new StreamCorruptedException( + String.format("invalid stream header: %04X%04X", s0, s1)); + } } } @@ -969,6 +1073,11 @@ public class ObjectInputStream * this method reads class descriptors according to the format defined in * the Object Serialization specification. * + * In fastSerialize mode, the descriptor is obtained by lookup method. And + * the resolveClass method is called here to get the classmeta. Since the + * descriptor is obtained by lookup, the descriptor is same as localdesc. + * So we cann't distinguish the receiver desc and local desc. + * * @return the class descriptor read * @throws IOException If an I/O error has occurred. * @throws ClassNotFoundException If the Class of a serialized object used @@ -979,6 +1088,29 @@ public class ObjectInputStream protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + // fastSerializer + if (useFastSerializer) { + String name = readUTF(); + Class cl = null; + ObjectStreamClass desc = new ObjectStreamClass(name); + try { + // In order to match this method, we add an annotateClass method in + // writeClassDescriptor. + cl = resolveClass(desc); + } catch (ClassNotFoundException ex) { + // resolveClass is just used to obtain Class which required by lookup method + // and it will be called again later, so we don't throw ClassNotFoundException here. + return desc; + } + if (cl != null) { + // This desc is localDesc. It may be different from the descriptor + // obtained from the stream. + desc = ObjectStreamClass.lookup(cl, true); + } + return desc; + } + + // Default deserialization. If the Class cannot be found, throw ClassNotFoundException. ObjectStreamClass desc = new ObjectStreamClass(); desc.readNonProxy(this); return desc; @@ -2045,41 +2177,63 @@ public class ObjectInputStream skipCustomData(); - try { - totalObjectRefs++; - depth++; - desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false)); + totalObjectRefs++; + depth++; - if (cl != null) { - // Check that serial filtering has been done on the local class descriptor's superclass, - // in case it does not appear in the stream. - - // Find the next super descriptor that has a local class descriptor. - // Descriptors for which there is no local class are ignored. - ObjectStreamClass superLocal = null; - for (ObjectStreamClass sDesc = desc.getSuperDesc(); sDesc != null; sDesc = sDesc.getSuperDesc()) { - if ((superLocal = sDesc.getLocalDesc()) != null) { - break; + if (useFastSerializer) { + desc.initNonProxyFast(readDesc, resolveEx); + ObjectStreamClass superDesc = desc.getSuperDesc(); + long originDepth = depth - 1; + // Since desc is obtained from the lookup method, we will lose the depth and + // totalObjectRefs of superDesc. So we add a loop here to compute the depth + // and objectRef of superDesc. + while (superDesc != null && superDesc.forClass() != null) { + filterCheck(superDesc.forClass(), -1); + superDesc = superDesc.getSuperDesc(); + totalObjectRefs++; + depth++; + } + depth = originDepth; + } else { + try { + desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false)); + + if (cl != null) { + // Check that serial filtering has been done on the local class descriptor's superclass, + // in case it does not appear in the stream. + // Find the next super descriptor that has a local class descriptor. + // Descriptors for which there is no local class are ignored. + ObjectStreamClass superLocal = null; + for (ObjectStreamClass sDesc = desc.getSuperDesc(); sDesc != null; sDesc = sDesc.getSuperDesc()) { + if ((superLocal = sDesc.getLocalDesc()) != null) { + break; + } } - } - // Scan local descriptor superclasses for a match with the local descriptor of the super found above. - // For each super descriptor before the match, invoke the serial filter on the class. - // The filter is invoked for each class that has not already been filtered - // but would be filtered if the instance had been serialized by this Java runtime. - for (ObjectStreamClass lDesc = desc.getLocalDesc().getSuperDesc(); - lDesc != null && lDesc != superLocal; - lDesc = lDesc.getSuperDesc()) { - filterCheck(lDesc.forClass(), -1); + // Scan local descriptor superclasses for a match with the local descriptor of the super found above. + // For each super descriptor before the match, invoke the serial filter on the class. + // The filter is invoked for each class that has not already been filtered + // but would be filtered if the instance had been serialized by this Java runtime. + for (ObjectStreamClass lDesc = desc.getLocalDesc().getSuperDesc(); + lDesc != null && lDesc != superLocal; + lDesc = lDesc.getSuperDesc()) { + filterCheck(lDesc.forClass(), -1); + } } + } finally { + depth--; } - } finally { - depth--; } handles.finish(descHandle); passHandle = descHandle; + if (Logging.fastSerLogger != null) { + Logging.fastSerLogger.log(Logger.Level.DEBUG, + "[Deserialize] useFastSerializer:{0}, Class name:{1}, SerialVersionUID:{2}, flags:{3}", + useFastSerializer, desc.getName(), desc.getSerialVersionUID(), desc.getFlags(this)); + } + return desc; } @@ -2946,8 +3100,6 @@ public class ObjectInputStream } } - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - /** * Performs a "freeze" action, required to adhere to final field semantics. * diff --git a/src/java.base/share/classes/java/io/ObjectOutputStream.java b/src/java.base/share/classes/java/io/ObjectOutputStream.java index be52df020..f855d6ee4 100644 --- a/src/java.base/share/classes/java/io/ObjectOutputStream.java +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringJoiner; +import jdk.internal.misc.Unsafe; import sun.reflect.misc.ReflectUtil; /** @@ -173,6 +174,25 @@ public class ObjectOutputStream }; } + private static class Logging { + /* + * Logger for FastSerializer. + * Setup the FastSerializer logger if it is set to DEBUG. + * (Assuming it will not change). + */ + static final System.Logger fastSerLogger; + + static { + if (printFastSerializer) { + System.Logger fastSerLog = System.getLogger("fastSerializer"); + fastSerLogger = (fastSerLog.isLoggable(System.Logger.Level.DEBUG)) + ? fastSerLog : null; + } else { + fastSerLogger = null; + } + } + } + /** filter stream for handling block data conversion */ private final BlockDataOutputStream bout; /** obj -> wire handle map */ @@ -191,7 +211,6 @@ public class ObjectOutputStream private final boolean enableOverride; /** if true, invoke replaceObject() */ private boolean enableReplace; - // values below valid only during upcalls to writeObject()/writeExternal() /** * Context during upcalls to class-defined writeObject methods; holds @@ -215,6 +234,28 @@ public class ObjectOutputStream new sun.security.action.GetBooleanAction( "sun.io.serialization.extendedDebugInfo")).booleanValue(); + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + /** + * Value of "UseFastSerializer" property, The fastSerializer is turned + * on when it is true. + */ + private static final boolean useFastSerializer = UNSAFE.getUseFastSerializer(); + + /** + * value of "printFastSerializer" property, + * as true or false for printing FastSerializer logs. + */ + @SuppressWarnings("removal") + private static final boolean printFastSerializer = java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "printFastSerializer")).booleanValue(); + + /** + * Magic number that is written to the stream header when using fastserilizer. + */ + private static final short STREAM_MAGIC_FAST = (short)0xdeca; + /** * Creates an ObjectOutputStream that writes to the specified OutputStream. * This constructor writes the serialization stream header to the @@ -329,6 +370,9 @@ public class ObjectOutputStream * object are written transitively so that a complete equivalent graph of * objects can be reconstructed by an ObjectInputStream. * + * The difference between fastSerialzation and default serialization is the + * descriptor serialization. The data serialization is same with each other. + * *

Exceptions are thrown for problems with the OutputStream and for * classes that should not be serialized. All exceptions are fatal to the * OutputStream, which is left in an indeterminate state, and it is up to @@ -638,7 +682,11 @@ public class ObjectOutputStream * stream */ protected void writeStreamHeader() throws IOException { - bout.writeShort(STREAM_MAGIC); + if (useFastSerializer) { + bout.writeShort(STREAM_MAGIC_FAST); + } else { + bout.writeShort(STREAM_MAGIC); + } bout.writeShort(STREAM_VERSION); } @@ -653,6 +701,9 @@ public class ObjectOutputStream * By default, this method writes class descriptors according to the format * defined in the Object Serialization specification. * + * In fastSerializer mode, we will only write the classname to the stream. + * The annotateClass is used to match the resolveClass in readClassDescriptor. + * *

Note that this method will only be called if the ObjectOutputStream * is not using the old serialization stream format (set by calling * ObjectOutputStream's {@code useProtocolVersion} method). If this @@ -670,7 +721,14 @@ public class ObjectOutputStream protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { - desc.writeNonProxy(this); + if (useFastSerializer) { + writeUTF(desc.getName()); + // The annotateClass is used to match the resolveClass called in + // readClassDescriptor. + annotateClass(desc.forClass()); + } else { + desc.writeNonProxy(this); + } } /** @@ -1278,9 +1336,21 @@ public class ObjectOutputStream bout.writeByte(TC_CLASSDESC); handles.assign(unshared ? null : desc); + if (Logging.fastSerLogger != null) { + Logging.fastSerLogger.log(System.Logger.Level.DEBUG, + "[Serialize] useFastSerializer:{0}, Class name:{1}, SerialVersionUID:{2}, flags:{3}, protocol:{4}", + useFastSerializer, desc.getName(), desc.getSerialVersionUID(), desc.getFlags(this), protocol); + } + if (protocol == PROTOCOL_VERSION_1) { // do not invoke class descriptor write hook with old protocol - desc.writeNonProxy(this); + if (useFastSerializer) { + // only write name and annotate class when using FastSerializer + writeUTF(desc.getName()); + annotateClass(desc.forClass()); + } else { + desc.writeNonProxy(this); + } } else { writeClassDescriptor(desc); } @@ -1293,8 +1363,9 @@ public class ObjectOutputStream annotateClass(cl); bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); - - writeClassDesc(desc.getSuperDesc(), false); + if (!useFastSerializer) { + writeClassDesc(desc.getSuperDesc(), false); + } } /** diff --git a/src/java.base/share/classes/java/io/ObjectStreamClass.java b/src/java.base/share/classes/java/io/ObjectStreamClass.java index afb3d8447..08488d19c 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamClass.java +++ b/src/java.base/share/classes/java/io/ObjectStreamClass.java @@ -295,6 +295,40 @@ public class ObjectStreamClass implements Serializable { return suid.longValue(); } + /** + * Return the flags for this class described by this descriptor. The flags + * means a set of bit masks for ObjectStreamClass, which indicate the status + * of SC_WRITE_METHOD, SC_SERIALIZABLE, SC_EXTERNALIZABLE, SC_BLOCK_DATA and + * SC_ENUM. + * + * @param serialStream ObjectOutputStream or ObjectInputStream + * + * @return the flags for this class described by this descriptor + */ + byte getFlags(Object serialStream) { + byte flags = 0; + if (externalizable) { + flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; + if (serialStream instanceof ObjectOutputStream) { + int protocol = ((ObjectOutputStream)serialStream).getProtocolVersion(); + if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { + flags |= ObjectStreamConstants.SC_BLOCK_DATA; + } + } else if (serialStream instanceof ObjectInputStream) { + flags |= ObjectStreamConstants.SC_BLOCK_DATA; + } + } else if (serializable) { + flags |= ObjectStreamConstants.SC_SERIALIZABLE; + } + if (hasWriteObjectData) { + flags |= ObjectStreamConstants.SC_WRITE_METHOD; + } + if (isEnum) { + flags |= ObjectStreamConstants.SC_ENUM; + } + return flags; + } + /** * Return the class in the local VM that this version is mapped to. Null * is returned if there is no corresponding local class. @@ -467,6 +501,15 @@ public class ObjectStreamClass implements Serializable { ObjectStreamClass() { } + /** + * Create a blank class descriptor with name. It is only used + * in fastSerialize path. + * @param name class name + */ + ObjectStreamClass(String name) { + this.name = name; + } + /** * Creates a PermissionDomain that grants no permission. */ @@ -661,6 +704,44 @@ public class ObjectStreamClass implements Serializable { initialized = true; } + /** + * Initializes class descriptor representing a non-proxy class. + * Used in fast serialization mode. + */ + void initNonProxyFast(ObjectStreamClass model, + ClassNotFoundException resolveEx) + { + this.cl = model.cl; + this.resolveEx = resolveEx; + this.superDesc = model.superDesc; + name = model.name; + this.suid = model.suid; + isProxy = false; + isEnum = model.isEnum; + serializable = model.serializable; + externalizable = model.externalizable; + hasBlockExternalData = model.hasBlockExternalData; + hasWriteObjectData = model.hasWriteObjectData; + fields = model.fields; + primDataSize = model.primDataSize; + numObjFields = model.numObjFields; + + writeObjectMethod = model.writeObjectMethod; + readObjectMethod = model.readObjectMethod; + readObjectNoDataMethod = model.readObjectNoDataMethod; + writeReplaceMethod = model.writeReplaceMethod; + readResolveMethod = model.readResolveMethod; + if (deserializeEx == null) { + deserializeEx = model.deserializeEx; + } + domains = model.domains; + cons = model.cons; + fieldRefl = model.fieldRefl; + localDesc = model; + + initialized = true; + } + /** * Reads non-proxy class descriptor information from given input stream. * The resulting class descriptor is not fully functional; it can only be diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index 22aa09c9d..b6e7978a5 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -3811,7 +3811,7 @@ public final class Unsafe { private static long convEndian(boolean big, long n) { return big == BIG_ENDIAN ? n : Long.reverseBytes(n) ; } - + public native boolean getUseFastSerializer(); private native long allocateMemory0(long bytes); private native long reallocateMemory0(long address, long bytes); private native void freeMemory0(long address); -- 2.37.0