Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1467496) +++ lucene/CHANGES.txt (working copy) @@ -216,6 +216,11 @@ * LUCENE-4926: Speed up DisjunctionMatchQuery. (Robert Muir) +* LUCENE-4930: Reduce contention in older/buggy JVMs when using + AttributeSource#addAttribute() because java.lang.ref.ReferenceQueue#poll() + is implemented using synchronization. (Christian Ziech, Karl Wright, + Uwe Schindler) + API Changes * LUCENE-4844: removed TaxonomyReader.getParent(), you should use Index: lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java =================================================================== --- lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java (revision 1467496) +++ lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java (working copy) @@ -59,7 +59,7 @@ this.length = length; this.chunkSizePower = chunkSizePower; this.chunkSizeMask = (1L << chunkSizePower) - 1L; - this.clones = trackClones ? WeakIdentityMap.newConcurrentHashMap() : null; + this.clones = trackClones ? WeakIdentityMap.newConcurrentHashMap(true) : null; assert chunkSizePower >= 0 && chunkSizePower <= 30; assert (length >>> chunkSizePower) < Integer.MAX_VALUE; Index: lucene/core/src/java/org/apache/lucene/util/AttributeSource.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/AttributeSource.java (revision 1467496) +++ lucene/core/src/java/org/apache/lucene/util/AttributeSource.java (working copy) @@ -55,9 +55,9 @@ private static final class DefaultAttributeFactory extends AttributeFactory { private static final WeakIdentityMap, WeakReference>> attClassImplMap = - WeakIdentityMap.newConcurrentHashMap(); + WeakIdentityMap.newConcurrentHashMap(false); - private DefaultAttributeFactory() {} + DefaultAttributeFactory() {} @Override public AttributeImpl createAttributeInstance(Class attClass) { @@ -201,7 +201,7 @@ /** a cache that stores all interfaces for known implementation classes for performance (slow reflection) */ private static final WeakIdentityMap,LinkedList>>> knownImplClasses = - WeakIdentityMap.newConcurrentHashMap(); + WeakIdentityMap.newConcurrentHashMap(false); static LinkedList>> getAttributeInterfaces(final Class clazz) { LinkedList>> foundInterfaces = knownImplClasses.get(clazz); Index: lucene/core/src/java/org/apache/lucene/util/VirtualMethod.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/VirtualMethod.java (revision 1467496) +++ lucene/core/src/java/org/apache/lucene/util/VirtualMethod.java (working copy) @@ -63,7 +63,7 @@ private final Class baseClass; private final String method; private final Class[] parameters; - private final WeakIdentityMap, Integer> cache = WeakIdentityMap.newConcurrentHashMap(); + private final WeakIdentityMap, Integer> cache = WeakIdentityMap.newConcurrentHashMap(false); /** * Creates a new instance for the given {@code baseClass} and method declaration. Index: lucene/core/src/java/org/apache/lucene/util/WeakIdentityMap.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/WeakIdentityMap.java (revision 1467496) +++ lucene/core/src/java/org/apache/lucene/util/WeakIdentityMap.java (working copy) @@ -44,25 +44,45 @@ * if not implemented carefully. The map only contains {@link Iterator} implementations * on the values and not-GCed keys. Lucene's implementation also supports {@code null} * keys, but those are never weak! + * + *

The map supports two modes of operation: + *

* * @lucene.internal */ public final class WeakIdentityMap { private final ReferenceQueue queue = new ReferenceQueue(); private final Map backingStore; + private final boolean reapOnRead; - /** Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}. */ - public static final WeakIdentityMap newHashMap() { - return new WeakIdentityMap(new HashMap()); + /** Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}. + * @param reapOnRead controls if the map cleans up the reference queue on every read operation. + */ + public static final WeakIdentityMap newHashMap(boolean reapOnRead) { + return new WeakIdentityMap(new HashMap(), reapOnRead); } - /** Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}. */ - public static final WeakIdentityMap newConcurrentHashMap() { - return new WeakIdentityMap(new ConcurrentHashMap()); + /** Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}. + * @param reapOnRead controls if the map cleans up the reference queue on every read operation. + */ + public static final WeakIdentityMap newConcurrentHashMap(boolean reapOnRead) { + return new WeakIdentityMap(new ConcurrentHashMap(), reapOnRead); } - private WeakIdentityMap(Map backingStore) { + private WeakIdentityMap(Map backingStore, boolean reapOnRead) { this.backingStore = backingStore; + this.reapOnRead = reapOnRead; } /** Removes all of the mappings from this map. */ @@ -73,13 +93,13 @@ /** Returns {@code true} if this map contains a mapping for the specified key. */ public boolean containsKey(Object key) { - reap(); + if (reapOnRead) reap(); return backingStore.containsKey(new IdentityWeakReference(key, null)); } /** Returns the value to which the specified key is mapped. */ public V get(Object key) { - reap(); + if (reapOnRead) reap(); return backingStore.get(new IdentityWeakReference(key, null)); } @@ -113,7 +133,7 @@ public int size() { if (backingStore.isEmpty()) return 0; - reap(); + if (reapOnRead) reap(); return backingStore.size(); } @@ -178,13 +198,21 @@ /** Returns an iterator over all values of this map. * This iterator may return values whose key is already - * garbage collected while iterator is consumed. */ + * garbage collected while iterator is consumed, + * especially if {@code reapOnRead} is {@code false}. */ public Iterator valueIterator() { - reap(); + if (reapOnRead) reap(); return backingStore.values().iterator(); } - private void reap() { + /** + * This method manually cleans up the reference queue to remove all garbage + * collected key/value pairs from the map. Calling this method is not needed + * if {@code reapOnRead = true}. Otherwise it might be a good idea + * to call this method when there is spare time (e.g. from a background thread). + * @see Information about the reapOnRead setting + */ + public void reap() { Reference zombie; while ((zombie = queue.poll()) != null) { backingStore.remove(zombie); Index: lucene/core/src/test/org/apache/lucene/util/TestWeakIdentityMap.java =================================================================== --- lucene/core/src/test/org/apache/lucene/util/TestWeakIdentityMap.java (revision 1467496) +++ lucene/core/src/test/org/apache/lucene/util/TestWeakIdentityMap.java (working copy) @@ -18,7 +18,6 @@ package org.apache.lucene.util; import java.util.Iterator; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Random; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -30,7 +29,7 @@ public void testSimpleHashMap() { final WeakIdentityMap map = - WeakIdentityMap.newHashMap(); + WeakIdentityMap.newHashMap(random().nextBoolean()); // we keep strong references to the keys, // so WeakIdentityMap will not forget about them: String key1 = new String("foo"); @@ -165,7 +164,7 @@ final int threadCount = 8, keyCount = 1024; final ExecutorService exec = Executors.newFixedThreadPool(threadCount, new NamedThreadFactory("testConcurrentHashMap")); final WeakIdentityMap map = - WeakIdentityMap.newConcurrentHashMap(); + WeakIdentityMap.newConcurrentHashMap(random().nextBoolean()); // we keep strong references to the keys, // so WeakIdentityMap will not forget about them: final AtomicReferenceArray keys = new AtomicReferenceArray(keyCount);