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