提交 54a226fd 编写于 作者: V vlivanov

8057020: LambdaForm caches should support eviction

Reviewed-by: psandoz, jrose, shade
上级 3dbb2e55
...@@ -125,7 +125,7 @@ class LambdaForm { ...@@ -125,7 +125,7 @@ class LambdaForm {
MemberName vmentry; // low-level behavior, or null if not yet prepared MemberName vmentry; // low-level behavior, or null if not yet prepared
private boolean isCompiled; private boolean isCompiled;
Object transformCache; // managed by LambdaFormEditor volatile Object transformCache; // managed by LambdaFormEditor
public static final int VOID_RESULT = -1, LAST_RESULT = -2; public static final int VOID_RESULT = -1, LAST_RESULT = -2;
......
...@@ -46,19 +46,16 @@ final class LambdaFormBuffer { ...@@ -46,19 +46,16 @@ final class LambdaFormBuffer {
private static final int F_TRANS = 0x10, F_OWNED = 0x03; private static final int F_TRANS = 0x10, F_OWNED = 0x03;
LambdaFormBuffer(LambdaForm lf) { 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; debugName = lf.debugName;
assert(lf.nameRefsAreLegal()); 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() { private LambdaForm lambdaForm() {
assert(!inTrans()); // need endEdit call to tidy things up assert(!inTrans()); // need endEdit call to tidy things up
return new LambdaForm(debugName, arity, nameArray(), resultIndex()); return new LambdaForm(debugName, arity, nameArray(), resultIndex());
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
package java.lang.invoke; package java.lang.invoke;
import java.lang.ref.SoftReference;
import java.util.Arrays; import java.util.Arrays;
import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.LambdaForm.BasicType.*;
...@@ -58,10 +59,9 @@ class LambdaFormEditor { ...@@ -58,10 +59,9 @@ class LambdaFormEditor {
* The sequence is unterminated, ending with an indefinite number of zero bytes. * 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. * 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 long packedBytes;
final byte[] fullBytes; final byte[] fullBytes;
final LambdaForm result; // result of transform, or null, if there is none available
private enum Kind { private enum Kind {
NO_KIND, // necessary because ordinal must be greater than zero NO_KIND, // necessary because ordinal must be greater than zero
...@@ -140,9 +140,9 @@ class LambdaFormEditor { ...@@ -140,9 +140,9 @@ class LambdaFormEditor {
Kind kind() { return Kind.values()[byteAt(0)]; } Kind kind() { return Kind.values()[byteAt(0)]; }
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) { private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
super(result);
this.packedBytes = packedBytes; this.packedBytes = packedBytes;
this.fullBytes = fullBytes; this.fullBytes = fullBytes;
this.result = result;
} }
private Transform(long packedBytes) { private Transform(long packedBytes) {
this(packedBytes, null, null); this(packedBytes, null, null);
...@@ -243,6 +243,7 @@ class LambdaFormEditor { ...@@ -243,6 +243,7 @@ class LambdaFormEditor {
buf.append("unpacked"); buf.append("unpacked");
buf.append(Arrays.toString(fullBytes)); buf.append(Arrays.toString(fullBytes));
} }
LambdaForm result = get();
if (result != null) { if (result != null) {
buf.append(" result="); buf.append(" result=");
buf.append(result); buf.append(result);
...@@ -253,7 +254,7 @@ class LambdaFormEditor { ...@@ -253,7 +254,7 @@ class LambdaFormEditor {
/** Find a previously cached transform equivalent to the given one, and return its result. */ /** Find a previously cached transform equivalent to the given one, and return its result. */
private LambdaForm getInCache(Transform key) { private LambdaForm getInCache(Transform key) {
assert(key.result == null); assert(key.get() == null);
// The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap. // The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap.
Object c = lambdaForm.transformCache; Object c = lambdaForm.transformCache;
Transform k = null; Transform k = null;
...@@ -276,7 +277,7 @@ class LambdaFormEditor { ...@@ -276,7 +277,7 @@ class LambdaFormEditor {
} }
} }
assert(k == null || key.equals(k)); 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. */ /** Arbitrary but reasonable limits on Transform[] size for cache. */
...@@ -293,7 +294,17 @@ class LambdaFormEditor { ...@@ -293,7 +294,17 @@ class LambdaFormEditor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c; ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
Transform k = m.putIfAbsent(key, key); 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); assert(pass == 0);
synchronized (lambdaForm) { synchronized (lambdaForm) {
...@@ -308,17 +319,27 @@ class LambdaFormEditor { ...@@ -308,17 +319,27 @@ class LambdaFormEditor {
if (c instanceof Transform) { if (c instanceof Transform) {
Transform k = (Transform)c; Transform k = (Transform)c;
if (k.equals(key)) { 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 // expand one-element cache to small array
ta = new Transform[MIN_CACHE_ARRAY_SIZE]; ta = new Transform[MIN_CACHE_ARRAY_SIZE];
ta[0] = k; ta[0] = k;
lambdaForm.transformCache = c = ta; lambdaForm.transformCache = ta;
} else { } else {
// it is already expanded // it is already expanded
ta = (Transform[])c; ta = (Transform[])c;
} }
int len = ta.length; int len = ta.length;
int stale = -1;
int i; int i;
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
Transform k = ta[i]; Transform k = ta[i];
...@@ -326,10 +347,18 @@ class LambdaFormEditor { ...@@ -326,10 +347,18 @@ class LambdaFormEditor {
break; break;
} }
if (k.equals(key)) { 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 // just fall through to cache update
} else if (len < MAX_CACHE_ARRAY_SIZE) { } else if (len < MAX_CACHE_ARRAY_SIZE) {
len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE); len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE);
...@@ -344,7 +373,8 @@ class LambdaFormEditor { ...@@ -344,7 +373,8 @@ class LambdaFormEditor {
// The second iteration will update for this query, concurrently. // The second iteration will update for this query, concurrently.
continue; continue;
} }
ta[i] = key; int idx = (stale >= 0) ? stale : i;
ta[idx] = key;
return form; return form;
} }
} }
......
...@@ -26,9 +26,8 @@ ...@@ -26,9 +26,8 @@
package java.lang.invoke; package java.lang.invoke;
import sun.invoke.util.Wrapper; import sun.invoke.util.Wrapper;
import java.lang.ref.SoftReference;
import static java.lang.invoke.MethodHandleStatics.*; 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 * Shared information for a group of method types, which differ
...@@ -51,7 +50,7 @@ final class MethodTypeForm { ...@@ -51,7 +50,7 @@ final class MethodTypeForm {
final MethodType basicType; // the canonical erasure, with primitives simplified final MethodType basicType; // the canonical erasure, with primitives simplified
// Cached adapter information: // Cached adapter information:
@Stable final MethodHandle[] methodHandles; @Stable final SoftReference<MethodHandle>[] methodHandles;
// Indexes into methodHandles: // Indexes into methodHandles:
static final int static final int
MH_BASIC_INV = 0, // cached instance of MH.invokeBasic MH_BASIC_INV = 0, // cached instance of MH.invokeBasic
...@@ -60,7 +59,7 @@ final class MethodTypeForm { ...@@ -60,7 +59,7 @@ final class MethodTypeForm {
MH_LIMIT = 3; MH_LIMIT = 3;
// Cached lambda form information, for basic types only: // Cached lambda form information, for basic types only:
final @Stable LambdaForm[] lambdaForms; final @Stable SoftReference<LambdaForm>[] lambdaForms;
// Indexes into lambdaForms: // Indexes into lambdaForms:
static final int static final int
LF_INVVIRTUAL = 0, // DMH invokeVirtual LF_INVVIRTUAL = 0, // DMH invokeVirtual
...@@ -108,26 +107,40 @@ final class MethodTypeForm { ...@@ -108,26 +107,40 @@ final class MethodTypeForm {
public MethodHandle cachedMethodHandle(int which) { public MethodHandle cachedMethodHandle(int which) {
assert(assertIsBasicType()); assert(assertIsBasicType());
return methodHandles[which]; SoftReference<MethodHandle> entry = methodHandles[which];
return (entry != null) ? entry.get() : null;
} }
synchronized public MethodHandle setCachedMethodHandle(int which, MethodHandle mh) { synchronized public MethodHandle setCachedMethodHandle(int which, MethodHandle mh) {
// Simulate a CAS, to avoid racy duplication of results. // Simulate a CAS, to avoid racy duplication of results.
MethodHandle prev = methodHandles[which]; SoftReference<MethodHandle> entry = methodHandles[which];
if (prev != null) return prev; if (entry != null) {
return methodHandles[which] = mh; MethodHandle prev = entry.get();
if (prev != null) {
return prev;
}
}
methodHandles[which] = new SoftReference<>(mh);
return mh;
} }
public LambdaForm cachedLambdaForm(int which) { public LambdaForm cachedLambdaForm(int which) {
assert(assertIsBasicType()); assert(assertIsBasicType());
return lambdaForms[which]; SoftReference<LambdaForm> entry = lambdaForms[which];
return (entry != null) ? entry.get() : null;
} }
synchronized public LambdaForm setCachedLambdaForm(int which, LambdaForm form) { synchronized public LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
// Simulate a CAS, to avoid racy duplication of results. // Simulate a CAS, to avoid racy duplication of results.
LambdaForm prev = lambdaForms[which]; SoftReference<LambdaForm> entry = lambdaForms[which];
if (prev != null) return prev; if (entry != null) {
return lambdaForms[which] = form; LambdaForm prev = entry.get();
if (prev != null) {
return prev;
}
}
lambdaForms[which] = new SoftReference<>(form);
return form;
} }
/** /**
...@@ -135,6 +148,7 @@ final class MethodTypeForm { ...@@ -135,6 +148,7 @@ final class MethodTypeForm {
* This MTF will stand for that type and all un-erased variations. * This MTF will stand for that type and all un-erased variations.
* Eagerly compute some basic properties of the type, common to all variations. * Eagerly compute some basic properties of the type, common to all variations.
*/ */
@SuppressWarnings({"rawtypes", "unchecked"})
protected MethodTypeForm(MethodType erasedType) { protected MethodTypeForm(MethodType erasedType) {
this.erasedType = erasedType; this.erasedType = erasedType;
...@@ -234,8 +248,8 @@ final class MethodTypeForm { ...@@ -234,8 +248,8 @@ final class MethodTypeForm {
// Initialize caches, but only for basic types // Initialize caches, but only for basic types
assert(basicType == erasedType); assert(basicType == erasedType);
this.lambdaForms = new LambdaForm[LF_LIMIT]; this.lambdaForms = new SoftReference[LF_LIMIT];
this.methodHandles = new MethodHandle[MH_LIMIT]; this.methodHandles = new SoftReference[MH_LIMIT];
} }
private static long pack(int a, int b, int c, int d) { private static long pack(int a, int b, int c, int d) {
...@@ -409,5 +423,4 @@ final class MethodTypeForm { ...@@ -409,5 +423,4 @@ final class MethodTypeForm {
public String toString() { public String toString() {
return "Form"+erasedType; return "Form"+erasedType;
} }
} }
...@@ -63,12 +63,17 @@ public abstract class LFCachingTestCase extends LambdaFormTestCase { ...@@ -63,12 +63,17 @@ public abstract class LFCachingTestCase extends LambdaFormTestCase {
} }
if (lambdaForm0 != lambdaForm1) { if (lambdaForm0 != lambdaForm1) {
System.err.println("Lambda form 0 toString is:"); // Since LambdaForm caches are based on SoftReferences, GC can cause element eviction.
System.err.println(lambdaForm0); if (noGCHappened()) {
System.err.println("Lambda form 1 toString is:"); System.err.println("Lambda form 0 toString is:");
System.err.println(lambdaForm1); System.err.println(lambdaForm0);
throw new AssertionError("Error: Lambda forms of the two method handles" System.err.println("Lambda form 1 toString is:");
+ " are not the same. LF cahing does not work"); 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 | } catch (IllegalAccessException | IllegalArgumentException |
SecurityException | InvocationTargetException ex) { SecurityException | InvocationTargetException ex) {
......
...@@ -23,9 +23,12 @@ ...@@ -23,9 +23,12 @@
import com.oracle.testlibrary.jsr292.Helper; import com.oracle.testlibrary.jsr292.Helper;
import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.HotSpotDiagnosticMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import jdk.testlibrary.Utils; import jdk.testlibrary.Utils;
import jdk.testlibrary.TimeLimitedRunner; import jdk.testlibrary.TimeLimitedRunner;
...@@ -50,6 +53,11 @@ public abstract class LambdaFormTestCase { ...@@ -50,6 +53,11 @@ public abstract class LambdaFormTestCase {
* used to get a lambda form from a method handle. * used to get a lambda form from a method handle.
*/ */
protected final static Method INTERNAL_FORM; protected final static Method INTERNAL_FORM;
private static final List<GarbageCollectorMXBean> gcInfo;
private static long gcCount() {
return gcInfo.stream().mapToLong(GarbageCollectorMXBean::getCollectionCount).sum();
}
static { static {
try { try {
...@@ -59,6 +67,11 @@ public abstract class LambdaFormTestCase { ...@@ -59,6 +67,11 @@ public abstract class LambdaFormTestCase {
} catch (Exception ex) { } catch (Exception ex) {
throw new Error("Unexpected 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 final TestMethods testMethod;
...@@ -67,6 +80,7 @@ public abstract class LambdaFormTestCase { ...@@ -67,6 +80,7 @@ public abstract class LambdaFormTestCase {
private static boolean passed = true; private static boolean passed = true;
private static int testCounter = 0; private static int testCounter = 0;
private static int failCounter = 0; private static int failCounter = 0;
private long gcCountAtStart;
/** /**
* Test case constructor. Generates test cases with random method types for * Test case constructor. Generates test cases with random method types for
...@@ -77,12 +91,17 @@ public abstract class LambdaFormTestCase { ...@@ -77,12 +91,17 @@ public abstract class LambdaFormTestCase {
*/ */
protected LambdaFormTestCase(TestMethods testMethod) { protected LambdaFormTestCase(TestMethods testMethod) {
this.testMethod = testMethod; this.testMethod = testMethod;
this.gcCountAtStart = gcCount();
} }
public TestMethods getTestMethod() { public TestMethods getTestMethod() {
return testMethod; return testMethod;
} }
protected boolean noGCHappened() {
return gcCount() == gcCountAtStart;
}
/** /**
* Routine that executes a test case. * Routine that executes a test case.
*/ */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册