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