< prev index next >

src/jdk/nashorn/internal/runtime/Context.java

Print this page
rev 1901 : 8135251: Use Unsafe.defineAnonymousClass for loading Nashorn script code
Reviewed-by: hannesw, lagergren, sundar
rev 1902 : 8136647: Syntactic error accidentally left in JDK-8135251 changeset
Reviewed-by: sundar
rev 1903 : 8136700: Make sure Context.anonymousHostClasses doesn't grow unbounded
Reviewed-by: hannesw, sundar
rev 1904 : 8138882: Performance regression due to anonymous classloading
Reviewed-by: attila, sundar
rev 1905 : 8162955: Activate anonymous class loading for small sources
Reviewed-by: sundar

@@ -23,10 +23,11 @@
  * questions.
  */
 
 package jdk.nashorn.internal.runtime;
 
+import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
 import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore;

@@ -39,12 +40,14 @@
 import java.io.PrintWriter;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.invoke.SwitchPoint;
+import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.AccessControlContext;

@@ -61,16 +64,19 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.LongAdder;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.logging.Level;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
 import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
 import jdk.nashorn.api.scripting.ClassFilter;
 import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;

@@ -85,10 +91,11 @@
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
 import jdk.nashorn.internal.runtime.logging.Logger;
 import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo;
 import jdk.nashorn.internal.runtime.options.Options;
+import sun.misc.Unsafe;
 
 /**
  * This class manages the global state of execution. Context is immutable.
  */
 public final class Context {

@@ -126,12 +133,15 @@
     // nashorn load psuedo URL prefixes
     private static final String LOAD_CLASSPATH = "classpath:";
     private static final String LOAD_FX = "fx:";
     private static final String LOAD_NASHORN = "nashorn:";
 
-    private static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
-    private static MethodType CREATE_PROGRAM_FUNCTION_TYPE = MethodType.methodType(ScriptFunction.class, ScriptObject.class);
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+    private static final MethodType CREATE_PROGRAM_FUNCTION_TYPE = MethodType.methodType(ScriptFunction.class, ScriptObject.class);
+
+    private static final LongAdder NAMED_INSTALLED_SCRIPT_COUNT = new LongAdder();
+    private static final LongAdder ANONYMOUS_INSTALLED_SCRIPT_COUNT = new LongAdder();
 
     /**
      * Should scripts use only object slots for fields, or dual long/object slots? The default
      * behaviour is to couple this to optimistic types, using dual representation if optimistic types are enabled
      * and single field representation otherwise. This can be overridden by setting either the "nashorn.fields.objects"

@@ -161,46 +171,37 @@
     /* Force DebuggerSupport to be loaded. */
     static {
         DebuggerSupport.FORCELOAD = true;
     }
 
+    static long getNamedInstalledScriptCount() {
+        return NAMED_INSTALLED_SCRIPT_COUNT.sum();
+    }
+
+    static long getAnonymousInstalledScriptCount() {
+        return ANONYMOUS_INSTALLED_SCRIPT_COUNT.sum();
+    }
+
     /**
      * ContextCodeInstaller that has the privilege of installing classes in the Context.
      * Can only be instantiated from inside the context and is opaque to other classes
      */
-    public static class ContextCodeInstaller implements CodeInstaller {
-        private final Context      context;
-        private final ScriptLoader loader;
-        private final CodeSource   codeSource;
-        private int usageCount = 0;
-        private int bytesDefined = 0;
-
-        // We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition
-        // will occur much earlier, the second is a safety measure for very large scripts/functions.
-        private final static int MAX_USAGES = 10;
-        private final static int MAX_BYTES_DEFINED = 200_000;
+    private abstract static class ContextCodeInstaller implements CodeInstaller {
+        final Context context;
+        final CodeSource codeSource;
 
-        private ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) {
+        ContextCodeInstaller(final Context context, final CodeSource codeSource) {
             this.context    = context;
-            this.loader     = loader;
             this.codeSource = codeSource;
         }
 
         @Override
         public Context getContext() {
             return context;
         }
 
         @Override
-        public Class<?> install(final String className, final byte[] bytecode) {
-            usageCount++;
-            bytesDefined += bytecode.length;
-            final String   binaryName = Compiler.binaryName(className);
-            return loader.installClass(binaryName, bytecode, codeSource);
-        }
-
-        @Override
         public void initialize(final Collection<Class<?>> classes, final Source source, final Object[] constants) {
             try {
                 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                     @Override
                     public Void run() throws Exception {

@@ -248,25 +249,153 @@
             }
             return null;
         }
 
         @Override
-        public CodeInstaller withNewLoader() {
+        public boolean isCompatibleWith(final CodeInstaller other) {
+            if (other instanceof ContextCodeInstaller) {
+                final ContextCodeInstaller cci = (ContextCodeInstaller)other;
+                return cci.context == context && cci.codeSource == codeSource;
+            }
+            return false;
+        }
+    }
+
+    private static class NamedContextCodeInstaller extends ContextCodeInstaller {
+        private final ScriptLoader loader;
+        private int usageCount = 0;
+        private int bytesDefined = 0;
+
+        // We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition
+        // will occur much earlier, the second is a safety measure for very large scripts/functions.
+        private final static int MAX_USAGES = 10;
+        private final static int MAX_BYTES_DEFINED = 200_000;
+
+        private NamedContextCodeInstaller(final Context context, final CodeSource codeSource, final ScriptLoader loader) {
+            super(context, codeSource);
+            this.loader = loader;
+        }
+
+        @Override
+        public Class<?> install(final String className, final byte[] bytecode) {
+            usageCount++;
+            bytesDefined += bytecode.length;
+            NAMED_INSTALLED_SCRIPT_COUNT.increment();
+            return loader.installClass(Compiler.binaryName(className), bytecode, codeSource);
+        }
+
+        @Override
+        public CodeInstaller getOnDemandCompilationInstaller() {
             // Reuse this installer if we're within our limits.
             if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) {
                 return this;
             }
-            return new ContextCodeInstaller(context, context.createNewLoader(), codeSource);
+            return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader());
         }
 
         @Override
-        public boolean isCompatibleWith(final CodeInstaller other) {
-            if (other instanceof ContextCodeInstaller) {
-                final ContextCodeInstaller cci = (ContextCodeInstaller)other;
-                return cci.context == context && cci.codeSource == codeSource;
+        public CodeInstaller getMultiClassCodeInstaller() {
+            // This installer is perfectly suitable for installing multiple classes that reference each other
+            // as it produces classes with resolvable names, all defined in a single class loader.
+            return this;
+        }
+    }
+
+    private final Map<CodeSource, HostClassReference> anonymousHostClasses = new HashMap<>();
+    private final ReferenceQueue<Class<?>> anonymousHostClassesRefQueue = new ReferenceQueue<>();
+
+    private static class HostClassReference extends WeakReference<Class<?>> {
+        final CodeSource codeSource;
+
+        HostClassReference(final CodeSource codeSource, final Class<?> clazz, final ReferenceQueue<Class<?>> refQueue) {
+            super(clazz, refQueue);
+            this.codeSource = codeSource;
+        }
+    }
+
+    private synchronized Class<?> getAnonymousHostClass(final CodeSource codeSource) {
+        // Remove cleared entries
+        for(;;) {
+            final HostClassReference clearedRef = (HostClassReference)anonymousHostClassesRefQueue.poll();
+            if (clearedRef == null) {
+                break;
+            }
+            anonymousHostClasses.remove(clearedRef.codeSource, clearedRef);
+        }
+
+        // Try to find an existing host class
+        final Reference<Class<?>> ref = anonymousHostClasses.get(codeSource);
+        if (ref != null) {
+            final Class<?> existingHostClass = ref.get();
+            if (existingHostClass != null) {
+                return existingHostClass;
+            }
+        }
+
+        // Define a new host class if existing is not found
+        final Class<?> newHostClass = createNewLoader().installClass(
+                // NOTE: we're defining these constants in AnonymousContextCodeInstaller so they are not
+                // initialized if we don't use AnonymousContextCodeInstaller. As this method is only ever
+                // invoked from AnonymousContextCodeInstaller, this is okay.
+                AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_NAME,
+                AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_BYTES, codeSource);
+        anonymousHostClasses.put(codeSource, new HostClassReference(codeSource, newHostClass, anonymousHostClassesRefQueue));
+        return newHostClass;
+    }
+
+    private static final class AnonymousContextCodeInstaller extends ContextCodeInstaller {
+        private static final Unsafe UNSAFE = getUnsafe();
+        private static final String ANONYMOUS_HOST_CLASS_NAME = Compiler.SCRIPTS_PACKAGE.replace('/', '.') + ".AnonymousHost";
+        private static final byte[] ANONYMOUS_HOST_CLASS_BYTES = getAnonymousHostClassBytes();
+
+        private final Class<?> hostClass;
+
+        private AnonymousContextCodeInstaller(final Context context, final CodeSource codeSource, final Class<?> hostClass) {
+            super(context, codeSource);
+            this.hostClass = hostClass;
+        }
+
+        @Override
+        public Class<?> install(final String className, final byte[] bytecode) {
+            ANONYMOUS_INSTALLED_SCRIPT_COUNT.increment();
+            return UNSAFE.defineAnonymousClass(hostClass, bytecode, null);
+        }
+
+        @Override
+        public CodeInstaller getOnDemandCompilationInstaller() {
+            // This code loader can be indefinitely reused for on-demand recompilations for the same code source.
+            return this;
+        }
+
+        @Override
+        public CodeInstaller getMultiClassCodeInstaller() {
+            // This code loader can not be used to install multiple classes that reference each other, as they
+            // would have no resolvable names. Therefore, in such situation we must revert to an installer that
+            // produces named classes.
+            return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader());
+        }
+
+        private static final byte[] getAnonymousHostClassBytes() {
+            final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+            cw.visit(V1_7, Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, ANONYMOUS_HOST_CLASS_NAME.replace('.', '/'), null, "java/lang/Object", null);
+            cw.visitEnd();
+            return cw.toByteArray();
+        }
+
+        private static Unsafe getUnsafe() {
+            return AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
+                @Override
+                public Unsafe run() {
+                    try {
+                        final Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+                        theUnsafeField.setAccessible(true);
+                        return (Unsafe)theUnsafeField.get(null);
+                    } catch (final ReflectiveOperationException e) {
+                        throw new RuntimeException(e);
             }
-            return false;
+                }
+            });
         }
     }
 
     /** Is Context global debug mode enabled ? */
     public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");

@@ -646,11 +775,11 @@
      *
      * @param source the script source
      * @return reusable compiled script across many global scopes.
      */
     public MultiGlobalCompiledScript compileScript(final Source source) {
-        final Class<?> clazz = compile(source, this.errors, this._strict);
+        final Class<?> clazz = compile(source, this.errors, this._strict, false);
         final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz);
 
         return new MultiGlobalCompiledScript() {
             @Override
             public ScriptFunction getFunction(final Global newGlobal) {

@@ -700,11 +829,11 @@
         // Nashorn extension: any 'eval' is unconditionally strict when -strict is specified.
         boolean strictFlag = strict || this._strict;
 
         Class<?> clazz = null;
         try {
-            clazz = compile(source, new ThrowErrorManager(), strictFlag);
+            clazz = compile(source, new ThrowErrorManager(), strictFlag, true);
         } catch (final ParserException e) {
             e.throwAsEcmaException(global);
             return null;
         }
 

@@ -1246,14 +1375,14 @@
             throw new AssertionError("Failed to create a program function", t);
         }
     }
 
     private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) {
-        return getProgramFunction(compile(source, errMan, this._strict), scope);
+        return getProgramFunction(compile(source, errMan, this._strict, false), scope);
     }
 
-    private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) {
+    private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict, final boolean isEval) {
         // start with no errors, no warnings.
         errMan.reset();
 
         Class<?> script = findCachedClass(source);
         if (script != null) {

@@ -1299,13 +1428,19 @@
         if (env._parse_only) {
             return null;
         }
 
         final URL          url    = source.getURL();
-        final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader;
         final CodeSource   cs     = new CodeSource(url, (CodeSigner[])null);
-        final CodeInstaller installer = new ContextCodeInstaller(this, loader, cs);
+        final CodeInstaller installer;
+        if (!env.useAnonymousClasses(source.getLength()) || env._persistent_cache || !env._lazy_compilation) {
+            // Persistent code cache and eager compilation preclude use of VM anonymous classes
+            final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader;
+            installer = new NamedContextCodeInstaller(this, cs, loader);
+        } else {
+            installer = new AnonymousContextCodeInstaller(this, cs, getAnonymousHostClass(cs));
+        }
 
         if (storedScript == null) {
             final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL;
 
             final Compiler compiler = Compiler.forInitialCompilation(
< prev index next >