CleanupReference.java revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.common; 6 7import android.os.Handler; 8import android.os.Looper; 9import android.os.Message; 10import android.util.Log; 11 12import java.lang.ref.ReferenceQueue; 13import java.lang.ref.WeakReference; 14import java.util.HashSet; 15import java.util.Set; 16 17/** 18 * Handles running cleanup tasks when an object becomes eligible for GC. Cleanup tasks 19 * are always executed on the main thread. In general, classes should not have 20 * finalizers and likewise should not use this class for the same reasons. The 21 * exception is where public APIs exist that require native side resources to be 22 * cleaned up in response to java side GC of API objects. (Private/internal 23 * interfaces should always favor explicit resource releases / destroy() 24 * protocol for this rather than depend on GC to trigger native cleanup). 25 * NOTE this uses WeakReference rather than PhantomReference, to avoid delaying the 26 * cleanup processing until after finalizers (if any) have run. In general usage of 27 * this class indicates the client does NOT use finalizers anyway (Good), so this should 28 * not be a visible difference in practice. 29 */ 30public class CleanupReference extends WeakReference<Object> { 31 private static final String TAG = "CleanupReference"; 32 33 private static final boolean DEBUG = false; // Always check in as false! 34 35 // The VM will enqueue CleanupReference instance onto sGcQueue when it becomes eligible for 36 // garbage collection (i.e. when all references to the underlying object are nullified). 37 // |sReaperThread| processes this queue by forwarding the references on to the UI thread 38 // (via REMOVE_REF message) to perform cleanup. 39 private static ReferenceQueue<Object> sGcQueue = new ReferenceQueue<Object>(); 40 private static Object sCleanupMonitor = new Object(); 41 42 static private final Thread sReaperThread = new Thread(TAG) { 43 public void run() { 44 while (true) { 45 try { 46 CleanupReference ref = (CleanupReference) sGcQueue.remove(); 47 if (DEBUG) Log.d(TAG, "removed one ref from GC queue"); 48 synchronized (sCleanupMonitor) { 49 Message.obtain(sHandler, REMOVE_REF, ref).sendToTarget(); 50 // Give the UI thread chance to run cleanup before looping around and 51 // taking the next item from the queue, to avoid Message bombing it. 52 sCleanupMonitor.wait(500); 53 } 54 } catch (Exception e) { 55 Log.e(TAG, "Queue remove exception:", e); 56 } 57 } 58 } 59 }; 60 61 static { 62 sReaperThread.setDaemon(true); 63 sReaperThread.start(); 64 } 65 66 // Message's sent in the |what| field to |sHandler|. 67 68 // Add a new reference to sRefs. |msg.obj| is the CleanupReference to add. 69 private static final int ADD_REF = 1; 70 // Remove reference from sRefs. |msg.obj| is the CleanupReference to remove. 71 private static final int REMOVE_REF = 2; 72 73 /** 74 * This {@link Handler} polls {@link #sRefs}, looking for cleanup tasks that 75 * are ready to run. 76 */ 77 private static Handler sHandler = new Handler(Looper.getMainLooper()) { 78 @Override 79 public void handleMessage(Message msg) { 80 TraceEvent.begin(); 81 CleanupReference ref = (CleanupReference) msg.obj; 82 switch (msg.what) { 83 case ADD_REF: 84 sRefs.add(ref); 85 break; 86 case REMOVE_REF: 87 ref.runCleanupTaskInternal(); 88 break; 89 default: 90 Log.e(TAG, "Bad message=" + msg.what); 91 break; 92 } 93 94 if (DEBUG) Log.d(TAG, "will try and cleanup; max = " + sRefs.size()); 95 96 synchronized (sCleanupMonitor) { 97 // Always run the cleanup loop here even when adding or removing refs, to avoid 98 // falling behind on rapid garbage allocation inner loops. 99 while ((ref = (CleanupReference) sGcQueue.poll()) != null) { 100 ref.runCleanupTaskInternal(); 101 } 102 sCleanupMonitor.notifyAll(); 103 } 104 TraceEvent.end(); 105 } 106 }; 107 108 /** 109 * Keep a strong reference to {@link CleanupReference} so that it will 110 * actually get enqueued. 111 * Only accessed on the UI thread. 112 */ 113 private static Set<CleanupReference> sRefs = new HashSet<CleanupReference>(); 114 115 private Runnable mCleanupTask; 116 117 /** 118 * @param obj the object whose loss of reachability should trigger the 119 * cleanup task. 120 * @param cleanupTask the task to run once obj loses reachability. 121 */ 122 public CleanupReference(Object obj, Runnable cleanupTask) { 123 super(obj, sGcQueue); 124 if (DEBUG) Log.d(TAG, "+++ CREATED ONE REF"); 125 mCleanupTask = cleanupTask; 126 handleOnUiThread(ADD_REF); 127 } 128 129 /** 130 * Clear the cleanup task {@link Runnable} so that nothing will be done 131 * after garbage collection. 132 */ 133 public void cleanupNow() { 134 handleOnUiThread(REMOVE_REF); 135 } 136 137 private void handleOnUiThread(int what) { 138 Message msg = Message.obtain(sHandler, what, this); 139 if (Looper.myLooper() == sHandler.getLooper()) { 140 sHandler.handleMessage(msg); 141 msg.recycle(); 142 } else { 143 msg.sendToTarget(); 144 } 145 } 146 147 private void runCleanupTaskInternal() { 148 if (DEBUG) Log.d(TAG, "runCleanupTaskInternal"); 149 sRefs.remove(this); 150 if (mCleanupTask != null) { 151 if (DEBUG) Log.i(TAG, "--- CLEANING ONE REF"); 152 mCleanupTask.run(); 153 mCleanupTask = null; 154 } 155 clear(); 156 } 157} 158