< prev index next >
src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
Print this page
rev 58565 : 8238358: Implementation of JEP 371: Hidden Classes
Reviewed-by: duke
Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com
@@ -23,11 +23,12 @@
* questions.
*/
package java.lang.invoke;
-import jdk.internal.misc.Unsafe;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
@@ -40,10 +41,13 @@
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.lang.invoke.MethodType.methodType;
+import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* <p>Methods to facilitate the creation of String concatenation methods, that
* can be used to efficiently concatenate a known number of arguments of known
@@ -131,10 +135,12 @@
/**
* Default strategy to use for concatenation.
*/
private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;
+ private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
+
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
@@ -187,12 +193,10 @@
/**
* Dump generated classes to disk, for debugging purposes.
*/
private static final ProxyClassesDumper DUMPER;
- private static final Class<?> STRING_HELPER;
-
static {
// In case we need to double-back onto the StringConcatFactory during this
// static initialization, make sure we have the reasonable defaults to complete
// the static initialization properly. After that, actual users would use
// the proper values we have read from the properties.
@@ -200,16 +204,10 @@
// CACHE_ENABLE = false; // implied
// CACHE = null; // implied
// DEBUG = false; // implied
// DUMPER = null; // implied
- try {
- STRING_HELPER = Class.forName("java.lang.StringConcatHelper");
- } catch (Throwable e) {
- throw new AssertionError(e);
- }
-
final String strategy =
VM.getSavedProperty("java.lang.invoke.stringConcat");
CACHE_ENABLE = Boolean.parseBoolean(
VM.getSavedProperty("java.lang.invoke.stringConcat.cache"));
DEBUG = Boolean.parseBoolean(
@@ -716,40 +714,27 @@
: args;
}
private static String getClassName(Class<?> hostClass) throws StringConcatException {
/*
- When cache is enabled, we want to cache as much as we can.
-
- However, there are two peculiarities:
+ The generated class is in the same package as the host class as
+ it's the implementation of the string concatenation for the host class.
- a) The generated class should stay within the same package as the
- host class, to allow Unsafe.defineAnonymousClass access controls
- to work properly. JDK may choose to fail with IllegalAccessException
- when accessing a VM anonymous class with non-privileged callers,
- see JDK-8058575.
-
- b) If we mark the stub with some prefix, say, derived from the package
- name because of (a), we can technically use that stub in other packages.
- But the call stack traces would be extremely puzzling to unsuspecting users
- and profiling tools: whatever stub wins the race, would be linked in all
- similar callsites.
-
- Therefore, we set the class prefix to match the host class package, and use
- the prefix as the cache key too. This only affects BC_* strategies, and only when
- cache is enabled.
+ When cache is enabled, we want to cache as much as we can.
*/
switch (STRATEGY) {
case BC_SB:
case BC_SB_SIZED:
case BC_SB_SIZED_EXACT: {
if (CACHE_ENABLE) {
String pkgName = hostClass.getPackageName();
- return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat";
+ return (!pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat";
} else {
- return hostClass.getName().replace('.', '/') + "$$StringConcat";
+ String name = hostClass.isHiddenClass() ? hostClass.getName().replace('/', '_')
+ : hostClass.getName();
+ return name.replace('.', '/') + "$$StringConcat";
}
}
case MH_SB_SIZED:
case MH_SB_SIZED_EXACT:
case MH_INLINE_SIZED_EXACT:
@@ -817,11 +802,11 @@
*
* <p>This strategy spins up the bytecode that has the same StringBuilder
* chain javac would otherwise emit. This strategy uses only the public API,
* and comes as the baseline for the current JDK behavior. On other words,
* this strategy moves the javac generated bytecode to runtime. The
- * generated bytecode is loaded via Unsafe.defineAnonymousClass, but with
+ * generated bytecode is loaded via Lookup::defineClass, but with
* the caller class coming from the BSM -- in other words, the protection
* guarantees are inherited from the method where invokedynamic was
* originally called. This means, among other things, that the bytecode is
* verified for all non-JDK uses.
*
@@ -846,11 +831,10 @@
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class BytecodeStringBuilderStrategy {
- static final Unsafe UNSAFE = Unsafe.getUnsafe();
static final int CLASSFILE_VERSION = 52;
static final String METHOD_NAME = "concat";
private BytecodeStringBuilderStrategy() {
// no instantiation
@@ -859,11 +843,11 @@
private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(CLASSFILE_VERSION,
ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
- className, // Unsafe.defineAnonymousClass would append an unique ID
+ className,
null,
"java/lang/Object",
null
);
@@ -872,10 +856,11 @@
METHOD_NAME,
args.toMethodDescriptorString(),
null,
null);
+ // use of @ForceInline no longer has any effect
mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true);
mv.visitCode();
Class<?>[] arr = args.parameterArray();
boolean[] guaranteedNonNull = new boolean[arr.length];
@@ -1141,15 +1126,13 @@
mv.visitEnd();
cw.visitEnd();
byte[] classBytes = cw.toByteArray();
try {
- Class<?> hostClass = lookup.lookupClass();
- Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null);
- UNSAFE.ensureClassInitialized(innerClass);
- dumpIfEnabled(innerClass.getName(), classBytes);
- return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args);
+ Class<?> innerClass = lookup.defineHiddenClass(classBytes, true, STRONG).lookupClass();
+ dumpIfEnabled(className, classBytes);
+ return lookup.findStatic(innerClass, METHOD_NAME, args);
} catch (Exception e) {
dumpIfEnabled(className + "$$FAILED", classBytes);
throw new StringConcatException("Exception while spinning the class", e);
}
}
@@ -1268,12 +1251,12 @@
*
* <p>This strategy avoids spinning up the bytecode by building the
* computation on MethodHandle combinators. The computation is built with
* public MethodHandle APIs, resolved from a public Lookup sequence, and
* ends up calling the public StringBuilder API. Therefore, this strategy
- * does not use any private API at all, even the Unsafe.defineAnonymousClass,
- * since everything is handled under cover by java.lang.invoke APIs.
+ * does not use any private API at all since everything is handled under
+ * cover by java.lang.invoke APIs.
*
* <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder,
* sized exactly".</b>
*
* <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first
@@ -1281,11 +1264,10 @@
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class MethodHandleStringBuilderStrategy {
-
private MethodHandleStringBuilderStrategy() {
// no instantiation
}
private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception {
@@ -1459,10 +1441,12 @@
sum += v;
}
return sum;
}
+ private static final Lookup MHSBS_LOOKUP = lookup();
+
private static final ConcurrentMap<Integer, MethodHandle> SUMMERS;
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() {
@Override
@@ -1472,13 +1456,13 @@
} else if (cnt <= 8) {
// Variable-arity collectors are not as efficient as small-count methods,
// unroll some initial sizes.
Class<?>[] cls = new Class<?>[cnt];
Arrays.fill(cls, int.class);
- return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);
+ return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);
} else {
- return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)
+ return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)
.asCollector(int[].class, cnt - 1);
}
}
};
@@ -1489,12 +1473,12 @@
Lookup publicLookup = MethodHandles.publicLookup();
NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class);
STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class);
BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class);
if (DEBUG) {
- BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP,
- MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class);
+ BUILDER_TO_STRING_CHECKED = lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class,
+ "toStringChecked", String.class, StringBuilder.class);
} else {
BUILDER_TO_STRING_CHECKED = null;
}
}
@@ -1514,12 +1498,10 @@
* particular implementation details for String, this opens the door for
* building a very optimal concatenation sequence. This is the only strategy
* that requires porting if there are private JDK changes occur.
*/
private static final class MethodHandleInlineCopyStrategy {
- static final Unsafe UNSAFE = Unsafe.getUnsafe();
-
private MethodHandleInlineCopyStrategy() {
// no instantiation
}
static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
@@ -1734,21 +1716,21 @@
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
- return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
- String.class, Wrapper.asPrimitiveType(c), String.class);
+ return JLA.stringConcatHelper("prepend",
+ methodType(long.class, long.class, byte[].class,
+ String.class, Wrapper.asPrimitiveType(c), String.class));
}
};
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
- return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
- Wrapper.asPrimitiveType(c));
+ return JLA.stringConcatHelper("mix", methodType(long.class, long.class, Wrapper.asPrimitiveType(c)));
}
};
private static final MethodHandle SIMPLE;
private static final MethodHandle NEW_STRING;
@@ -1757,22 +1739,22 @@
private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
private static final long INITIAL_CODER;
static {
try {
- MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", long.class);
+ MethodHandle initCoder = JLA.stringConcatHelper("initialCoder", methodType(long.class));
INITIAL_CODER = (long) initCoder.invoke();
} catch (Throwable e) {
throw new AssertionError(e);
}
PREPENDERS = new ConcurrentHashMap<>();
MIXERS = new ConcurrentHashMap<>();
- SIMPLE = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "simpleConcat", String.class, Object.class, Object.class);
- NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, long.class);
- NEW_ARRAY = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newArray", byte[].class, long.class);
+ SIMPLE = JLA.stringConcatHelper("simpleConcat", methodType(String.class, Object.class, Object.class));
+ NEW_STRING = JLA.stringConcatHelper("newString", methodType(String.class, byte[].class, long.class));
+ NEW_ARRAY = JLA.stringConcatHelper( "newArray", methodType(byte[].class, long.class));
}
}
/**
* Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
@@ -1782,11 +1764,11 @@
private Stringifiers() {
// no instantiation
}
private static final MethodHandle OBJECT_INSTANCE =
- lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "stringOf", String.class, Object.class);
+ JLA.stringConcatHelper("stringOf", methodType(String.class, Object.class));
private static class FloatStringifiers {
private static final MethodHandle FLOAT_INSTANCE =
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class);
< prev index next >