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