xref: /aosp_15_r20/external/leakcanary2/leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 /*
2  * Copyright (C) 2015 Square, Inc.
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 package leakcanary
17 
18 import shark.SharkLog
19 import java.lang.ref.ReferenceQueue
20 import java.util.UUID
21 import java.util.concurrent.Executor
22 
23 /**
24  * [ObjectWatcher] can be passed objects to [watch]. It will create [KeyedWeakReference] instances
25  * that reference watches objects, and check if those references have been cleared as expected on
26  * the [checkRetainedExecutor] executor. If not, these objects are considered retained and
27  * [ObjectWatcher] will then notify registered [OnObjectRetainedListener]s on that executor thread.
28  *
29  * [checkRetainedExecutor] is expected to run its tasks on a background thread, with a significant
30  * delay to give the GC the opportunity to identify weakly reachable objects.
31  *
32  * [ObjectWatcher] is thread safe.
33  */
34 // Thread safe by locking on all methods, which is reasonably efficient given how often
35 // these methods are accessed.
36 class ObjectWatcher constructor(
37   private val clock: Clock,
38   private val checkRetainedExecutor: Executor,
39   /**
40    * Calls to [watch] will be ignored when [isEnabled] returns false
41    */
<lambda>null42   private val isEnabled: () -> Boolean = { true }
43 ) : ReachabilityWatcher {
44 
45   private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
46 
47   /**
48    * References passed to [watch].
49    */
50   private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
51 
52   private val queue = ReferenceQueue<Any>()
53 
54   /**
55    * Returns true if there are watched objects that aren't weakly reachable, and
56    * have been watched for long enough to be considered retained.
57    */
58   val hasRetainedObjects: Boolean
59     @Synchronized get() {
60       removeWeaklyReachableObjects()
<lambda>null61       return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
62     }
63 
64   /**
65    * Returns the number of retained objects, ie the number of watched objects that aren't weakly
66    * reachable, and have been watched for long enough to be considered retained.
67    */
68   val retainedObjectCount: Int
69     @Synchronized get() {
70       removeWeaklyReachableObjects()
<lambda>null71       return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
72     }
73 
74   /**
75    * Returns true if there are watched objects that aren't weakly reachable, even
76    * if they haven't been watched for long enough to be considered retained.
77    */
78   val hasWatchedObjects: Boolean
79     @Synchronized get() {
80       removeWeaklyReachableObjects()
81       return watchedObjects.isNotEmpty()
82     }
83 
84   /**
85    * Returns the objects that are currently considered retained. Useful for logging purposes.
86    * Be careful with those objects and release them ASAP as you may creating longer lived leaks
87    * then the one that are already there.
88    */
89   val retainedObjects: List<Any>
90     @Synchronized get() {
91       removeWeaklyReachableObjects()
92       val instances = mutableListOf<Any>()
93       for (weakReference in watchedObjects.values) {
94         if (weakReference.retainedUptimeMillis != -1L) {
95           val instance = weakReference.get()
96           if (instance != null) {
97             instances.add(instance)
98           }
99         }
100       }
101       return instances
102     }
103 
addOnObjectRetainedListenernull104   @Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
105     onObjectRetainedListeners.add(listener)
106   }
107 
removeOnObjectRetainedListenernull108   @Synchronized fun removeOnObjectRetainedListener(listener: OnObjectRetainedListener) {
109     onObjectRetainedListeners.remove(listener)
110   }
111 
112   /**
113    * Identical to [watch] with an empty string reference name.
114    */
115   @Deprecated(
116     "Add description parameter explaining why an object is watched to help understand leak traces.",
117     replaceWith = ReplaceWith(
118       "expectWeaklyReachable(watchedObject, \"Explain why this object should be garbage collected soon\")"
119     )
120   )
watchnull121   fun watch(watchedObject: Any) {
122     expectWeaklyReachable(watchedObject, "")
123   }
124 
125   @Deprecated(
126     "Method renamed expectWeaklyReachable() to clarify usage.",
127     replaceWith = ReplaceWith(
128       "expectWeaklyReachable(watchedObject, description)"
129     )
130   )
watchnull131   fun watch(
132     watchedObject: Any,
133     description: String
134   ) {
135     expectWeaklyReachable(watchedObject, description)
136   }
137 
138 
expectWeaklyReachablenull139   @Synchronized override fun expectWeaklyReachable(
140     watchedObject: Any,
141     description: String
142   ) {
143     if (!isEnabled()) {
144       return
145     }
146     removeWeaklyReachableObjects()
147     val key = UUID.randomUUID()
148       .toString()
149     val watchUptimeMillis = clock.uptimeMillis()
150     val reference =
151       KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
152     SharkLog.d {
153       "Watching " +
154         (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
155         (if (description.isNotEmpty()) " ($description)" else "") +
156         " with key $key"
157     }
158 
159     watchedObjects[key] = reference
160     checkRetainedExecutor.execute {
161       moveToRetained(key)
162     }
163   }
164 
165   /**
166    * Clears all [KeyedWeakReference] that were created before [heapDumpUptimeMillis] (based on
167    * [clock] [Clock.uptimeMillis])
168    */
clearObjectsWatchedBeforenull169   @Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
170     val weakRefsToRemove =
171       watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
172     weakRefsToRemove.values.forEach { it.clear() }
173     watchedObjects.keys.removeAll(weakRefsToRemove.keys)
174   }
175 
176   /**
177    * Clears all [KeyedWeakReference]
178    */
clearWatchedObjectsnull179   @Synchronized fun clearWatchedObjects() {
180     watchedObjects.values.forEach { it.clear() }
181     watchedObjects.clear()
182   }
183 
moveToRetainednull184   @Synchronized private fun moveToRetained(key: String) {
185     removeWeaklyReachableObjects()
186     val retainedRef = watchedObjects[key]
187     if (retainedRef != null) {
188       retainedRef.retainedUptimeMillis = clock.uptimeMillis()
189       onObjectRetainedListeners.forEach { it.onObjectRetained() }
190     }
191   }
192 
removeWeaklyReachableObjectsnull193   private fun removeWeaklyReachableObjects() {
194     // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
195     // reachable. This is before finalization or garbage collection has actually happened.
196     var ref: KeyedWeakReference?
197     do {
198       ref = queue.poll() as KeyedWeakReference?
199       if (ref != null) {
200         watchedObjects.remove(ref.key)
201       }
202     } while (ref != null)
203   }
204 }
205