8057020: LambdaForm caches should support eviction
Reviewed-by: psandoz, jrose, shade
This commit is contained in:
parent
bf31fc249e
commit
ea89dad13c
@ -125,7 +125,7 @@ class LambdaForm {
|
||||
MemberName vmentry; // low-level behavior, or null if not yet prepared
|
||||
private boolean isCompiled;
|
||||
|
||||
Object transformCache; // managed by LambdaFormEditor
|
||||
volatile Object transformCache; // managed by LambdaFormEditor
|
||||
|
||||
public static final int VOID_RESULT = -1, LAST_RESULT = -2;
|
||||
|
||||
|
@ -46,19 +46,16 @@ final class LambdaFormBuffer {
|
||||
private static final int F_TRANS = 0x10, F_OWNED = 0x03;
|
||||
|
||||
LambdaFormBuffer(LambdaForm lf) {
|
||||
this(lf.arity, lf.names, lf.result);
|
||||
this.arity = lf.arity;
|
||||
setNames(lf.names);
|
||||
int result = lf.result;
|
||||
if (result == LAST_RESULT) result = length - 1;
|
||||
if (result >= 0 && lf.names[result].type != V_TYPE)
|
||||
resultName = lf.names[result];
|
||||
debugName = lf.debugName;
|
||||
assert(lf.nameRefsAreLegal());
|
||||
}
|
||||
|
||||
private LambdaFormBuffer(int arity, Name[] names, int result) {
|
||||
this.arity = arity;
|
||||
setNames(names);
|
||||
if (result == LAST_RESULT) result = length - 1;
|
||||
if (result >= 0 && names[result].type != V_TYPE)
|
||||
resultName = names[result];
|
||||
}
|
||||
|
||||
private LambdaForm lambdaForm() {
|
||||
assert(!inTrans()); // need endEdit call to tidy things up
|
||||
return new LambdaForm(debugName, arity, nameArray(), resultIndex());
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Arrays;
|
||||
import static java.lang.invoke.LambdaForm.*;
|
||||
import static java.lang.invoke.LambdaForm.BasicType.*;
|
||||
@ -58,10 +59,9 @@ class LambdaFormEditor {
|
||||
* The sequence is unterminated, ending with an indefinite number of zero bytes.
|
||||
* Sequences that are simple (short enough and with small enough values) pack into a 64-bit long.
|
||||
*/
|
||||
private static final class Transform {
|
||||
private static final class Transform extends SoftReference<LambdaForm> {
|
||||
final long packedBytes;
|
||||
final byte[] fullBytes;
|
||||
final LambdaForm result; // result of transform, or null, if there is none available
|
||||
|
||||
private enum Kind {
|
||||
NO_KIND, // necessary because ordinal must be greater than zero
|
||||
@ -140,9 +140,9 @@ class LambdaFormEditor {
|
||||
Kind kind() { return Kind.values()[byteAt(0)]; }
|
||||
|
||||
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
|
||||
super(result);
|
||||
this.packedBytes = packedBytes;
|
||||
this.fullBytes = fullBytes;
|
||||
this.result = result;
|
||||
}
|
||||
private Transform(long packedBytes) {
|
||||
this(packedBytes, null, null);
|
||||
@ -243,6 +243,7 @@ class LambdaFormEditor {
|
||||
buf.append("unpacked");
|
||||
buf.append(Arrays.toString(fullBytes));
|
||||
}
|
||||
LambdaForm result = get();
|
||||
if (result != null) {
|
||||
buf.append(" result=");
|
||||
buf.append(result);
|
||||
@ -253,7 +254,7 @@ class LambdaFormEditor {
|
||||
|
||||
/** Find a previously cached transform equivalent to the given one, and return its result. */
|
||||
private LambdaForm getInCache(Transform key) {
|
||||
assert(key.result == null);
|
||||
assert(key.get() == null);
|
||||
// The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap.
|
||||
Object c = lambdaForm.transformCache;
|
||||
Transform k = null;
|
||||
@ -276,7 +277,7 @@ class LambdaFormEditor {
|
||||
}
|
||||
}
|
||||
assert(k == null || key.equals(k));
|
||||
return k == null ? null : k.result;
|
||||
return (k != null) ? k.get() : null;
|
||||
}
|
||||
|
||||
/** Arbitrary but reasonable limits on Transform[] size for cache. */
|
||||
@ -293,7 +294,17 @@ class LambdaFormEditor {
|
||||
@SuppressWarnings("unchecked")
|
||||
ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
|
||||
Transform k = m.putIfAbsent(key, key);
|
||||
return k != null ? k.result : form;
|
||||
if (k == null) return form;
|
||||
LambdaForm result = k.get();
|
||||
if (result != null) {
|
||||
return result;
|
||||
} else {
|
||||
if (m.replace(key, k, key)) {
|
||||
return form;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(pass == 0);
|
||||
synchronized (lambdaForm) {
|
||||
@ -308,17 +319,27 @@ class LambdaFormEditor {
|
||||
if (c instanceof Transform) {
|
||||
Transform k = (Transform)c;
|
||||
if (k.equals(key)) {
|
||||
return k.result;
|
||||
LambdaForm result = k.get();
|
||||
if (result == null) {
|
||||
lambdaForm.transformCache = key;
|
||||
return form;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else if (k.get() == null) { // overwrite stale entry
|
||||
lambdaForm.transformCache = key;
|
||||
return form;
|
||||
}
|
||||
// expand one-element cache to small array
|
||||
ta = new Transform[MIN_CACHE_ARRAY_SIZE];
|
||||
ta[0] = k;
|
||||
lambdaForm.transformCache = c = ta;
|
||||
lambdaForm.transformCache = ta;
|
||||
} else {
|
||||
// it is already expanded
|
||||
ta = (Transform[])c;
|
||||
}
|
||||
int len = ta.length;
|
||||
int stale = -1;
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
Transform k = ta[i];
|
||||
@ -326,10 +347,18 @@ class LambdaFormEditor {
|
||||
break;
|
||||
}
|
||||
if (k.equals(key)) {
|
||||
return k.result;
|
||||
LambdaForm result = k.get();
|
||||
if (result == null) {
|
||||
ta[i] = key;
|
||||
return form;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else if (stale < 0 && k.get() == null) {
|
||||
stale = i; // remember 1st stale entry index
|
||||
}
|
||||
}
|
||||
if (i < len) {
|
||||
if (i < len || stale >= 0) {
|
||||
// just fall through to cache update
|
||||
} else if (len < MAX_CACHE_ARRAY_SIZE) {
|
||||
len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE);
|
||||
@ -344,7 +373,8 @@ class LambdaFormEditor {
|
||||
// The second iteration will update for this query, concurrently.
|
||||
continue;
|
||||
}
|
||||
ta[i] = key;
|
||||
int idx = (stale >= 0) ? stale : i;
|
||||
ta[idx] = key;
|
||||
return form;
|
||||
}
|
||||
}
|
||||
|
@ -26,9 +26,8 @@
|
||||
package java.lang.invoke;
|
||||
|
||||
import sun.invoke.util.Wrapper;
|
||||
import java.lang.ref.SoftReference;
|
||||
import static java.lang.invoke.MethodHandleStatics.*;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.*;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
|
||||
|
||||
/**
|
||||
* Shared information for a group of method types, which differ
|
||||
@ -51,7 +50,7 @@ final class MethodTypeForm {
|
||||
final MethodType basicType; // the canonical erasure, with primitives simplified
|
||||
|
||||
// Cached adapter information:
|
||||
@Stable final MethodHandle[] methodHandles;
|
||||
@Stable final SoftReference<MethodHandle>[] methodHandles;
|
||||
// Indexes into methodHandles:
|
||||
static final int
|
||||
MH_BASIC_INV = 0, // cached instance of MH.invokeBasic
|
||||
@ -60,7 +59,7 @@ final class MethodTypeForm {
|
||||
MH_LIMIT = 3;
|
||||
|
||||
// Cached lambda form information, for basic types only:
|
||||
final @Stable LambdaForm[] lambdaForms;
|
||||
final @Stable SoftReference<LambdaForm>[] lambdaForms;
|
||||
// Indexes into lambdaForms:
|
||||
static final int
|
||||
LF_INVVIRTUAL = 0, // DMH invokeVirtual
|
||||
@ -108,26 +107,40 @@ final class MethodTypeForm {
|
||||
|
||||
public MethodHandle cachedMethodHandle(int which) {
|
||||
assert(assertIsBasicType());
|
||||
return methodHandles[which];
|
||||
SoftReference<MethodHandle> entry = methodHandles[which];
|
||||
return (entry != null) ? entry.get() : null;
|
||||
}
|
||||
|
||||
synchronized public MethodHandle setCachedMethodHandle(int which, MethodHandle mh) {
|
||||
// Simulate a CAS, to avoid racy duplication of results.
|
||||
MethodHandle prev = methodHandles[which];
|
||||
if (prev != null) return prev;
|
||||
return methodHandles[which] = mh;
|
||||
SoftReference<MethodHandle> entry = methodHandles[which];
|
||||
if (entry != null) {
|
||||
MethodHandle prev = entry.get();
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
methodHandles[which] = new SoftReference<>(mh);
|
||||
return mh;
|
||||
}
|
||||
|
||||
public LambdaForm cachedLambdaForm(int which) {
|
||||
assert(assertIsBasicType());
|
||||
return lambdaForms[which];
|
||||
SoftReference<LambdaForm> entry = lambdaForms[which];
|
||||
return (entry != null) ? entry.get() : null;
|
||||
}
|
||||
|
||||
synchronized public LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
|
||||
// Simulate a CAS, to avoid racy duplication of results.
|
||||
LambdaForm prev = lambdaForms[which];
|
||||
if (prev != null) return prev;
|
||||
return lambdaForms[which] = form;
|
||||
SoftReference<LambdaForm> entry = lambdaForms[which];
|
||||
if (entry != null) {
|
||||
LambdaForm prev = entry.get();
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
lambdaForms[which] = new SoftReference<>(form);
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,6 +148,7 @@ final class MethodTypeForm {
|
||||
* This MTF will stand for that type and all un-erased variations.
|
||||
* Eagerly compute some basic properties of the type, common to all variations.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
protected MethodTypeForm(MethodType erasedType) {
|
||||
this.erasedType = erasedType;
|
||||
|
||||
@ -234,8 +248,8 @@ final class MethodTypeForm {
|
||||
|
||||
// Initialize caches, but only for basic types
|
||||
assert(basicType == erasedType);
|
||||
this.lambdaForms = new LambdaForm[LF_LIMIT];
|
||||
this.methodHandles = new MethodHandle[MH_LIMIT];
|
||||
this.lambdaForms = new SoftReference[LF_LIMIT];
|
||||
this.methodHandles = new SoftReference[MH_LIMIT];
|
||||
}
|
||||
|
||||
private static long pack(int a, int b, int c, int d) {
|
||||
@ -409,5 +423,4 @@ final class MethodTypeForm {
|
||||
public String toString() {
|
||||
return "Form"+erasedType;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,12 +63,17 @@ public abstract class LFCachingTestCase extends LambdaFormTestCase {
|
||||
}
|
||||
|
||||
if (lambdaForm0 != lambdaForm1) {
|
||||
System.err.println("Lambda form 0 toString is:");
|
||||
System.err.println(lambdaForm0);
|
||||
System.err.println("Lambda form 1 toString is:");
|
||||
System.err.println(lambdaForm1);
|
||||
throw new AssertionError("Error: Lambda forms of the two method handles"
|
||||
+ " are not the same. LF cahing does not work");
|
||||
// Since LambdaForm caches are based on SoftReferences, GC can cause element eviction.
|
||||
if (noGCHappened()) {
|
||||
System.err.println("Lambda form 0 toString is:");
|
||||
System.err.println(lambdaForm0);
|
||||
System.err.println("Lambda form 1 toString is:");
|
||||
System.err.println(lambdaForm1);
|
||||
throw new AssertionError("Error: Lambda forms of the two method handles"
|
||||
+ " are not the same. LF cahing does not work");
|
||||
} else {
|
||||
System.err.println("LambdaForms differ, but there was a GC in between. Ignore the failure.");
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException |
|
||||
SecurityException | InvocationTargetException ex) {
|
||||
|
@ -23,9 +23,12 @@
|
||||
|
||||
import com.oracle.testlibrary.jsr292.Helper;
|
||||
import com.sun.management.HotSpotDiagnosticMXBean;
|
||||
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import jdk.testlibrary.Utils;
|
||||
|
||||
@ -49,6 +52,11 @@ public abstract class LambdaFormTestCase {
|
||||
* used to get a lambda form from a method handle.
|
||||
*/
|
||||
protected final static Method INTERNAL_FORM;
|
||||
private static final List<GarbageCollectorMXBean> gcInfo;
|
||||
|
||||
private static long gcCount() {
|
||||
return gcInfo.stream().mapToLong(GarbageCollectorMXBean::getCollectionCount).sum();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
@ -58,10 +66,15 @@ public abstract class LambdaFormTestCase {
|
||||
} catch (Exception ex) {
|
||||
throw new Error("Unexpected exception: ", ex);
|
||||
}
|
||||
|
||||
gcInfo = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
if (gcInfo.size() == 0) {
|
||||
throw new Error("No GarbageCollectorMXBeans found.");
|
||||
}
|
||||
}
|
||||
|
||||
private final TestMethods testMethod;
|
||||
|
||||
private long gcCountAtStart;
|
||||
/**
|
||||
* Test case constructor. Generates test cases with random method types for
|
||||
* given methods form {@code j.l.i.MethodHandles} class.
|
||||
@ -71,12 +84,17 @@ public abstract class LambdaFormTestCase {
|
||||
*/
|
||||
protected LambdaFormTestCase(TestMethods testMethod) {
|
||||
this.testMethod = testMethod;
|
||||
this.gcCountAtStart = gcCount();
|
||||
}
|
||||
|
||||
public TestMethods getTestMethod() {
|
||||
return testMethod;
|
||||
}
|
||||
|
||||
protected boolean noGCHappened() {
|
||||
return gcCount() == gcCountAtStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routine that executes a test case.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user