1 /* 2 * Copyright 2009 Goldman Sachs International. All Rights Reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 20 * CA 95054 USA or visit www.sun.com if you need additional information or 21 * have any questions. 22 * 23 */ 24 25 /* 26 * @test 27 * @bug 6865031 28 * @summary Application gives bad result (throws bad exception) with compressed oops 29 * @run main/othervm -XX:+UseCompressedOops -XX:HeapBaseMinAddress=32g -XX:-LoopUnswitching -XX:CompileCommand=inline,AbstractMemoryEfficientList.equals Test hello goodbye 30 */ 31 32 import java.lang.ref.ReferenceQueue; 33 import java.lang.ref.WeakReference; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 38 interface MyList { 39 public int size(); 40 public Object set(final int index, final Object element); 41 public Object get(final int index); 42 } 43 44 abstract class AbstractMemoryEfficientList implements MyList { 45 abstract public int size(); 46 abstract public Object get(final int index); 47 abstract public Object set(final int index, final Object element); 48 49 public boolean equals(Object o) { 50 if (o == this) { 51 return true; 52 } 53 54 if (!(o instanceof MyList)) { 55 return false; 56 } 57 58 final MyList that = (MyList) o; 59 if (this.size() != that.size()) { 60 return false; 61 } 62 63 for (int i = 0; i < this.size(); i++) { 64 try { 65 if (!((this.get(i)).equals(that.get(i)))) { 66 return false; 67 } 68 } catch (IndexOutOfBoundsException e) { 69 System.out.println("THROWING RT EXC"); 70 System.out.println("concurrent modification of this:" + this.getClass() + ":" + System.identityHashCode(this) + "; that:" + that.getClass() + ":" + System.identityHashCode(that) + "; i:" + i); 71 e.printStackTrace(); 72 System.exit(97); 73 throw new RuntimeException("concurrent modification of this:" + this.getClass() + ":" + System.identityHashCode(this) + "; that:" + that.getClass() + ":" + System.identityHashCode(that) + "; i:" + i, e); 74 } 75 } 76 return true; 77 } 78 79 public int hashCode() { 80 int hashCode = 1; 81 for (int i = 0; i < this.size(); i++) { 82 Object obj = this.get(i); 83 hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); 84 } 85 return hashCode; 86 } 87 } 88 89 final class SingletonList extends AbstractMemoryEfficientList { 90 private Object element1; 91 92 SingletonList(final Object obj1) { 93 super(); 94 this.element1 = obj1; 95 } 96 97 public int size() { 98 return 1; 99 } 100 101 public Object get(final int index) { 102 if (index == 0) { 103 return this.element1; 104 } else { 105 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); 106 } 107 } 108 109 public Object set(final int index, final Object element) { 110 if (index == 0) { 111 final Object previousElement = this.element1; 112 this.element1 = element; 113 return previousElement; 114 } else { 115 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); 116 } 117 } 118 } 119 120 final class DoubletonList extends AbstractMemoryEfficientList { 121 private Object element1; 122 private Object element2; 123 124 DoubletonList(final Object obj1, final Object obj2) { 125 this.element1 = obj1; 126 this.element2 = obj2; 127 } 128 129 public int size() { 130 return 2; 131 } 132 133 public Object get(final int index) { 134 switch (index) { 135 case 0 : return this.element1; 136 case 1 : return this.element2; 137 default: throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); 138 } 139 } 140 141 public Object set(final int index, final Object element) { 142 switch (index) { 143 case 0 : 144 { 145 final Object previousElement = this.element1; 146 this.element1 = element; 147 return previousElement; 148 } 149 case 1 : 150 { 151 final Object previousElement = this.element2; 152 this.element2 = element; 153 return previousElement; 154 } 155 default : throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size()); 156 } 157 } 158 } 159 160 class WeakPool<V> { 161 protected static final int DEFAULT_INITIAL_CAPACITY = 16; 162 private static final int MAXIMUM_CAPACITY = 1 << 30; 163 private static final float DEFAULT_LOAD_FACTOR = 0.75f; 164 165 protected Entry<V>[] table; 166 167 private int size; 168 protected int threshold; 169 private final float loadFactor; 170 private final ReferenceQueue<V> queue = new ReferenceQueue<V>(); 171 172 public WeakPool() 173 { 174 this.loadFactor = DEFAULT_LOAD_FACTOR; 175 threshold = DEFAULT_INITIAL_CAPACITY; 176 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 177 } 178 179 /** 180 * Check for equality of non-null reference x and possibly-null y. By 181 * default uses Object.equals. 182 */ 183 private boolean eq(Object x, Object y) 184 { 185 return x == y || x.equals(y); 186 } 187 188 /** 189 * Return index for hash code h. 190 */ 191 private int indexFor(int h, int length) 192 { 193 return h & length - 1; 194 } 195 196 /** 197 * Expunge stale entries from the table. 198 */ 199 private void expungeStaleEntries() 200 { 201 Object r; 202 while ((r = queue.poll()) != null) 203 { 204 Entry e = (Entry) r; 205 int h = e.hash; 206 int i = indexFor(h, table.length); 207 208 // System.out.println("EXPUNGING " + h); 209 Entry<V> prev = table[i]; 210 Entry<V> p = prev; 211 while (p != null) 212 { 213 Entry<V> next = p.next; 214 if (p == e) 215 { 216 if (prev == e) 217 { 218 table[i] = next; 219 } 220 else 221 { 222 prev.next = next; 223 } 224 e.next = null; // Help GC 225 size--; 226 break; 227 } 228 prev = p; 229 p = next; 230 } 231 } 232 } 233 234 /** 235 * Return the table after first expunging stale entries 236 */ 237 private Entry<V>[] getTable() 238 { 239 expungeStaleEntries(); 240 return table; 241 } 242 243 /** 244 * Returns the number of key-value mappings in this map. 245 * This result is a snapshot, and may not reflect unprocessed 246 * entries that will be removed before next attempted access 247 * because they are no longer referenced. 248 */ 249 public int size() 250 { 251 if (size == 0) 252 { 253 return 0; 254 } 255 expungeStaleEntries(); 256 return size; 257 } 258 259 /** 260 * Returns <tt>true</tt> if this map contains no key-value mappings. 261 * This result is a snapshot, and may not reflect unprocessed 262 * entries that will be removed before next attempted access 263 * because they are no longer referenced. 264 */ 265 public boolean isEmpty() 266 { 267 return size() == 0; 268 } 269 270 /** 271 * Returns the value stored in the pool that equals the requested key 272 * or <tt>null</tt> if the map contains no mapping for 273 * this key (or the key is null) 274 * 275 * @param key the key whose equals value is to be returned. 276 * @return the object that is equal the specified key, or 277 * <tt>null</tt> if key is null or no object in the pool equals the key. 278 */ 279 public V get(V key) 280 { 281 if (key == null) 282 { 283 return null; 284 } 285 int h = key.hashCode(); 286 Entry<V>[] tab = getTable(); 287 int index = indexFor(h, tab.length); 288 Entry<V> e = tab[index]; 289 while (e != null) 290 { 291 V candidate = e.get(); 292 if (e.hash == h && eq(key, candidate)) 293 { 294 return candidate; 295 } 296 e = e.next; 297 } 298 return null; 299 } 300 301 /** 302 * Returns the entry associated with the specified key in the HashMap. 303 * Returns null if the HashMap contains no mapping for this key. 304 */ 305 Entry getEntry(Object key) 306 { 307 int h = key.hashCode(); 308 Entry[] tab = getTable(); 309 int index = indexFor(h, tab.length); 310 Entry e = tab[index]; 311 while (e != null && !(e.hash == h && eq(key, e.get()))) 312 { 313 e = e.next; 314 } 315 return e; 316 } 317 318 /** 319 * Places the object into the pool. If the object is null, nothing happens. 320 * If an equal object already exists, it is not replaced. 321 * 322 * @param key the object to put into the pool. key may be null. 323 * @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 324 */ 325 public V put(V key) 326 { 327 if (key == null) 328 { 329 return null; 330 } 331 int h = key.hashCode(); 332 Entry<V>[] tab = getTable(); 333 int i = indexFor(h, tab.length); 334 335 for (Entry<V> e = tab[i]; e != null; e = e.next) 336 { 337 V candidate = e.get(); 338 if (h == e.hash && eq(key, candidate)) 339 { 340 return candidate; 341 } 342 } 343 344 tab[i] = new Entry<V>(key, queue, h, tab[i]); 345 346 if (++size >= threshold) 347 { 348 resize(tab.length * 2); 349 } 350 351 // System.out.println("Added " + key + " to pool"); 352 return key; 353 } 354 355 /** 356 * Rehashes the contents of this map into a new array with a 357 * larger capacity. This method is called automatically when the 358 * number of keys in this map reaches its threshold. 359 * <p/> 360 * If current capacity is MAXIMUM_CAPACITY, this method does not 361 * resize the map, but but sets threshold to Integer.MAX_VALUE. 362 * This has the effect of preventing future calls. 363 * 364 * @param newCapacity the new capacity, MUST be a power of two; 365 * must be greater than current capacity unless current 366 * capacity is MAXIMUM_CAPACITY (in which case value 367 * is irrelevant). 368 */ 369 void resize(int newCapacity) 370 { 371 Entry<V>[] oldTable = getTable(); 372 int oldCapacity = oldTable.length; 373 if (oldCapacity == MAXIMUM_CAPACITY) 374 { 375 threshold = Integer.MAX_VALUE; 376 return; 377 } 378 379 Entry<V>[] newTable = new Entry[newCapacity]; 380 transfer(oldTable, newTable); 381 table = newTable; 382 383 /* 384 * If ignoring null elements and processing ref queue caused massive 385 * shrinkage, then restore old table. This should be rare, but avoids 386 * unbounded expansion of garbage-filled tables. 387 */ 388 if (size >= threshold / 2) 389 { 390 threshold = (int) (newCapacity * loadFactor); 391 } 392 else 393 { 394 expungeStaleEntries(); 395 transfer(newTable, oldTable); 396 table = oldTable; 397 } 398 } 399 400 /** 401 * Transfer all entries from src to dest tables 402 */ 403 private void transfer(Entry[] src, Entry[] dest) 404 { 405 for (int j = 0; j < src.length; ++j) 406 { 407 Entry e = src[j]; 408 src[j] = null; 409 while (e != null) 410 { 411 Entry next = e.next; 412 Object key = e.get(); 413 if (key == null) 414 { 415 e.next = null; // Help GC 416 size--; 417 } 418 else 419 { 420 int i = indexFor(e.hash, dest.length); 421 e.next = dest[i]; 422 dest[i] = e; 423 } 424 e = next; 425 } 426 } 427 } 428 429 /** 430 * Removes the object in the pool that equals the key. 431 * 432 * @param key 433 * @return previous value associated with specified key, or <tt>null</tt> 434 * if there was no mapping for key or the key is null. 435 */ 436 public V removeFromPool(V key) 437 { 438 if (key == null) 439 { 440 return null; 441 } 442 int h = key.hashCode(); 443 Entry<V>[] tab = getTable(); 444 int i = indexFor(h, tab.length); 445 Entry<V> prev = tab[i]; 446 Entry<V> e = prev; 447 448 while (e != null) 449 { 450 Entry<V> next = e.next; 451 V candidate = e.get(); 452 if (h == e.hash && eq(key, candidate)) 453 { 454 size--; 455 if (prev == e) 456 { 457 tab[i] = next; 458 } 459 else 460 { 461 prev.next = next; 462 } 463 return candidate; 464 } 465 prev = e; 466 e = next; 467 } 468 469 return null; 470 } 471 472 /** 473 * Removes all mappings from this map. 474 */ 475 public void clear() 476 { 477 // clear out ref queue. We don't need to expunge entries 478 // since table is getting cleared. 479 while (queue.poll() != null) 480 { 481 // nop 482 } 483 484 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 485 threshold = DEFAULT_INITIAL_CAPACITY; 486 size = 0; 487 488 // Allocation of array may have caused GC, which may have caused 489 // additional entries to go stale. Removing these entries from the 490 // reference queue will make them eligible for reclamation. 491 while (queue.poll() != null) 492 { 493 // nop 494 } 495 } 496 497 /** 498 * The entries in this hash table extend WeakReference, using its main ref 499 * field as the key. 500 */ 501 protected static class Entry<V> 502 extends WeakReference<V> 503 { 504 private final int hash; 505 private Entry<V> next; 506 507 /** 508 * Create new entry. 509 */ 510 Entry(final V key, final ReferenceQueue<V> queue, final int hash, final Entry<V> next) 511 { 512 super(key, queue); 513 this.hash = hash; 514 this.next = next; 515 } 516 517 public V getKey() 518 { 519 return super.get(); 520 } 521 522 public boolean equals(Object o) 523 { 524 if (!(o instanceof WeakPool.Entry)) 525 { 526 return false; 527 } 528 WeakPool.Entry<V> that = (WeakPool.Entry<V>) o; 529 V k1 = this.getKey(); 530 V k2 = that.getKey(); 531 return (k1==k2 || k1.equals(k2)); 532 } 533 534 public int hashCode() 535 { 536 return this.hash; 537 } 538 539 public String toString() 540 { 541 return String.valueOf(this.getKey()); 542 } 543 } 544 } 545 546 final class MultiSynonymKey { 547 private List<MyList> keys; 548 549 public MultiSynonymKey() { 550 keys = new ArrayList<MyList>(); 551 } 552 553 public MultiSynonymKey(MyList... arg) { 554 keys = Arrays.asList(arg); 555 } 556 557 public List<MyList> getKeys() { 558 return keys; 559 } 560 561 public int hashCode() { 562 return this.getKeys().hashCode(); 563 } 564 565 public boolean equals(Object obj) { 566 if (this == obj) { 567 return true; 568 } 569 570 if (!(obj instanceof MultiSynonymKey)) { 571 return false; 572 } 573 574 MultiSynonymKey that = (MultiSynonymKey) obj; 575 return this.getKeys().equals(that.getKeys()); 576 } 577 578 public String toString() { 579 return this.getClass().getName() + this.getKeys().toString(); 580 } 581 } 582 583 public class Test extends Thread { 584 static public Test test; 585 static private byte[] arg1; 586 static private byte[] arg2; 587 static public WeakPool<MultiSynonymKey> wp; 588 public volatile MultiSynonymKey ml1; 589 public volatile MultiSynonymKey ml2; 590 private volatile MultiSynonymKey ml3; 591 592 public void run() { 593 int count=0; 594 while (true) { 595 try { 596 Thread.sleep(10); 597 } catch (Exception e) {} 598 synchronized (wp) { 599 ml2 = new MultiSynonymKey(new DoubletonList(new String(arg1), new String(arg2))); 600 wp.put(ml2); 601 ml3 = new MultiSynonymKey(new DoubletonList(new String(arg1), new String(arg2))); 602 } 603 try { 604 Thread.sleep(10); 605 } catch (Exception e) {} 606 synchronized (wp) { 607 ml1 = new MultiSynonymKey(new SingletonList(new String(arg1))); 608 wp.put(ml1); 609 ml3 = new MultiSynonymKey(new SingletonList(new String(arg1))); 610 } 611 if (count++==100) 612 System.exit(95); 613 } 614 } 615 616 public static void main(String[] args) throws Exception { 617 wp = new WeakPool<MultiSynonymKey>(); 618 test = new Test(); 619 620 test.arg1 = args[0].getBytes(); 621 test.arg2 = args[1].getBytes(); 622 623 test.ml1 = new MultiSynonymKey(new SingletonList(new String(test.arg1))); 624 test.ml2 = new MultiSynonymKey(new DoubletonList(new String(test.arg1), new String(test.arg2))); 625 test.ml3 = new MultiSynonymKey(new DoubletonList(new String(test.arg1), new String(test.arg2))); 626 627 wp.put(test.ml1); 628 wp.put(test.ml2); 629 630 test.setDaemon(true); 631 test.start(); 632 633 int counter = 0; 634 while (true) { 635 synchronized (wp) { 636 MultiSynonymKey foo = test.ml3; 637 638 if (wp.put(foo) == foo) { 639 // System.out.println("foo " + counter); 640 // System.out.println(foo); 641 } 642 } 643 counter++; 644 } 645 } 646 647 private boolean eq(Object x, Object y) { 648 return x == y || x.equals(y); 649 } 650 }