8135251: Use Unsafe.defineAnonymousClass for loading Nashorn script code
Reviewed-by: hannesw, lagergren, sundar
This commit is contained in:
parent
f4e6cd073f
commit
7d849fd622
@ -488,8 +488,9 @@ abstract class CompilationPhase {
|
||||
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();
|
||||
|
@ -101,16 +101,24 @@ public interface CodeInstaller {
|
||||
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.
|
||||
*/
|
||||
|
@ -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.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;
|
||||
@ -61,14 +64,18 @@ import java.util.HashMap;
|
||||
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.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.
|
||||
@ -128,9 +136,12 @@ public final class Context {
|
||||
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 @@ public final class Context {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -190,14 +200,6 @@ public final class Context {
|
||||
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 {
|
||||
@ -249,15 +251,6 @@ public final class Context {
|
||||
return null;
|
||||
}
|
||||
|
||||
@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) {
|
||||
@ -268,6 +261,117 @@ public final class Context {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public final class Context {
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -649,7 +649,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
*/
|
||||
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,
|
||||
|
@ -220,19 +220,20 @@ public final class Timing implements Loggable {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user