leakcanary
leakcanary copied to clipboard
WeakHashMap enhancement and expand
https://github.com/square/leakcanary/issues/2463 solution
Before:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 215.8 kB in 3085 objects
│ ↓ WeakHashMap.table
│ ~~~~~
├─ java.util.WeakHashMap$Entry[] array
│ Leaking: UNKNOWN
│ Retaining 215.8 kB in 3082 objects
│ ↓ WeakHashMap$Entry[0]
│ ~~~
├─ java.util.WeakHashMap$Entry instance
│ Leaking: UNKNOWN
│ Retaining 215.7 kB in 3081 objects
│ referent instance of com.example.leakcanary.DebugExampleApplication
│ ↓ WeakHashMap$Entry.value
│ ~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.7 kB in 3080 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = 31142903-21db-4a7a-bc0a-541cba0a2ee8
watchDurationMillis = 7289
retainedDurationMillis = 2283
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
After:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 215.8 kB in 3085 objects
│ ↓ WeakHashMap[instance @315631192 of com.example.leakcanary.DebugExampleApplication]
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.7 kB in 3080 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = 033292c1-9c67-49b5-bf0d-7b738024aa9b
watchDurationMillis = 8698
retainedDurationMillis = 3691
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
Before:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 216.4 kB in 3104 objects
│ ↓ WeakHashMap.queue
│ ~~~~~
├─ java.lang.ref.ReferenceQueue instance
│ Leaking: UNKNOWN
│ Retaining 28 B in 2 objects
│ ↓ ReferenceQueue.head
│ ~~~~
├─ java.util.WeakHashMap$Entry instance
│ Leaking: UNKNOWN
│ Retaining 72 B in 2 objects
│ ↓ WeakHashMap$Entry.value
│ ~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.6 kB in 3079 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = e547b1e4-759a-48b6-8e70-52693714f24d
watchDurationMillis = 83313
retainedDurationMillis = 78306
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
After:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 216.6 kB in 3111 objects
│ ↓ WeakHashMap[null]
│ ~~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.7 kB in 3080 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = 1d2f976d-d14a-4165-b655-9ae3f8ed2096
watchDurationMillis = 8204
retainedDurationMillis = 3197
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
Before:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 216.5 kB in 3108 objects
│ ↓ WeakHashMap.queue
│ ~~~~~
├─ java.lang.ref.ReferenceQueue instance
│ Leaking: UNKNOWN
│ Retaining 28 B in 2 objects
│ ↓ ReferenceQueue.tail
│ ~~~~
├─ java.util.WeakHashMap$Entry instance
│ Leaking: UNKNOWN
│ Retaining 36 B in 1 objects
│ ↓ WeakHashMap$Entry.value
│ ~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.7 kB in 3080 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = f8a44ae6-6046-4cec-a5ac-febe0156c454
watchDurationMillis = 7194
retainedDurationMillis = 2188
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
After:
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (DebugExampleApplication↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.DebugExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│ Leaking: UNKNOWN
│ Retaining 216.6 kB in 3111 objects
│ ↓ WeakHashMap[null]
│ ~~~~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 215.7 kB in 3080 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.helper_text
│ View.mWindowAttachCount = 1
│ mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.example.leakcanary.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.5 kB in 73 objects
key = 1d2f976d-d14a-4165-b655-9ae3f8ed2096
watchDurationMillis = 8204
retainedDurationMillis = 3197
mApplication instance of com.example.leakcanary.DebugExampleApplication
mBase instance of android.app.ContextImpl
Thanks, I do want to take a closer look, check out the branch locally, figure out why it's failing etc.
The failure has to do with the dynamic nature of WeakHashMap: the tests are adding a non retained key the map, so depending on how much work the GC is doing, sometimes the entry will still be in the map and sometimes not.
Updated the branch with cleanups and fixes.