--- old/src/java.base/share/classes/java/io/ObjectInputStream.java 2017-10-27 21:44:35.595564513 +0900 +++ new/src/java.base/share/classes/java/io/ObjectInputStream.java 2017-10-27 21:44:35.527564496 +0900 @@ -419,6 +419,19 @@ // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; + // save currentReader + Object outerReader = currentReader; + + Thread thread = Thread.currentThread(); + if (outerReader == null || + outerReader == thread || + (outerReader instanceof ReaderContext && + ((ReaderContext) outerReader).thread == thread)) { + // If no other thead caches ReaderContext or this is recursive entry, + // place mark so that latestUserDefinedLoader() can cache loader in + // this context + currentReader = thread; + } try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); @@ -436,6 +449,13 @@ if (closed && depth == 0) { clear(); } + if (outerReader == null || + outerReader == thread || + (outerReader instanceof ReaderContext && + ((ReaderContext) outerReader).thread == thread)) { + // restore/clear currentReader when in correct thread/non-nested call + currentReader = outerReader; + } } } @@ -514,6 +534,19 @@ public Object readUnshared() throws IOException, ClassNotFoundException { // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; + // save currentReader + Object outerReader = currentReader; + + Thread thread = Thread.currentThread(); + if (outerReader == null || + outerReader == thread || + (outerReader instanceof ReaderContext && + ((ReaderContext) outerReader).thread == thread)) { + // If no other thead caches ReaderContext or this is recursive entry, + // place mark so that latestUserDefinedLoader() can cache loader in + // this context + currentReader = thread; + } try { Object obj = readObject0(true); handles.markDependency(outerHandle, passHandle); @@ -531,6 +564,13 @@ if (closed && depth == 0) { clear(); } + if (outerReader == null || + outerReader == thread || + (outerReader instanceof ReaderContext && + ((ReaderContext) outerReader).thread == thread)) { + // restore/clear currentReader when in correct thread/non-nested call + currentReader = outerReader; + } } } @@ -2389,14 +2429,60 @@ double[] dst, int dstpos, int ndoubles); + // Holds a pair of latestUserDefinedLoader() result and the thread that called + // the method, so that latestUserDefinedLoader() can identify if it can reuse + // the cached result. + private static class ReaderContext { + final ClassLoader loader; + final Thread thread = Thread.currentThread(); + + ReaderContext(ClassLoader loader) { + this.loader = loader; + } + } + + // Records the thread that can cache latestUserDefinedLoader. This field containes + // either null (when not called via public readObject() / readUnshared()), or + // a Thread instance (marking the thread that entered public readObject() / + // readUnshared() and is responsible for cleanup too), or + // a ReaderContext instance with cached loader. + // On recursive entry to public readObject() / readUnshared(), the current value + // of this field is saved in a local variable and reset to the Thread instance, + // so that the nexted latestUserDefinedLoader() can evaluate loader in the context. + // This field is left non-volatile because this field does not intend to limit + // only one thread to use ReaderContext but to make sure a thread uses correct + // ReaderContext and cleans it up on return from readObject() / readUnshared(). + private Object currentReader; + /** * Returns the first non-null and non-platform class loader (not counting * class loaders of generated reflection implementation classes) up the * execution stack, or the platform class loader if only code from the * bootstrap and platform class loader is on the stack. */ - private static ClassLoader latestUserDefinedLoader() { - return jdk.internal.misc.VM.latestUserDefinedLoader(); + private ClassLoader latestUserDefinedLoader() { + Object readerCtx = currentReader; + ClassLoader loader; + Thread thread = Thread.currentThread(); + if (readerCtx == thread) { + // entered via public readObject() / readUnshared() + // so we must evaluate loader lazily and cache it + loader = jdk.internal.misc.VM.latestUserDefinedLoader(); + currentReader = new ReaderContext(loader); + } else if (readerCtx instanceof ReaderContext && + ((ReaderContext) readerCtx).thread == thread) { + // use cached value if correct thread + loader = ((ReaderContext) readerCtx).loader; + } else { + // not called via public readObject() / readUnshared(): + // (readerCtx == null) or + // invalid multi threaded use: + // (readerCtx != Thread.currentThread() && + // readerCtx.thread != Thread.currentThread()) + // - don't cache + loader = jdk.internal.misc.VM.latestUserDefinedLoader(); + } + return loader; } /**