1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import java.lang.ref.PhantomReference; 18 import java.lang.ref.Reference; 19 import java.lang.ref.ReferenceQueue; 20 import java.lang.ref.WeakReference; 21 import java.math.BigInteger; 22 import java.util.concurrent.ArrayBlockingQueue; 23 import java.util.concurrent.atomic.AtomicInteger; 24 import java.util.concurrent.ConcurrentHashMap; 25 import java.util.TreeMap; 26 27 /** 28 * Test that objects get finalized and their references cleared in the right order. 29 * 30 * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects. 31 * We then alternately drop the last 50, and add 50 more. When we see an object finalized 32 * or its reference cleared, we make sure that the preceding objects in its group of 50 33 * have also had their references cleared. We also perform a number of other more 34 * straightforward checks, such as ensuring that all references are eventually cleared, 35 * and all objects are finalized. 36 */ 37 public class Main { 38 // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few 39 // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked 40 // down and fixed. 41 static final boolean CHECK_PHANTOM_REFS = false; 42 43 static final int MAX_LIVE_OBJS = 150; 44 static final int DROP_OBJS = 50; // Number of linked objects dropped in each batch. 45 static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS; 46 static final int TOTAL_OBJS = 200_000; // Allocate this many finalizable objects in total. 47 static final boolean REPORT_DROPS = false; 48 static volatile boolean pleaseStop; 49 50 AtomicInteger totalFinalized = new AtomicInteger(0); 51 int maxDropped = 0; 52 int liveObjects = 0; 53 54 // Number of next finalizable object to be allocated. 55 int nextAllocated = 0; 56 57 // List of finalizable objects in descending order. We add to the front and drop 58 // from the rear. 59 FinalizableObject listHead; 60 61 // A possibly incomplete list of FinalizableObject indices that were finalized, but 62 // have yet to be checked for consistency with reference processing. 63 ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000); 64 65 // Maps from object number to Reference; Cleared references are deleted when queues are 66 // processed. 67 TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>(); 68 ConcurrentHashMap<Integer, MyPhantomReference> phantomRefs = new ConcurrentHashMap<>(); 69 70 class FinalizableObject { 71 int n; 72 FinalizableObject next; FinalizableObject(int num, FinalizableObject nextObj)73 FinalizableObject(int num, FinalizableObject nextObj) { 74 n = num; 75 next = nextObj; 76 } finalize()77 protected void finalize() { 78 if (!inPhantomRefs(n)) { 79 System.out.println("PhantomRef enqueued before finalizer ran"); 80 } 81 totalFinalized.incrementAndGet(); 82 if (!finalized.offer(n) && REPORT_DROPS) { 83 System.out.println("Dropped finalization of " + n); 84 } 85 } 86 } 87 ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>(); 88 class MyWeakReference extends WeakReference<FinalizableObject> { 89 int n; MyWeakReference(FinalizableObject obj)90 MyWeakReference(FinalizableObject obj) { 91 super(obj, refQueue); 92 n = obj.n; 93 } 94 }; 95 class MyPhantomReference extends PhantomReference<FinalizableObject> { 96 int n; MyPhantomReference(FinalizableObject obj)97 MyPhantomReference(FinalizableObject obj) { 98 super(obj, refQueue); 99 n = obj.n; 100 } 101 } inPhantomRefs(int n)102 boolean inPhantomRefs(int n) { 103 MyPhantomReference ref = phantomRefs.get(n); 104 if (ref == null) { 105 return false; 106 } 107 if (ref.n != n) { 108 System.out.println("phantomRef retrieval failed"); 109 } 110 return true; 111 } 112 CheckOKToClearWeak(int num)113 void CheckOKToClearWeak(int num) { 114 if (num > maxDropped) { 115 System.out.println("WeakRef to live object " + num + " was cleared/enqueued."); 116 } 117 int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS; 118 for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) { 119 if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) { 120 throw new AssertionError("MyWeakReference logic error!"); 121 } 122 // wr referent was dropped in same batch and precedes it in list. 123 if (wr.get() != null) { 124 // This violates the WeakReference spec, and can result in strong references 125 // to objects that have been cleaned. 126 System.out.println("WeakReference to " + wr.n 127 + " was erroneously cleared after " + num); 128 } 129 } 130 } 131 CheckOKToClearPhantom(int num)132 void CheckOKToClearPhantom(int num) { 133 if (num > maxDropped) { 134 System.out.println("PhantomRef to live object " + num + " was enqueued."); 135 } 136 MyWeakReference wr = weakRefs.get(num); 137 if (wr != null && wr.get() != null) { 138 System.out.println("PhantomRef cleared before WeakRef for " + num); 139 } 140 } 141 emptyAndCheckQueues()142 void emptyAndCheckQueues() { 143 // Check recently finalized objects for consistency with cleared references. 144 while (true) { 145 Integer num = finalized.poll(); 146 if (num == null) { 147 break; 148 } 149 MyWeakReference wr = weakRefs.get(num); 150 if (wr != null) { 151 if (wr.n != num) { 152 System.out.println("Finalization logic error!"); 153 } 154 if (wr.get() != null) { 155 System.out.println("Finalizing object with uncleared reference"); 156 } 157 } 158 CheckOKToClearWeak(num); 159 } 160 // Check recently enqueued references for consistency. 161 while (true) { 162 Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll(); 163 if (ref == null) { 164 break; 165 } 166 if (ref instanceof MyWeakReference) { 167 MyWeakReference wr = (MyWeakReference) ref; 168 if (wr.get() != null) { 169 System.out.println("WeakRef " + wr.n + " enqueued but not cleared"); 170 } 171 CheckOKToClearWeak(wr.n); 172 if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) { 173 System.out.println("Missing WeakReference: " + wr.n); 174 } 175 } else if (ref instanceof MyPhantomReference) { 176 MyPhantomReference pr = (MyPhantomReference) ref; 177 CheckOKToClearPhantom(pr.n); 178 if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) { 179 System.out.println("Missing PhantomReference: " + pr.n); 180 } 181 } else { 182 System.out.println("Found unrecognized reference in queue"); 183 } 184 } 185 } 186 187 188 /** 189 * Add n objects to the head of the list. These will be assigned the next n consecutive 190 * numbers after the current head of the list. 191 */ addObjects(int n)192 void addObjects(int n) { 193 for (int i = 0; i < n; ++i) { 194 int me = nextAllocated++; 195 listHead = new FinalizableObject(me, listHead); 196 weakRefs.put(me, new MyWeakReference(listHead)); 197 phantomRefs.put(me, new MyPhantomReference(listHead)); 198 } 199 liveObjects += n; 200 } 201 202 /** 203 * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects 204 * in the list. 205 */ dropObjects(int n)206 void dropObjects(int n) { 207 FinalizableObject list = listHead; 208 FinalizableObject last = null; 209 if (n > liveObjects) { 210 System.out.println("Removing too many elements"); 211 } 212 if (liveObjects == n) { 213 maxDropped = list.n; 214 listHead = null; 215 } else { 216 final int skip = liveObjects - n; 217 for (int i = 0; i < skip; ++i) { 218 last = list; 219 list = list.next; 220 } 221 int expected = nextAllocated - skip - 1; 222 if (list.n != expected) { 223 System.out.println("dropObjects found " + list.n + " but expected " + expected); 224 } 225 maxDropped = expected; 226 last.next = null; 227 } 228 liveObjects -= n; 229 } 230 testLoop()231 void testLoop() { 232 System.out.println("Starting"); 233 addObjects(MIN_LIVE_OBJS); 234 final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS; 235 for (int i = 0; i < ITERS; ++i) { 236 addObjects(DROP_OBJS); 237 if (liveObjects != MAX_LIVE_OBJS) { 238 System.out.println("Unexpected live object count"); 239 } 240 dropObjects(DROP_OBJS); 241 if (i % 100 == 0) { 242 // Make sure we don't fall too far behind, otherwise we may run out of memory. 243 System.runFinalization(); 244 } 245 emptyAndCheckQueues(); 246 } 247 dropObjects(MIN_LIVE_OBJS); 248 if (liveObjects != 0 || listHead != null) { 249 System.out.println("Unexpected live objecs at end"); 250 } 251 if (maxDropped != TOTAL_OBJS - 1) { 252 System.out.println("Unexpected dropped object count: " + maxDropped); 253 } 254 for (int i = 0; i < 2; ++i) { 255 Runtime.getRuntime().gc(); 256 System.runFinalization(); 257 emptyAndCheckQueues(); 258 } 259 if (!weakRefs.isEmpty()) { 260 System.out.println("Weak Reference map nonempty size = " + weakRefs.size()); 261 } 262 if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) { 263 try { 264 Thread.sleep(500); 265 } catch (InterruptedException e) { 266 System.out.println("Unexpected interrupt"); 267 } 268 if (!phantomRefs.isEmpty()) { 269 System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size()); 270 System.out.print("First elements:"); 271 int i = 0; 272 for (MyPhantomReference pr : phantomRefs.values()) { 273 System.out.print(" " + pr.n); 274 if (++i > 10) { 275 break; 276 } 277 } 278 System.out.println(""); 279 } 280 } 281 if (totalFinalized.get() != TOTAL_OBJS) { 282 System.out.println("Finalized only " + totalFinalized + " objects"); 283 } 284 } 285 286 static Runnable causeGCs = new Runnable() { 287 public void run() { 288 // Allocate a lot. 289 BigInteger counter = BigInteger.ZERO; 290 while (!pleaseStop) { 291 counter = counter.add(BigInteger.TEN); 292 } 293 // Look at counter to reduce chance of optimizing out the allocation. 294 if (counter.longValue() % 10 != 0) { 295 System.out.println("Bad causeGCs counter value: " + counter); 296 } 297 } 298 }; 299 main(String[] args)300 public static void main(String[] args) throws Exception { 301 Main theTest = new Main(); 302 Thread gcThread = new Thread(causeGCs); 303 gcThread.setDaemon(true); // Terminate if main thread dies. 304 gcThread.start(); 305 theTest.testLoop(); 306 pleaseStop = true; 307 gcThread.join(); 308 System.out.println("Finished"); 309 } 310 } 311