| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.lang.ref.PhantomReference; |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.math.BigInteger; |
| import java.util.concurrent.ArrayBlockingQueue; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.TreeMap; |
| |
| /** |
| * Test that objects get finalized and their references cleared in the right order. |
| * |
| * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects. |
| * We then alternately drop the last 50, and add 50 more. When we see an object finalized |
| * or its reference cleared, we make sure that the preceding objects in its group of 50 |
| * have also had their references cleared. We also perform a number of other more |
| * straightforward checks, such as ensuring that all references are eventually cleared, |
| * and all objects are finalized. |
| */ |
| public class Main { |
| // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few |
| // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked |
| // down and fixed. |
| static final boolean CHECK_PHANTOM_REFS = false; |
| |
| static final int MAX_LIVE_OBJS = 150; |
| static final int DROP_OBJS = 50; // Number of linked objects dropped in each batch. |
| static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS; |
| static final int TOTAL_OBJS = 200_000; // Allocate this many finalizable objects in total. |
| static final boolean REPORT_DROPS = false; |
| static volatile boolean pleaseStop; |
| |
| AtomicInteger totalFinalized = new AtomicInteger(0); |
| int maxDropped = 0; |
| int liveObjects = 0; |
| |
| // Number of next finalizable object to be allocated. |
| int nextAllocated = 0; |
| |
| // List of finalizable objects in descending order. We add to the front and drop |
| // from the rear. |
| FinalizableObject listHead; |
| |
| // A possibly incomplete list of FinalizableObject indices that were finalized, but |
| // have yet to be checked for consistency with reference processing. |
| ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000); |
| |
| // Maps from object number to Reference; Cleared references are deleted when queues are |
| // processed. |
| TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>(); |
| ConcurrentHashMap<Integer, MyPhantomReference> phantomRefs = new ConcurrentHashMap<>(); |
| |
| class FinalizableObject { |
| int n; |
| FinalizableObject next; |
| FinalizableObject(int num, FinalizableObject nextObj) { |
| n = num; |
| next = nextObj; |
| } |
| protected void finalize() { |
| if (!inPhantomRefs(n)) { |
| System.out.println("PhantomRef enqueued before finalizer ran"); |
| } |
| totalFinalized.incrementAndGet(); |
| if (!finalized.offer(n) && REPORT_DROPS) { |
| System.out.println("Dropped finalization of " + n); |
| } |
| } |
| } |
| ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>(); |
| class MyWeakReference extends WeakReference<FinalizableObject> { |
| int n; |
| MyWeakReference(FinalizableObject obj) { |
| super(obj, refQueue); |
| n = obj.n; |
| } |
| }; |
| class MyPhantomReference extends PhantomReference<FinalizableObject> { |
| int n; |
| MyPhantomReference(FinalizableObject obj) { |
| super(obj, refQueue); |
| n = obj.n; |
| } |
| } |
| boolean inPhantomRefs(int n) { |
| MyPhantomReference ref = phantomRefs.get(n); |
| if (ref == null) { |
| return false; |
| } |
| if (ref.n != n) { |
| System.out.println("phantomRef retrieval failed"); |
| } |
| return true; |
| } |
| |
| void CheckOKToClearWeak(int num) { |
| if (num > maxDropped) { |
| System.out.println("WeakRef to live object " + num + " was cleared/enqueued."); |
| } |
| int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS; |
| for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) { |
| if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) { |
| throw new AssertionError("MyWeakReference logic error!"); |
| } |
| // wr referent was dropped in same batch and precedes it in list. |
| if (wr.get() != null) { |
| // This violates the WeakReference spec, and can result in strong references |
| // to objects that have been cleaned. |
| System.out.println("WeakReference to " + wr.n |
| + " was erroneously cleared after " + num); |
| } |
| } |
| } |
| |
| void CheckOKToClearPhantom(int num) { |
| if (num > maxDropped) { |
| System.out.println("PhantomRef to live object " + num + " was enqueued."); |
| } |
| MyWeakReference wr = weakRefs.get(num); |
| if (wr != null && wr.get() != null) { |
| System.out.println("PhantomRef cleared before WeakRef for " + num); |
| } |
| } |
| |
| void emptyAndCheckQueues() { |
| // Check recently finalized objects for consistency with cleared references. |
| while (true) { |
| Integer num = finalized.poll(); |
| if (num == null) { |
| break; |
| } |
| MyWeakReference wr = weakRefs.get(num); |
| if (wr != null) { |
| if (wr.n != num) { |
| System.out.println("Finalization logic error!"); |
| } |
| if (wr.get() != null) { |
| System.out.println("Finalizing object with uncleared reference"); |
| } |
| } |
| CheckOKToClearWeak(num); |
| } |
| // Check recently enqueued references for consistency. |
| while (true) { |
| Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll(); |
| if (ref == null) { |
| break; |
| } |
| if (ref instanceof MyWeakReference) { |
| MyWeakReference wr = (MyWeakReference) ref; |
| if (wr.get() != null) { |
| System.out.println("WeakRef " + wr.n + " enqueued but not cleared"); |
| } |
| CheckOKToClearWeak(wr.n); |
| if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) { |
| System.out.println("Missing WeakReference: " + wr.n); |
| } |
| } else if (ref instanceof MyPhantomReference) { |
| MyPhantomReference pr = (MyPhantomReference) ref; |
| CheckOKToClearPhantom(pr.n); |
| if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) { |
| System.out.println("Missing PhantomReference: " + pr.n); |
| } |
| } else { |
| System.out.println("Found unrecognized reference in queue"); |
| } |
| } |
| } |
| |
| |
| /** |
| * Add n objects to the head of the list. These will be assigned the next n consecutive |
| * numbers after the current head of the list. |
| */ |
| void addObjects(int n) { |
| for (int i = 0; i < n; ++i) { |
| int me = nextAllocated++; |
| listHead = new FinalizableObject(me, listHead); |
| weakRefs.put(me, new MyWeakReference(listHead)); |
| phantomRefs.put(me, new MyPhantomReference(listHead)); |
| } |
| liveObjects += n; |
| } |
| |
| /** |
| * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects |
| * in the list. |
| */ |
| void dropObjects(int n) { |
| FinalizableObject list = listHead; |
| FinalizableObject last = null; |
| if (n > liveObjects) { |
| System.out.println("Removing too many elements"); |
| } |
| if (liveObjects == n) { |
| maxDropped = list.n; |
| listHead = null; |
| } else { |
| final int skip = liveObjects - n; |
| for (int i = 0; i < skip; ++i) { |
| last = list; |
| list = list.next; |
| } |
| int expected = nextAllocated - skip - 1; |
| if (list.n != expected) { |
| System.out.println("dropObjects found " + list.n + " but expected " + expected); |
| } |
| maxDropped = expected; |
| last.next = null; |
| } |
| liveObjects -= n; |
| } |
| |
| void testLoop() { |
| System.out.println("Starting"); |
| addObjects(MIN_LIVE_OBJS); |
| final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS; |
| for (int i = 0; i < ITERS; ++i) { |
| addObjects(DROP_OBJS); |
| if (liveObjects != MAX_LIVE_OBJS) { |
| System.out.println("Unexpected live object count"); |
| } |
| dropObjects(DROP_OBJS); |
| if (i % 100 == 0) { |
| // Make sure we don't fall too far behind, otherwise we may run out of memory. |
| System.runFinalization(); |
| } |
| emptyAndCheckQueues(); |
| } |
| dropObjects(MIN_LIVE_OBJS); |
| if (liveObjects != 0 || listHead != null) { |
| System.out.println("Unexpected live objecs at end"); |
| } |
| if (maxDropped != TOTAL_OBJS - 1) { |
| System.out.println("Unexpected dropped object count: " + maxDropped); |
| } |
| for (int i = 0; i < 2; ++i) { |
| Runtime.getRuntime().gc(); |
| System.runFinalization(); |
| emptyAndCheckQueues(); |
| } |
| if (!weakRefs.isEmpty()) { |
| System.out.println("Weak Reference map nonempty size = " + weakRefs.size()); |
| } |
| if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) { |
| try { |
| Thread.sleep(500); |
| } catch (InterruptedException e) { |
| System.out.println("Unexpected interrupt"); |
| } |
| if (!phantomRefs.isEmpty()) { |
| System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size()); |
| System.out.print("First elements:"); |
| int i = 0; |
| for (MyPhantomReference pr : phantomRefs.values()) { |
| System.out.print(" " + pr.n); |
| if (++i > 10) { |
| break; |
| } |
| } |
| System.out.println(""); |
| } |
| } |
| if (totalFinalized.get() != TOTAL_OBJS) { |
| System.out.println("Finalized only " + totalFinalized + " objects"); |
| } |
| } |
| |
| static Runnable causeGCs = new Runnable() { |
| public void run() { |
| // Allocate a lot. |
| BigInteger counter = BigInteger.ZERO; |
| while (!pleaseStop) { |
| counter = counter.add(BigInteger.TEN); |
| } |
| // Look at counter to reduce chance of optimizing out the allocation. |
| if (counter.longValue() % 10 != 0) { |
| System.out.println("Bad causeGCs counter value: " + counter); |
| } |
| } |
| }; |
| |
| public static void main(String[] args) throws Exception { |
| Main theTest = new Main(); |
| Thread gcThread = new Thread(causeGCs); |
| gcThread.setDaemon(true); // Terminate if main thread dies. |
| gcThread.start(); |
| theTest.testLoop(); |
| pleaseStop = true; |
| gcThread.join(); |
| System.out.println("Finished"); |
| } |
| } |