• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Reference;
18 import java.lang.ref.WeakReference;
19 import java.lang.ref.SoftReference;
20 import java.math.BigInteger;
21 import java.util.ArrayList;
22 
23 /**
24  * Basic test of WeakReferences with large amounts of memory that's only reachable through
25  * finalizers. Also makes sure that finalizer-reachable data is not collected.
26  * Can easily be modified to time Reference.get() blocking.
27  */
28 public class Main {
29     static final boolean PRINT_TIMES = false;  // true will cause benchmark failure.
30     // Data structures repeatedly allocated in background to trigger GC.
31     // Size of finalizer reachable trees.
32     static final int TREE_HEIGHT = 15;  // Trees contain 2^TREE_HEIGHT -1 allocated objects.
33     // Number of finalizable tree-owning objects that exist at one point.
34     static final int N_RESURRECTING_OBJECTS = 10;
35     // Number of short-lived, not finalizer-reachable, objects allocated between trees.
36     static final int N_PLAIN_OBJECTS = 20_000;
37     // Number of SoftReferences to CBTs we allocate.
38     static final int N_SOFTREFS = 10;
39 
40     static final boolean BACKGROUND_GC_THREAD = true;
41     static final int NBATCHES = 10;
42     static final int NREFS = PRINT_TIMES ? 1_000_000 : 300_000;  // Multiple of NBATCHES.
43     static final int REFS_PER_BATCH = NREFS / NBATCHES;
44 
45     static volatile boolean pleaseStop = false;
46 
47     // Large array of WeakReferences filled and accessed by tests below.
48     ArrayList<WeakReference<Integer>> weakRefs = new ArrayList<>(NREFS);
49 
50     /**
51      * Complete binary tree data structure. make(n) takes O(2^n) space.
52      */
53     static class CBT {
54         CBT left;
55         CBT right;
CBT(CBT l, CBT r)56         CBT(CBT l, CBT r) {
57             left = l;
58             right = r;
59         }
make(int n)60         static CBT make(int n) {
61             if (n == 0) {
62                 return null;
63             }
64             return new CBT(make(n - 1), make(n - 1));
65         }
66         /**
67          * Check that path described by bit-vector path has the correct length.
68          */
check(int n, int path)69         void check(int n, int path) {
70             CBT current = this;
71             for (int i = 0; i < n; i++, path = path >>> 1) {
72                 // Unexpectedly short paths result in NPE.
73                 if ((path & 1) == 0) {
74                     current = current.left;
75                 } else {
76                     current = current.right;
77                 }
78             }
79             if (current != null) {
80                 System.out.println("Complete binary tree path too long");
81             }
82         }
83     }
84 
85 
86     /**
87      * A finalizable object that refers to O(2^TREE_HEIGHT) otherwise unreachable memory.
88      * When finalized, it creates a new identical object, making sure that one always stays
89      * around.
90      */
91     static class ResurrectingObject {
92         CBT stuff;
ResurrectingObject()93         ResurrectingObject() {
94             stuff = CBT.make(TREE_HEIGHT);
95         }
96         static ResurrectingObject a[] = new ResurrectingObject[2];
97         static int i = 0;
allocOne()98         static synchronized void allocOne() {
99             a[(++i) % 2] = new ResurrectingObject();
100             // Check the previous one to make it hard to optimize anything out.
101             if (i > 1) {
102                 a[(i + 1) % 2].stuff.check(TREE_HEIGHT, i /* weirdly interpreted as path */);
103             }
104         }
finalize()105         protected void finalize() {
106             stuff.check(TREE_HEIGHT, 42 /* Some path descriptor */);
107             // Allocate a new one to replace this one.
108             allocOne();
109         }
110     }
111 
fillWeakRefs()112     void fillWeakRefs() {
113         for (int i = 0; i < NREFS; ++i) {
114              weakRefs.add(null);
115         }
116     }
117 
118     /*
119      * Return maximum observed time in nanos to dereference a WeakReference to an unreachable
120      * object. weakRefs is presumed to be pre-filled to have the correct size.
121      */
timeUnreachableInner()122     long timeUnreachableInner() {
123         long maxNanos = 0;
124         // Fill weakRefs with WeakReferences to unreachable integers, a batch at a time.
125         // Then time and test .get() calls on carefully sampled array entries, some of which
126         // will have been cleared.
127         for (int i = 0; i < NBATCHES; ++i) {
128             for (int j = 0; j < REFS_PER_BATCH; ++j) {
129                 weakRefs.set(i * REFS_PER_BATCH + j,
130                         new WeakReference(new Integer(i * REFS_PER_BATCH + j)));
131             }
132             try {
133                 Thread.sleep(50);
134             } catch (InterruptedException e) {
135                 System.out.println("Unexpected exception");
136             }
137             // Iterate over the filled-in section of weakRefs, but look only at a subset of the
138             // elements, making sure the subsets for different top-level iterations are disjoint.
139             // Otherwise the get() calls here will extend the lifetimes of the referents, and we
140             // may never see any cleared WeakReferences.
141             for (int j = (i + 1) * REFS_PER_BATCH - i - 1; j >= 0; j -= NBATCHES) {
142                 WeakReference<Integer> wr = weakRefs.get(j);
143                 if (wr != null) {
144                     long startNanos = System.nanoTime();
145                     Integer referent = wr.get();
146                     long totalNanos = System.nanoTime() - startNanos;
147                     if (referent == null) {
148                         // Optimization to reduce max space use and scanning time.
149                         weakRefs.set(j, null);
150                     }
151                     maxNanos = Math.max(maxNanos, totalNanos);
152                     if (referent != null && referent.intValue() != j) {
153                         System.out.println("Unexpected referent; expected " + j + " got "
154                                 + referent.intValue());
155                     }
156                 }
157             }
158         }
159         return maxNanos;
160     }
161 
162     /*
163      * Wrapper for the above that also checks that references were reclaimed.
164      * We do this separately to make sure any stack references from the core of the
165      * test are gone. Empirically, we otherwise sometimes see the zeroth WeakReference
166      * not reclaimed.
167      */
timeUnreachable()168     long timeUnreachable() {
169         long maxNanos = timeUnreachableInner();
170         Runtime.getRuntime().gc();
171         System.runFinalization();  // Presumed to wait for reference clearing.
172         for (int i = 0; i < NREFS; ++i) {
173             if (weakRefs.get(i) != null && weakRefs.get(i).get() != null) {
174                 System.out.println("WeakReference to " + i + " wasn't cleared");
175             }
176         }
177         return maxNanos;
178     }
179 
180     /**
181      * Return maximum observed time in nanos to dereference a WeakReference to a reachable
182      * object. Overwrites weakRefs, which is presumed to have NREFS entries already.
183     */
timeReachable()184     long timeReachable() {
185         long maxNanos = 0;
186         // Similar to the above, but we use WeakReferences to otherwise reachable objects,
187         // which should thus not get cleared.
188         Integer[] strongRefs = new Integer[NREFS];
189         for (int i = 0; i < NBATCHES; ++i) {
190             for (int j = i * REFS_PER_BATCH; j < (i + 1) * REFS_PER_BATCH; ++j) {
191                 Integer newObj = new Integer(j);
192                 strongRefs[j] = newObj;
193                 weakRefs.set(j, new WeakReference(newObj));
194             }
195             for (int j = (i + 1) * REFS_PER_BATCH - 1; j >= 0; --j) {
196                 WeakReference<Integer> wr = weakRefs.get(j);
197                 long startNanos = System.nanoTime();
198                 Integer referent = wr.get();
199                 long totalNanos = System.nanoTime() - startNanos;
200                 maxNanos = Math.max(maxNanos, totalNanos);
201                 if (referent == null) {
202                     System.out.println("Unexpectedly cleared referent at " + j);
203                 } else if (referent.intValue() != j) {
204                     System.out.println("Unexpected reachable referent; expected " + j + " got "
205                             + referent.intValue());
206                 }
207             }
208         }
209         Reference.reachabilityFence(strongRefs);
210         return maxNanos;
211     }
212 
runTest()213     void runTest() {
214         System.out.println("Starting");
215         fillWeakRefs();
216         long unreachableNanos = timeUnreachable();
217         if (PRINT_TIMES) {
218             System.out.println("Finished timeUnrechable()");
219         }
220         long reachableNanos = timeReachable();
221         String unreachableMillis =
222                 String. format("%,.3f", ((double) unreachableNanos) / 1_000_000);
223         String reachableMillis =
224                 String. format("%,.3f", ((double) reachableNanos) / 1_000_000);
225         if (PRINT_TIMES) {
226             System.out.println(
227                     "Max time for WeakReference.get (unreachable): " + unreachableMillis);
228             System.out.println(
229                     "Max time for WeakReference.get (reachable): " + reachableMillis);
230         }
231         // Only report extremely egregious pauses to avoid spurious failures.
232         if (unreachableNanos > 10_000_000_000L) {
233             System.out.println("WeakReference.get (unreachable) time unreasonably long");
234         }
235         if (reachableNanos > 10_000_000_000L) {
236             System.out.println("WeakReference.get (reachable) time unreasonably long");
237         }
238     }
239 
240     /**
241      * Allocate and GC a lot, while keeping significant amounts of finalizer and
242      * SoftReference-reachable memory around.
243      */
244     static Runnable allocFinalizable = new Runnable() {
245         public void run() {
246             // Allocate and drop some finalizable objects that take a long time
247             // to mark. Designed to be hard to optimize away. Each of these objects will
248             // build a new one in its finalizer before really going away.
249             ArrayList<SoftReference<CBT>> softRefs = new ArrayList<>(N_SOFTREFS);
250             for (int i = 0; i < N_SOFTREFS; ++i) {
251                 // These should not normally get reclaimed, since we shouldn't run out of
252                 // memory. They do increase tracing time.
253                 softRefs.add(new SoftReference(CBT.make(TREE_HEIGHT)));
254             }
255             for (int i = 0; i < N_RESURRECTING_OBJECTS; ++i) {
256                 ResurrectingObject.allocOne();
257             }
258             BigInteger counter = BigInteger.ZERO;
259             for (int i = 1; !pleaseStop; ++i) {
260                 // Allocate a lot of short-lived objects, using BigIntegers to minimize the chance
261                 // of the allocation getting optimized out. This makes things slightly more
262                 // realistic, since not all objects will be finalizer reachable.
263                 for (int j = 0; j < N_PLAIN_OBJECTS / 2; ++j) {
264                     counter = counter.add(BigInteger.TEN);
265                 }
266                 // Look at counter to reduce chance of optimizing out the allocation.
267                 if (counter.longValue() % 10 != 0) {
268                     System.out.println("Bad allocFinalizable counter value: " + counter);
269                 }
270                 // Explicitly collect here, mostly to prevent heap growth. Otherwise we get
271                 // ahead of the GC and eventually block on it.
272                 Runtime.getRuntime().gc();
273                 if (PRINT_TIMES && i % 100 == 0) {
274                     System.out.println("Collected " + i + " times");
275                 }
276             }
277             // To be safe, access softRefs.
278             final CBT sample = softRefs.get(N_SOFTREFS / 2).get();
279             if (sample != null) {
280               sample.check(TREE_HEIGHT, 47 /* some path descriptor */);
281             }
282         }
283     };
284 
main(String[] args)285     public static void main(String[] args) throws Exception {
286         Main theTest = new Main();
287         Thread allocThread = null;
288         if (BACKGROUND_GC_THREAD) {
289             allocThread = new Thread(allocFinalizable);
290             allocThread.setDaemon(true);  // Terminate if main thread dies.
291             allocThread.start();
292         }
293         theTest.runTest();
294         if (BACKGROUND_GC_THREAD) {
295             pleaseStop = true;
296             allocThread.join();
297         }
298         System.out.println("Finished");
299     }
300 }
301