kvn@1334: /* kvn@1334: * Copyright 2009 Goldman Sachs International. All Rights Reserved. kvn@1334: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. kvn@1334: * kvn@1334: * This code is free software; you can redistribute it and/or modify it kvn@1334: * under the terms of the GNU General Public License version 2 only, as kvn@1334: * published by the Free Software Foundation. kvn@1334: * kvn@1334: * This code is distributed in the hope that it will be useful, but WITHOUT kvn@1334: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or kvn@1334: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License kvn@1334: * version 2 for more details (a copy is included in the LICENSE file that kvn@1334: * accompanied this code). kvn@1334: * kvn@1334: * You should have received a copy of the GNU General Public License version kvn@1334: * 2 along with this work; if not, write to the Free Software Foundation, kvn@1334: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. kvn@1334: * trims@1907: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA trims@1907: * or visit www.oracle.com if you need additional information or have any trims@1907: * questions. kvn@1334: * kvn@1334: */ kvn@1334: kvn@1334: /* kvn@1334: * @test kvn@1334: * @bug 6865031 kvn@1334: * @summary Application gives bad result (throws bad exception) with compressed oops kvn@1391: * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UseCompressedOops -XX:HeapBaseMinAddress=32g -XX:-LoopUnswitching -XX:CompileCommand=inline,AbstractMemoryEfficientList.equals Test hello goodbye kvn@1334: */ kvn@1334: kvn@1334: import java.lang.ref.ReferenceQueue; kvn@1334: import java.lang.ref.WeakReference; kvn@1334: import java.util.ArrayList; kvn@1334: import java.util.Arrays; kvn@1334: import java.util.List; kvn@1334: kvn@1334: interface MyList { kvn@1334: public int size(); kvn@1334: public Object set(final int index, final Object element); kvn@1334: public Object get(final int index); kvn@1334: } kvn@1334: kvn@1334: abstract class AbstractMemoryEfficientList implements MyList { kvn@1334: abstract public int size(); kvn@1334: abstract public Object get(final int index); kvn@1334: abstract public Object set(final int index, final Object element); kvn@1334: kvn@1334: public boolean equals(Object o) { kvn@1334: if (o == this) { kvn@1334: return true; kvn@1334: } kvn@1334: kvn@1334: if (!(o instanceof MyList)) { kvn@1334: return false; kvn@1334: } kvn@1334: kvn@1334: final MyList that = (MyList) o; kvn@1334: if (this.size() != that.size()) { kvn@1334: return false; kvn@1334: } kvn@1334: kvn@1334: for (int i = 0; i < this.size(); i++) { kvn@1334: try { kvn@1334: if (!((this.get(i)).equals(that.get(i)))) { kvn@1334: return false; kvn@1334: } kvn@1334: } catch (IndexOutOfBoundsException e) { kvn@1334: System.out.println("THROWING RT EXC"); kvn@1334: System.out.println("concurrent modification of this:" + this.getClass() + ":" + System.identityHashCode(this) + "; that:" + that.getClass() + ":" + System.identityHashCode(that) + "; i:" + i); kvn@1334: e.printStackTrace(); kvn@1334: System.exit(97); kvn@1334: throw new RuntimeException("concurrent modification of this:" + this.getClass() + ":" + System.identityHashCode(this) + "; that:" + that.getClass() + ":" + System.identityHashCode(that) + "; i:" + i, e); kvn@1334: } kvn@1334: } kvn@1334: return true; kvn@1334: } kvn@1334: kvn@1334: public int hashCode() { kvn@1334: int hashCode = 1; kvn@1334: for (int i = 0; i < this.size(); i++) { kvn@1334: Object obj = this.get(i); kvn@1334: hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); kvn@1334: } kvn@1334: return hashCode; kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: final class SingletonList extends AbstractMemoryEfficientList { kvn@1334: private Object element1; kvn@1334: kvn@1334: SingletonList(final Object obj1) { kvn@1334: super(); kvn@1334: this.element1 = obj1; kvn@1334: } kvn@1334: kvn@1334: public int size() { kvn@1334: return 1; kvn@1334: } kvn@1334: kvn@1334: public Object get(final int index) { kvn@1334: if (index == 0) { kvn@1334: return this.element1; kvn@1334: } else { kvn@1334: throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: public Object set(final int index, final Object element) { kvn@1334: if (index == 0) { kvn@1334: final Object previousElement = this.element1; kvn@1334: this.element1 = element; kvn@1334: return previousElement; kvn@1334: } else { kvn@1334: throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); kvn@1334: } kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: final class DoubletonList extends AbstractMemoryEfficientList { kvn@1334: private Object element1; kvn@1334: private Object element2; kvn@1334: kvn@1334: DoubletonList(final Object obj1, final Object obj2) { kvn@1334: this.element1 = obj1; kvn@1334: this.element2 = obj2; kvn@1334: } kvn@1334: kvn@1334: public int size() { kvn@1334: return 2; kvn@1334: } kvn@1334: kvn@1334: public Object get(final int index) { kvn@1334: switch (index) { kvn@1334: case 0 : return this.element1; kvn@1334: case 1 : return this.element2; kvn@1334: default: throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: public Object set(final int index, final Object element) { kvn@1334: switch (index) { kvn@1334: case 0 : kvn@1334: { kvn@1334: final Object previousElement = this.element1; kvn@1334: this.element1 = element; kvn@1334: return previousElement; kvn@1334: } kvn@1334: case 1 : kvn@1334: { kvn@1334: final Object previousElement = this.element2; kvn@1334: this.element2 = element; kvn@1334: return previousElement; kvn@1334: } kvn@1334: default : throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); kvn@1334: } kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: class WeakPool { kvn@1334: protected static final int DEFAULT_INITIAL_CAPACITY = 16; kvn@1334: private static final int MAXIMUM_CAPACITY = 1 << 30; kvn@1334: private static final float DEFAULT_LOAD_FACTOR = 0.75f; kvn@1334: kvn@1334: protected Entry[] table; kvn@1334: kvn@1334: private int size; kvn@1334: protected int threshold; kvn@1334: private final float loadFactor; kvn@1334: private final ReferenceQueue queue = new ReferenceQueue(); kvn@1334: kvn@1334: public WeakPool() kvn@1334: { kvn@1334: this.loadFactor = DEFAULT_LOAD_FACTOR; kvn@1334: threshold = DEFAULT_INITIAL_CAPACITY; kvn@1334: table = new Entry[DEFAULT_INITIAL_CAPACITY]; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Check for equality of non-null reference x and possibly-null y. By kvn@1334: * default uses Object.equals. kvn@1334: */ kvn@1334: private boolean eq(Object x, Object y) kvn@1334: { kvn@1334: return x == y || x.equals(y); kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Return index for hash code h. kvn@1334: */ kvn@1334: private int indexFor(int h, int length) kvn@1334: { kvn@1334: return h & length - 1; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Expunge stale entries from the table. kvn@1334: */ kvn@1334: private void expungeStaleEntries() kvn@1334: { kvn@1334: Object r; kvn@1334: while ((r = queue.poll()) != null) kvn@1334: { kvn@1334: Entry e = (Entry) r; kvn@1334: int h = e.hash; kvn@1334: int i = indexFor(h, table.length); kvn@1334: kvn@1334: // System.out.println("EXPUNGING " + h); kvn@1334: Entry prev = table[i]; kvn@1334: Entry p = prev; kvn@1334: while (p != null) kvn@1334: { kvn@1334: Entry next = p.next; kvn@1334: if (p == e) kvn@1334: { kvn@1334: if (prev == e) kvn@1334: { kvn@1334: table[i] = next; kvn@1334: } kvn@1334: else kvn@1334: { kvn@1334: prev.next = next; kvn@1334: } kvn@1334: e.next = null; // Help GC kvn@1334: size--; kvn@1334: break; kvn@1334: } kvn@1334: prev = p; kvn@1334: p = next; kvn@1334: } kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Return the table after first expunging stale entries kvn@1334: */ kvn@1334: private Entry[] getTable() kvn@1334: { kvn@1334: expungeStaleEntries(); kvn@1334: return table; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Returns the number of key-value mappings in this map. kvn@1334: * This result is a snapshot, and may not reflect unprocessed kvn@1334: * entries that will be removed before next attempted access kvn@1334: * because they are no longer referenced. kvn@1334: */ kvn@1334: public int size() kvn@1334: { kvn@1334: if (size == 0) kvn@1334: { kvn@1334: return 0; kvn@1334: } kvn@1334: expungeStaleEntries(); kvn@1334: return size; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Returns true if this map contains no key-value mappings. kvn@1334: * This result is a snapshot, and may not reflect unprocessed kvn@1334: * entries that will be removed before next attempted access kvn@1334: * because they are no longer referenced. kvn@1334: */ kvn@1334: public boolean isEmpty() kvn@1334: { kvn@1334: return size() == 0; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Returns the value stored in the pool that equals the requested key kvn@1334: * or null if the map contains no mapping for kvn@1334: * this key (or the key is null) kvn@1334: * kvn@1334: * @param key the key whose equals value is to be returned. kvn@1334: * @return the object that is equal the specified key, or kvn@1334: * null if key is null or no object in the pool equals the key. kvn@1334: */ kvn@1334: public V get(V key) kvn@1334: { kvn@1334: if (key == null) kvn@1334: { kvn@1334: return null; kvn@1334: } kvn@1334: int h = key.hashCode(); kvn@1334: Entry[] tab = getTable(); kvn@1334: int index = indexFor(h, tab.length); kvn@1334: Entry e = tab[index]; kvn@1334: while (e != null) kvn@1334: { kvn@1334: V candidate = e.get(); kvn@1334: if (e.hash == h && eq(key, candidate)) kvn@1334: { kvn@1334: return candidate; kvn@1334: } kvn@1334: e = e.next; kvn@1334: } kvn@1334: return null; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Returns the entry associated with the specified key in the HashMap. kvn@1334: * Returns null if the HashMap contains no mapping for this key. kvn@1334: */ kvn@1334: Entry getEntry(Object key) kvn@1334: { kvn@1334: int h = key.hashCode(); kvn@1334: Entry[] tab = getTable(); kvn@1334: int index = indexFor(h, tab.length); kvn@1334: Entry e = tab[index]; kvn@1334: while (e != null && !(e.hash == h && eq(key, e.get()))) kvn@1334: { kvn@1334: e = e.next; kvn@1334: } kvn@1334: return e; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Places the object into the pool. If the object is null, nothing happens. kvn@1334: * If an equal object already exists, it is not replaced. kvn@1334: * kvn@1334: * @param key the object to put into the pool. key may be null. kvn@1334: * @return the object in the pool that is equal to the key, or the newly placed key if no such object existed when put was called kvn@1334: */ kvn@1334: public V put(V key) kvn@1334: { kvn@1334: if (key == null) kvn@1334: { kvn@1334: return null; kvn@1334: } kvn@1334: int h = key.hashCode(); kvn@1334: Entry[] tab = getTable(); kvn@1334: int i = indexFor(h, tab.length); kvn@1334: kvn@1334: for (Entry e = tab[i]; e != null; e = e.next) kvn@1334: { kvn@1334: V candidate = e.get(); kvn@1334: if (h == e.hash && eq(key, candidate)) kvn@1334: { kvn@1334: return candidate; kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: tab[i] = new Entry(key, queue, h, tab[i]); kvn@1334: kvn@1334: if (++size >= threshold) kvn@1334: { kvn@1334: resize(tab.length * 2); kvn@1334: } kvn@1334: kvn@1334: // System.out.println("Added " + key + " to pool"); kvn@1334: return key; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Rehashes the contents of this map into a new array with a kvn@1334: * larger capacity. This method is called automatically when the kvn@1334: * number of keys in this map reaches its threshold. kvn@1334: *

kvn@1334: * If current capacity is MAXIMUM_CAPACITY, this method does not kvn@1334: * resize the map, but but sets threshold to Integer.MAX_VALUE. kvn@1334: * This has the effect of preventing future calls. kvn@1334: * kvn@1334: * @param newCapacity the new capacity, MUST be a power of two; kvn@1334: * must be greater than current capacity unless current kvn@1334: * capacity is MAXIMUM_CAPACITY (in which case value kvn@1334: * is irrelevant). kvn@1334: */ kvn@1334: void resize(int newCapacity) kvn@1334: { kvn@1334: Entry[] oldTable = getTable(); kvn@1334: int oldCapacity = oldTable.length; kvn@1334: if (oldCapacity == MAXIMUM_CAPACITY) kvn@1334: { kvn@1334: threshold = Integer.MAX_VALUE; kvn@1334: return; kvn@1334: } kvn@1334: kvn@1334: Entry[] newTable = new Entry[newCapacity]; kvn@1334: transfer(oldTable, newTable); kvn@1334: table = newTable; kvn@1334: kvn@1334: /* kvn@1334: * If ignoring null elements and processing ref queue caused massive kvn@1334: * shrinkage, then restore old table. This should be rare, but avoids kvn@1334: * unbounded expansion of garbage-filled tables. kvn@1334: */ kvn@1334: if (size >= threshold / 2) kvn@1334: { kvn@1334: threshold = (int) (newCapacity * loadFactor); kvn@1334: } kvn@1334: else kvn@1334: { kvn@1334: expungeStaleEntries(); kvn@1334: transfer(newTable, oldTable); kvn@1334: table = oldTable; kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Transfer all entries from src to dest tables kvn@1334: */ kvn@1334: private void transfer(Entry[] src, Entry[] dest) kvn@1334: { kvn@1334: for (int j = 0; j < src.length; ++j) kvn@1334: { kvn@1334: Entry e = src[j]; kvn@1334: src[j] = null; kvn@1334: while (e != null) kvn@1334: { kvn@1334: Entry next = e.next; kvn@1334: Object key = e.get(); kvn@1334: if (key == null) kvn@1334: { kvn@1334: e.next = null; // Help GC kvn@1334: size--; kvn@1334: } kvn@1334: else kvn@1334: { kvn@1334: int i = indexFor(e.hash, dest.length); kvn@1334: e.next = dest[i]; kvn@1334: dest[i] = e; kvn@1334: } kvn@1334: e = next; kvn@1334: } kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Removes the object in the pool that equals the key. kvn@1334: * kvn@1334: * @param key kvn@1334: * @return previous value associated with specified key, or null kvn@1334: * if there was no mapping for key or the key is null. kvn@1334: */ kvn@1334: public V removeFromPool(V key) kvn@1334: { kvn@1334: if (key == null) kvn@1334: { kvn@1334: return null; kvn@1334: } kvn@1334: int h = key.hashCode(); kvn@1334: Entry[] tab = getTable(); kvn@1334: int i = indexFor(h, tab.length); kvn@1334: Entry prev = tab[i]; kvn@1334: Entry e = prev; kvn@1334: kvn@1334: while (e != null) kvn@1334: { kvn@1334: Entry next = e.next; kvn@1334: V candidate = e.get(); kvn@1334: if (h == e.hash && eq(key, candidate)) kvn@1334: { kvn@1334: size--; kvn@1334: if (prev == e) kvn@1334: { kvn@1334: tab[i] = next; kvn@1334: } kvn@1334: else kvn@1334: { kvn@1334: prev.next = next; kvn@1334: } kvn@1334: return candidate; kvn@1334: } kvn@1334: prev = e; kvn@1334: e = next; kvn@1334: } kvn@1334: kvn@1334: return null; kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * Removes all mappings from this map. kvn@1334: */ kvn@1334: public void clear() kvn@1334: { kvn@1334: // clear out ref queue. We don't need to expunge entries kvn@1334: // since table is getting cleared. kvn@1334: while (queue.poll() != null) kvn@1334: { kvn@1334: // nop kvn@1334: } kvn@1334: kvn@1334: table = new Entry[DEFAULT_INITIAL_CAPACITY]; kvn@1334: threshold = DEFAULT_INITIAL_CAPACITY; kvn@1334: size = 0; kvn@1334: kvn@1334: // Allocation of array may have caused GC, which may have caused kvn@1334: // additional entries to go stale. Removing these entries from the kvn@1334: // reference queue will make them eligible for reclamation. kvn@1334: while (queue.poll() != null) kvn@1334: { kvn@1334: // nop kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: /** kvn@1334: * The entries in this hash table extend WeakReference, using its main ref kvn@1334: * field as the key. kvn@1334: */ kvn@1334: protected static class Entry kvn@1334: extends WeakReference kvn@1334: { kvn@1334: private final int hash; kvn@1334: private Entry next; kvn@1334: kvn@1334: /** kvn@1334: * Create new entry. kvn@1334: */ kvn@1334: Entry(final V key, final ReferenceQueue queue, final int hash, final Entry next) kvn@1334: { kvn@1334: super(key, queue); kvn@1334: this.hash = hash; kvn@1334: this.next = next; kvn@1334: } kvn@1334: kvn@1334: public V getKey() kvn@1334: { kvn@1334: return super.get(); kvn@1334: } kvn@1334: kvn@1334: public boolean equals(Object o) kvn@1334: { kvn@1334: if (!(o instanceof WeakPool.Entry)) kvn@1334: { kvn@1334: return false; kvn@1334: } kvn@1334: WeakPool.Entry that = (WeakPool.Entry) o; kvn@1334: V k1 = this.getKey(); kvn@1334: V k2 = that.getKey(); kvn@1334: return (k1==k2 || k1.equals(k2)); kvn@1334: } kvn@1334: kvn@1334: public int hashCode() kvn@1334: { kvn@1334: return this.hash; kvn@1334: } kvn@1334: kvn@1334: public String toString() kvn@1334: { kvn@1334: return String.valueOf(this.getKey()); kvn@1334: } kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: final class MultiSynonymKey { kvn@1334: private List keys; kvn@1334: kvn@1334: public MultiSynonymKey() { kvn@1334: keys = new ArrayList(); kvn@1334: } kvn@1334: kvn@1334: public MultiSynonymKey(MyList... arg) { kvn@1334: keys = Arrays.asList(arg); kvn@1334: } kvn@1334: kvn@1334: public List getKeys() { kvn@1334: return keys; kvn@1334: } kvn@1334: kvn@1334: public int hashCode() { kvn@1334: return this.getKeys().hashCode(); kvn@1334: } kvn@1334: kvn@1334: public boolean equals(Object obj) { kvn@1334: if (this == obj) { kvn@1334: return true; kvn@1334: } kvn@1334: kvn@1334: if (!(obj instanceof MultiSynonymKey)) { kvn@1334: return false; kvn@1334: } kvn@1334: kvn@1334: MultiSynonymKey that = (MultiSynonymKey) obj; kvn@1334: return this.getKeys().equals(that.getKeys()); kvn@1334: } kvn@1334: kvn@1334: public String toString() { kvn@1334: return this.getClass().getName() + this.getKeys().toString(); kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: public class Test extends Thread { kvn@1334: static public Test test; kvn@1334: static private byte[] arg1; kvn@1334: static private byte[] arg2; kvn@1334: static public WeakPool wp; kvn@1334: public volatile MultiSynonymKey ml1; kvn@1334: public volatile MultiSynonymKey ml2; kvn@1334: private volatile MultiSynonymKey ml3; kvn@1334: kvn@1334: public void run() { kvn@1334: int count=0; kvn@1334: while (true) { kvn@1334: try { kvn@1334: Thread.sleep(10); kvn@1334: } catch (Exception e) {} kvn@1334: synchronized (wp) { kvn@1334: ml2 = new MultiSynonymKey(new DoubletonList(new String(arg1), new String(arg2))); kvn@1334: wp.put(ml2); kvn@1334: ml3 = new MultiSynonymKey(new DoubletonList(new String(arg1), new String(arg2))); kvn@1334: } kvn@1334: try { kvn@1334: Thread.sleep(10); kvn@1334: } catch (Exception e) {} kvn@1334: synchronized (wp) { kvn@1334: ml1 = new MultiSynonymKey(new SingletonList(new String(arg1))); kvn@1334: wp.put(ml1); kvn@1334: ml3 = new MultiSynonymKey(new SingletonList(new String(arg1))); kvn@1334: } kvn@1334: if (count++==100) kvn@1334: System.exit(95); kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: public static void main(String[] args) throws Exception { kvn@1334: wp = new WeakPool(); kvn@1334: test = new Test(); kvn@1334: kvn@1334: test.arg1 = args[0].getBytes(); kvn@1334: test.arg2 = args[1].getBytes(); kvn@1334: kvn@1334: test.ml1 = new MultiSynonymKey(new SingletonList(new String(test.arg1))); kvn@1334: test.ml2 = new MultiSynonymKey(new DoubletonList(new String(test.arg1), new String(test.arg2))); kvn@1334: test.ml3 = new MultiSynonymKey(new DoubletonList(new String(test.arg1), new String(test.arg2))); kvn@1334: kvn@1334: wp.put(test.ml1); kvn@1334: wp.put(test.ml2); kvn@1334: kvn@1334: test.setDaemon(true); kvn@1334: test.start(); kvn@1334: kvn@1334: int counter = 0; kvn@1334: while (true) { kvn@1334: synchronized (wp) { kvn@1334: MultiSynonymKey foo = test.ml3; kvn@1334: kvn@1334: if (wp.put(foo) == foo) { kvn@1334: // System.out.println("foo " + counter); kvn@1334: // System.out.println(foo); kvn@1334: } kvn@1334: } kvn@1334: counter++; kvn@1334: } kvn@1334: } kvn@1334: kvn@1334: private boolean eq(Object x, Object y) { kvn@1334: return x == y || x.equals(y); kvn@1334: } kvn@1334: }