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