leakcanary icon indicating copy to clipboard operation
leakcanary copied to clipboard

WeakHashMap enhancement and expand

Open EpicDima opened this issue 1 year ago • 1 comments

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

EpicDima avatar Feb 11 '24 02:02 EpicDima

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Feb 11 '24 02:02 CLAassistant

Thanks, I do want to take a closer look, check out the branch locally, figure out why it's failing etc.

pyricau avatar Mar 26 '24 17:03 pyricau

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.

pyricau avatar Mar 26 '24 17:03 pyricau

Updated the branch with cleanups and fixes.

pyricau avatar Mar 26 '24 18:03 pyricau