8135251: Use Unsafe.defineAnonymousClass for loading Nashorn script code
Reviewed-by: hannesw, lagergren, sundar
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Sep 16 14:42:32 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Sep 16 18:34:10 2015 +0200
@@ -488,8 +488,9 @@
Class<?> rootClass = null;
long length = 0L;
- final CodeInstaller codeInstaller = compiler.getCodeInstaller();
- final Map<String, byte[]> bytecode = compiler.getBytecode();
+ final CodeInstaller origCodeInstaller = compiler.getCodeInstaller();
+ final Map<String, byte[]> bytecode = compiler.getBytecode();
+ final CodeInstaller codeInstaller = bytecode.size() > 1 ? origCodeInstaller.getMultiClassCodeInstaller() : origCodeInstaller;
for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
final String className = entry.getKey();
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeInstaller.java Wed Sep 16 14:42:32 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeInstaller.java Wed Sep 16 18:34:10 2015 +0200
@@ -101,16 +101,24 @@
public StoredScript loadScript(Source source, String functionKey);
/**
- * Returns a new code installer that shares most of the functionality of this code installer, but uses a
- * new, independent class loader.
- * @return a new code installer with a new independent class loader.
+ * Returns a code installer {@code #isCompatibleWith(CodeInstaller) compatible with} this installer, but
+ * is suitable for on-demand compilations. Can return itself if it is itself suitable.
+ * @return a compatible code installer suitable for on-demand compilations.
*/
- public CodeInstaller withNewLoader();
+ public CodeInstaller getOnDemandCompilationInstaller();
+
+ /**
+ * Returns a code installer {@code #isCompatibleWith(CodeInstaller) compatible with} this installer, but
+ * is suitable for installation of multiple classes that reference each other by name. Should be used when
+ * a compilation job produces multiple compilation units. Can return itself if it is itself suitable.
+ * @return a compatible code installer suitable for installation of multiple classes.
+ */
+ public CodeInstaller getMultiClassCodeInstaller();
/**
* Returns true if this code installer is compatible with the other code installer. Compatibility is expected to be
* an equivalence relation, and installers are supposed to be compatible with those they create using
- * {@link #withNewLoader()}.
+ * {@link #getOnDemandCompilationInstaller()}.
* @param other the other code installer tested for compatibility with this code installer.
* @return true if this code installer is compatible with the other code installer.
*/
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java Wed Sep 16 14:42:32 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java Wed Sep 16 18:34:10 2015 +0200
@@ -25,6 +25,7 @@
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;
@@ -41,8 +42,10 @@
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;
@@ -61,14 +64,18 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
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;
@@ -87,6 +94,7 @@
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.
@@ -128,9 +136,12 @@
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();
+ private static final boolean DISABLE_VM_ANONYMOUS_CLASSES = Options.getBooleanProperty("nashorn.disableVmAnonymousClasses");
/**
* 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
@@ -163,25 +174,24 @@
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;
+ private abstract static class ContextCodeInstaller implements CodeInstaller {
+ final Context context;
+ final CodeSource codeSource;
- // 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 ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) {
- this.context = context;
- this.loader = loader;
+ ContextCodeInstaller(final Context context, final CodeSource codeSource) {
+ this.context = context;
this.codeSource = codeSource;
}
@@ -191,14 +201,6 @@
}
@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>() {
@@ -250,15 +252,6 @@
}
@Override
- public CodeInstaller withNewLoader() {
- // 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);
- }
-
- @Override
public boolean isCompatibleWith(final CodeInstaller other) {
if (other instanceof ContextCodeInstaller) {
final ContextCodeInstaller cci = (ContextCodeInstaller)other;
@@ -268,6 +261,117 @@
}
}
+ 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 NamedContextCodeInstaller(context, codeSource, context.createNewLoader());
+ }
+
+ @Override
+ 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, Reference<Class<?>>> anonymousHostClasses = new ConcurrentHashMap<>();
+
+ 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) {
+ super(context, codeSource);
+ hostClass = getAnonymousHostClass();
+ }
+
+ @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 Class<?> getAnonymousHostClass() {
+ final Reference<Class<?>> ref = context.anonymousHostClasses.get(codeSource);
+ if (ref != null) {
+ final Class<?> existingHostClass = ref.get();
+ if (existingHostClass != null) {
+ return existingHostClass;
+ }
+ }
+ final Class<?> newHostClass = context.createNewLoader().installClass(ANONYMOUS_HOST_CLASS_NAME, ANONYMOUS_HOST_CLASS_BYTES, codeSource);
+ context.anonymousHostClasses.put(codeSource, new WeakReference<>(newHostClass));
+ final context.anonymousHostClasses.remo
+ return newHostClass;
+ }
+
+ 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);
+ }
+ }
+ });
+ }
+ }
+
/** Is Context global debug mode enabled ? */
public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");
@@ -1294,9 +1398,15 @@
}
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 (DISABLE_VM_ANONYMOUS_CLASSES || 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);
+ }
if (storedScript == null) {
final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 14:42:32 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 18:34:10 2015 +0200
@@ -649,7 +649,7 @@
*/
private CodeInstaller getInstallerForNewCode() {
final ScriptEnvironment env = installer.getContext().getEnv();
- return env._optimistic_types || env._loader_per_compile ? installer.withNewLoader() : installer;
+ return env._optimistic_types || env._loader_per_compile ? installer.getOnDemandCompilationInstaller() : installer;
}
Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java Wed Sep 16 14:42:32 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java Wed Sep 16 18:34:10 2015 +0200
@@ -220,19 +220,20 @@
}
final long total = t - startTime;
- sb.append('\n');
- sb.append("Total runtime: ").
+ return sb.append("\nTotal runtime: ").
append(toMillisPrint(total)).
append(" ms (Non-runtime: ").
append(toMillisPrint(knownTime)).
append(" ms [").
append((int)(knownTime * 100.0 / total)).
- append("%])");
-
- sb.append("\n\nEmitted compile units: ").
- append(CompileUnit.getEmittedUnitCount());
-
- return sb.toString();
+ append("%])").
+ append("\n\nEmitted compile units: ").
+ append(CompileUnit.getEmittedUnitCount()).
+ append("\nCompile units installed as named classes: ").
+ append(Context.getNamedInstalledScriptCount()).
+ append("\nCompile units installed as anonymous classes: ").
+ append(Context.getAnonymousInstalledScriptCount()).
+ toString();
}
private void accumulateTime(final String module, final long duration) {