--- old/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java 2014-09-10 17:04:10.604832252 +0200 +++ new/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java 2014-09-10 17:04:10.527833657 +0200 @@ -25,6 +25,7 @@ package java.lang.invoke; +import java.lang.ref.WeakReference; import java.util.Arrays; import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.LambdaForm.BasicType.*; @@ -33,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap; import sun.invoke.util.Wrapper; +import sun.misc.Unsafe; /** Transforms on LFs. * A lambda-form editor can derive new LFs from its base LF. @@ -58,10 +60,9 @@ * 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 WeakReference { 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 +141,9 @@ 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 +244,7 @@ buf.append("unpacked"); buf.append(Arrays.toString(fullBytes)); } + LambdaForm result = get(); if (result != null) { buf.append(" result="); buf.append(result); @@ -253,7 +255,7 @@ /** 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; @@ -270,13 +272,13 @@ } else { Transform[] ta = (Transform[])c; for (int i = 0; i < ta.length; i++) { - Transform t = ta[i]; + Transform t = get(ta, i); if (t == null) break; if (t.equals(key)) { k = t; break; } } } assert(k == null || key.equals(k)); - return k == null ? null : k.result; + return k == null ? null : k.get(); } /** Arbitrary but reasonable limits on Transform[] size for cache. */ @@ -287,13 +289,23 @@ */ private LambdaForm putInCache(Transform key, LambdaForm form) { key = key.withResult(form); + Object c = lambdaForm.transformCache; for (int pass = 0; ; pass++) { - Object c = lambdaForm.transformCache; if (c instanceof ConcurrentHashMap) { @SuppressWarnings("unchecked") ConcurrentHashMap m = (ConcurrentHashMap) 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) { + if (m.replace(key, k, key)) { + return form; + } else { + continue; + } + } else { + return result; + } } assert(pass == 0); synchronized (lambdaForm) { @@ -308,17 +320,27 @@ 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; + ta[0] = k; // no need for volatile write here since it's new + lambdaForm.transformCache = ta; // and published via volatile } 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 +348,18 @@ break; } if (k.equals(key)) { - return k.result; + LambdaForm result = k.get(); + if (result == null) { + lazySet(ta, i, key); // just order is important + return form; + } else { + return result; + } + } else if (stale < 0 && k.get() == null) { + stale = i; // remember 1st stale entry index } } - if (i < len) { + if (stale >= 0 || i < len) { // just fall through to cache update } else if (len < MAX_CACHE_ARRAY_SIZE) { len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE); @@ -338,14 +368,13 @@ } else { ConcurrentHashMap m = new ConcurrentHashMap<>(MAX_CACHE_ARRAY_SIZE * 2); for (Transform k : ta) { - if (k == null) break; m.put(k, k); } - lambdaForm.transformCache = m; + lambdaForm.transformCache = c = m; // The second iteration will update for this query, concurrently. continue; } - ta[i] = key; + lazySet(ta, stale >= 0 ? stale : i, key); return form; } } @@ -812,7 +841,7 @@ } } - form = new LambdaForm(lambdaForm.debugName, arity2, names2, result2); + form = new LambdaForm(lambdaForm, lambdaForm.debugName, arity2, names2, result2); return putInCache(key, form); } @@ -823,4 +852,42 @@ } return true; } + + // Unsafe mechanics for volatile array elements + + private static final Unsafe unsafe; + private static final int base; + private static final int shift; + + static { + try { + unsafe = Unsafe.getUnsafe(); + base = unsafe.arrayBaseOffset(Object[].class); + int scale = unsafe.arrayIndexScale(Object[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + shift = 31 - Integer.numberOfLeadingZeros(scale); + } catch (Exception e) { + throw new Error(e); + } + } + + @SuppressWarnings("unchecked") + private static E get(E[] array, int i) { + return (E) unsafe.getObjectVolatile(array, checkedByteOffset(array, i)); + } + + private static void set(E[] array, int i, E newValue) { + unsafe.putObjectVolatile(array, checkedByteOffset(array, i), newValue); + } + + private static void lazySet(E[] array, int i, E newValue) { + unsafe.putOrderedObject(array, checkedByteOffset(array, i), newValue); + } + + private static long checkedByteOffset(Object[] array, int i) { + if (i < 0 || i >= array.length) + throw new IndexOutOfBoundsException("index " + i); + return ((long) i << shift) + base; + } }