diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index 2f14e01ce..d8f1679b4 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -1018,6 +1018,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 @@ -1102,7 +1106,10 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "fullFence", CC "()V", FN_PTR(Unsafe_FullFence)}, {CC "isBigEndian0", CC "()Z", FN_PTR(Unsafe_isBigEndian0)}, - {CC "unalignedAccess0", CC "()Z", FN_PTR(Unsafe_unalignedAccess0)} + {CC "unalignedAccess0", CC "()Z", FN_PTR(Unsafe_unalignedAccess0)}, + + {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 4b8dbe899..ec5a4e50c 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2684,6 +2684,10 @@ define_pd_global(uint64_t,MaxRAM, 1ULL*G); JFR_ONLY(product(ccstr, StartFlightRecording, NULL, \ "Start flight recording with options")) \ \ + experimental(bool, UseFastSerializer, false, \ + "Cache-based serialization.It is extremely fast, but it can only" \ + "be effective in certain scenarios.") \ + \ experimental(bool, UseFastUnorderedTimeStamps, false, \ "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 939b7647e..f59a51316 100644 --- a/src/java.base/share/classes/java/io/ObjectInputStream.java +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java @@ -331,6 +331,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. @@ -344,6 +347,25 @@ 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. + */ + private static final boolean fastSerializerEscapeMode = java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "fastSerializerEscapeMode")).booleanValue(); + /** * Creates an ObjectInputStream that reads from the specified InputStream. * A serialization stream header is read from the stream and verified. @@ -421,6 +443,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 @@ -709,11 +734,20 @@ public class ObjectInputStream vlist.register(obj, prio); } + /** + * Cache the class meta during serialization. + * Only used in FastSerilizer. + */ + protected 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 ObjectOutputStream is * annotateClass. This method will be invoked only once for * each unique class in the stream. This method can be implemented by @@ -752,16 +786,26 @@ 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; } /** @@ -935,9 +979,25 @@ 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", 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; + } + } else if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) { + throw new StreamCorruptedException( + String.format("invalid stream header: %04X%04X", s0, s1)); } } @@ -951,6 +1011,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 @@ -961,6 +1026,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; @@ -2008,36 +2096,52 @@ 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); @@ -2936,8 +3040,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 135e5645a..044924593 100644 --- a/src/java.base/share/classes/java/io/ObjectOutputStream.java +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java @@ -36,6 +36,7 @@ import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static java.io.ObjectStreamClass.processQueue; +import jdk.internal.misc.Unsafe; import sun.reflect.misc.ReflectUtil; /** @@ -192,7 +193,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 +215,14 @@ 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(); + /** * Creates an ObjectOutputStream that writes to the specified OutputStream. * This constructor writes the serialization stream header to the @@ -328,6 +336,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 @@ -636,7 +647,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); } @@ -651,6 +666,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 useProtocolVersion method). If this @@ -668,7 +686,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,7 +1303,13 @@ public class ObjectOutputStream 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); } @@ -1291,8 +1322,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 17739cdc7..ac3a92bef 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamClass.java +++ b/src/java.base/share/classes/java/io/ObjectStreamClass.java @@ -560,6 +560,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. */ @@ -746,6 +755,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/java/io/ObjectStreamConstants.java b/src/java.base/share/classes/java/io/ObjectStreamConstants.java index 43a480ce4..96157782a 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamConstants.java +++ b/src/java.base/share/classes/java/io/ObjectStreamConstants.java @@ -38,6 +38,11 @@ public interface ObjectStreamConstants { */ static final short STREAM_MAGIC = (short)0xaced; + /** + * Magic number that is written to the stream header when using fastserilizer. + */ + static final short STREAM_MAGIC_FAST = (short)0xdeca; + /** * Version number that is written to the stream header. */ 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 031b5aae5..d78caabdc 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -3703,7 +3703,7 @@ public final class Unsafe { private static long convEndian(boolean big, long n) { return big == BE ? 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.19.0