< 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,32 ****
--- 23,33 ----
* 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,50 ****
--- 40,53 ----
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,76 ****
--- 64,82 ----
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,94 ****
--- 91,101 ----
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,137 ****
// 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);
/**
* 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"
--- 133,147 ----
// 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 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,206 ****
/* Force DebuggerSupport to be loaded. */
static {
DebuggerSupport.FORCELOAD = true;
}
/**
* 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 ContextCodeInstaller(final Context context, final ScriptLoader loader, 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 {
--- 171,207 ----
/* 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
*/
! private abstract static class ContextCodeInstaller implements CodeInstaller {
! final Context context;
! final CodeSource codeSource;
! ContextCodeInstaller(final Context context, final CodeSource codeSource) {
this.context = context;
this.codeSource = codeSource;
}
@Override
public Context getContext() {
return context;
}
@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,272 ****
}
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) {
! final ContextCodeInstaller cci = (ContextCodeInstaller)other;
! return cci.context == context && cci.codeSource == codeSource;
}
! return false;
}
}
/** Is Context global debug mode enabled ? */
public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");
--- 249,401 ----
}
return null;
}
@Override
! 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 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, 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);
}
! }
! });
}
}
/** Is Context global debug mode enabled ? */
public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");
*** 646,656 ****
*
* @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 MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz);
return new MultiGlobalCompiledScript() {
@Override
public ScriptFunction getFunction(final Global newGlobal) {
--- 775,785 ----
*
* @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, false);
final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz);
return new MultiGlobalCompiledScript() {
@Override
public ScriptFunction getFunction(final Global newGlobal) {
*** 700,710 ****
// 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);
} catch (final ParserException e) {
e.throwAsEcmaException(global);
return null;
}
--- 829,839 ----
// 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, true);
} catch (final ParserException e) {
e.throwAsEcmaException(global);
return null;
}
*** 1246,1259 ****
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);
}
! private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) {
// start with no errors, no warnings.
errMan.reset();
Class<?> script = findCachedClass(source);
if (script != null) {
--- 1375,1388 ----
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, false), scope);
}
! 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,1311 ****
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);
if (storedScript == null) {
final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL;
final Compiler compiler = Compiler.forInitialCompilation(
--- 1428,1446 ----
if (env._parse_only) {
return null;
}
final URL url = source.getURL();
final CodeSource cs = new CodeSource(url, (CodeSigner[])null);
! 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 >