1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3 * 4 * This code is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License version 2 only, as 6 * published by the Free Software Foundation. 7 * 8 * This code is distributed in the hope that it will be useful, but WITHOUT 9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 11 * version 2 for more details (a copy is included in the LICENSE file that 12 * accompanied this code). 13 * 14 * You should have received a copy of the GNU General Public License version 15 * 2 along with this work; if not, write to the Free Software Foundation, 16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 17 * 18 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 19 * or visit www.oracle.com if you need additional information or have any 20 * questions. 21 */ 22 23 /* 24 * This file is available under and governed by the GNU General Public 25 * License version 2 only, as published by the Free Software Foundation. 26 * However, the following notice accompanied the original version of this 27 * file: 28 * 29 * Written by Martin Buchholz with assistance from members of JCP 30 * JSR-166 Expert Group and released to the public domain, as 31 * explained at http://creativecommons.org/publicdomain/zero/1.0/ 32 */ 33 34 /* 35 * @test 36 * @bug 8054446 8137184 8137185 37 * @modules java.base/java.util.concurrent:open 38 * java.base/java.util.concurrent.locks:open 39 * @summary Regression test for memory leak in remove(Object) 40 */ 41 42 import java.lang.reflect.AccessibleObject; 43 import java.lang.reflect.Array; 44 import java.lang.reflect.Field; 45 import java.lang.reflect.Modifier; 46 import java.util.ArrayDeque; 47 import java.util.Collection; 48 import java.util.Collections; 49 import java.util.IdentityHashMap; 50 import java.util.Set; 51 import java.util.concurrent.ArrayBlockingQueue; 52 import java.util.concurrent.ConcurrentHashMap; 53 import java.util.concurrent.ConcurrentLinkedDeque; 54 import java.util.concurrent.ConcurrentLinkedQueue; 55 import java.util.concurrent.LinkedBlockingDeque; 56 import java.util.concurrent.LinkedBlockingQueue; 57 import java.util.concurrent.LinkedTransferQueue; 58 import java.util.concurrent.PriorityBlockingQueue; 59 60 public class RemoveLeak { main(String[] args)61 public static void main(String[] args) throws Throwable { 62 new RemoveLeak().main(); 63 } 64 main()65 void main() throws Throwable { 66 test(new ConcurrentLinkedDeque()); 67 test(new ConcurrentLinkedQueue()); 68 test(new LinkedBlockingDeque(10)); 69 test(new LinkedBlockingQueue(10)); 70 test(new LinkedTransferQueue()); 71 test(new ArrayBlockingQueue(10)); 72 test(new PriorityBlockingQueue()); 73 } 74 test(Collection c)75 void test(Collection c) throws Throwable { 76 assertNoLeak(c, () -> addRemove(c)); 77 78 // A demo that the leak detection infrastructure works 79 // assertNoLeak(c, () -> c.add(1)); 80 // printRetainedObjects(c); 81 } 82 addRemove(Collection c)83 static void addRemove(Collection c) { 84 c.add(1); 85 c.remove(1); 86 } 87 88 // -------- leak detection infrastructure --------------- assertNoLeak(Object root, Runnable r)89 void assertNoLeak(Object root, Runnable r) { 90 int prev = retainedObjects(root).size(); 91 for (int i = 0; i < 3; i++) { 92 for (int j = 0; j < 3; j++) r.run(); 93 int next = retainedObjects(root).size(); 94 if (next <= prev) 95 return; 96 prev = next; 97 } 98 throw new AssertionError( 99 String.format("probable memory leak in %s: %s", 100 root.getClass().getSimpleName(), root)); 101 } 102 103 ConcurrentHashMap<Class<?>, Collection<Field>> classFields 104 = new ConcurrentHashMap<>(); 105 referenceFieldsOf(Class<?> k)106 Collection<Field> referenceFieldsOf(Class<?> k) { 107 Collection<Field> fields = classFields.get(k); 108 if (fields == null) { 109 fields = new ArrayDeque<Field>(); 110 ArrayDeque<Field> allFields = new ArrayDeque<>(); 111 for (Class<?> c = k; c != null; c = c.getSuperclass()) 112 for (Field field : c.getDeclaredFields()) 113 if (!Modifier.isStatic(field.getModifiers()) 114 && !field.getType().isPrimitive()) 115 fields.add(field); 116 AccessibleObject.setAccessible( 117 fields.toArray(new AccessibleObject[0]), true); 118 classFields.put(k, fields); 119 } 120 return fields; 121 } 122 get(Field field, Object x)123 static Object get(Field field, Object x) { 124 try { return field.get(x); } 125 catch (IllegalAccessException ex) { throw new AssertionError(ex); } 126 } 127 retainedObjects(Object x)128 Set<Object> retainedObjects(Object x) { 129 ArrayDeque<Object> todo = new ArrayDeque<>() { 130 public void push(Object x) { if (x != null) super.push(x); }}; 131 Set<Object> uniqueObjects = Collections.newSetFromMap( 132 new IdentityHashMap<Object, Boolean>()); 133 todo.push(x); 134 while (!todo.isEmpty()) { 135 Object y = todo.pop(); 136 if (uniqueObjects.contains(y)) 137 continue; 138 uniqueObjects.add(y); 139 Class<?> k = y.getClass(); 140 if (k.isArray() && !k.getComponentType().isPrimitive()) { 141 for (int i = 0, len = Array.getLength(y); i < len; i++) 142 todo.push(Array.get(y, i)); 143 } else { 144 for (Field field : referenceFieldsOf(k)) 145 todo.push(get(field, y)); 146 } 147 } 148 return uniqueObjects; 149 } 150 151 /** for debugging the retained object graph */ printRetainedObjects(Object x)152 void printRetainedObjects(Object x) { 153 for (Object y : retainedObjects(x)) 154 System.out.printf("%s : %s%n", y.getClass().getSimpleName(), y); 155 } 156 } 157