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