Map 导致的内存泄露

Map导致的内存泄露

    • 1.关于这个内存泄露场景,有诸多不解,特此记录
      • 1.1.测试内存泄露操作,及解决办法
      • 1.2.为了找到泄露的原因,途中发现一个不解之处,推测
    • 2.Map内存泄露原因包含
      • 2.1.Map是长生命周期,value含有短生命周期对象的强引用
      • 2.2.value未被移除
    • 3.使用LeakCanary找Map的内存泄露
    • 4.结束

1.关于这个内存泄露场景,有诸多不解,特此记录

场景简介:Disposable 存到 CompositeDisposable 内,CompositeDisposable 存到静态的Map中,造成内存泄露。

注:Map在单例类内和静态Map效果等同,都不会回收。

LeakCanaryActivity :

public class LeakCanaryActivity extends BaseActivity implements LocationListener {private Disposable disposable;private CompositeDisposable compositeDisposableNew2;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.leak_activity);compositeDisposableNew2 = new CompositeDisposable();Observable.just("1231").subscribe(new Observer<String>() {@Overridepublic void onSubscribe(Disposable d) {disposable = d;}@Overridepublic void onNext(String s) {}@Overridepublic void onError(Throwable e) {}@Overridepublic void onComplete() {}});findViewById(R.id.tv_test_4).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {LeakTextManager.getInstance().create(disposable);}});findViewById(R.id.tv_test_5).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {CompositeDisposable compositeDisposableNew = new CompositeDisposable();LeakTextManager.getInstance().setCompositeDisposableNew(compositeDisposableNew, disposable);}});findViewById(R.id.tv_test_6).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {LeakTextManager.getInstance().setCompositeDisposableNew(compositeDisposableNew2, disposable);}});}

LeakTextManager :

public class LeakTextManager {private static LeakTextManager instance;private LeakTextManager() {}public static LeakTextManager getInstance() {if (instance == null) {instance = new LeakTextManager();}return instance;}private HashMap<Object, CompositeDisposable> mMaps = new HashMap<>();private CompositeDisposable compositeDisposableNew;public void create(Disposable disposable) {compositeDisposableNew = new CompositeDisposable();compositeDisposableNew.add(disposable);mMaps.put("da", compositeDisposableNew);}public void setCompositeDisposableNew(CompositeDisposable compositeDisposableNew, Disposable disposable) {this.compositeDisposableNew = compositeDisposableNew;compositeDisposableNew.add(disposable);mMaps.put("2211d", compositeDisposableNew);}public void log() {System.out.println("compositeDisposableNew ==" + compositeDisposableNew);}}

1.1.测试内存泄露操作,及解决办法

有三个按钮,tv_test_4、tv_test_5、tv_test_6,点击其中一个,然后关闭Activity随即发生泄露。

2021-05-19 17:16:07.930 29687-31383/com.yoshin.tspsdk D/LeakCanary: ====================================HEAP ANALYSIS RESULT====================================1 APPLICATION LEAKSReferences underlined with "~~~" are likely causes.Learn more at https://squ.re/leaks.Signature: 27470564cfe5c45d246f5b39a145dac1e5b801┬───│ GC Root: Local variable in native code│├─ android.os.HandlerThread instance│    Leaking: NO (PathClassLoader↓ is not leaking)│    Thread name: 'LeakCanary-Heap-Dump'│    ↓ HandlerThread.contextClassLoader├─ dalvik.system.PathClassLoader instance│    Leaking: NO (LeakTextManager↓ is not leaking and A ClassLoader is never leaking)│    ↓ PathClassLoader.runtimeInternalObjects├─ java.lang.Object[] array│    Leaking: NO (LeakTextManager↓ is not leaking)│    ↓ Object[].[1638]├─ com.yoshin.tspsdk.leakcanary.LeakTextManager class│    Leaking: NO (a class is never leaking)│    ↓ static LeakTextManager.instance│                             ~~~~~~~~├─ com.yoshin.tspsdk.leakcanary.LeakTextManager instance│    Leaking: UNKNOWN│    ↓ LeakTextManager.compositeDisposableNew│                      ~~~~~~~~~~~~~~~~~~~~~~├─ io.reactivex.disposables.CompositeDisposable instance│    Leaking: UNKNOWN│    ↓ CompositeDisposable.resources│                          ~~~~~~~~~├─ io.reactivex.internal.util.OpenHashSet instance│    Leaking: UNKNOWN│    ↓ OpenHashSet.keys│                  ~~~~├─ java.lang.Object[] array│    Leaking: UNKNOWN│    ↓ Object[].[0]~~~├─ io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable instance│    Leaking: UNKNOWN│    ↓ ObservableScalarXMap$ScalarDisposable.observer│                                            ~~~~~~~~├─ com.yoshin.tspsdk.leakcanary.LeakCanaryActivity$5 instance│    Leaking: UNKNOWN│    Anonymous class implementing io.reactivex.Observer│    ↓ LeakCanaryActivity$5.this$0~~~~~~╰→ com.yoshin.tspsdk.leakcanary.LeakCanaryActivity instance​     Leaking: YES (ObjectWatcher was watching this because com.yoshin.tspsdk.leakcanary.LeakCanaryActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)​     key = 2cab45c2-8bbc-466e-b799-48f728159486​     watchDurationMillis = 5164​     retainedDurationMillis = 163====================================0 LIBRARY LEAKSLibrary Leaks are leaks coming from the Android Framework or Google libraries.====================================METADATAPlease include this in bug reports and Stack Overflow questions.Build.VERSION.SDK_INT: 29Build.MANUFACTURER: HUAWEILeakCanary version: 2.2App process name: com.yoshin.tspsdkAnalysis duration: 3369 msHeap dump file path: /data/user/0/com.yoshin.tspsdk/files/leakcanary/2021-05-19_17-16-02_919.hprofHeap dump timestamp: 1621415767928====================================

解决:
在退出activity之前把 “da”、“2211d” 从map中移除,使用的是 map.remove ,这样就不泄露了

1.2.为了找到泄露的原因,途中发现一个不解之处,推测

在查找泄露的途中,我修改了下面代码:

    public void create(Disposable disposable) {compositeDisposableNew = new CompositeDisposable();compositeDisposableNew.add(disposable);mMaps.put("da", compositeDisposableNew);}public void setCompositeDisposableNew(CompositeDisposable compositeDisposableNew, Disposable disposable) {this.compositeDisposableNew = compositeDisposableNew;compositeDisposableNew.add(disposable);mMaps.put("2211d", compositeDisposableNew);}

改成:

    public void create(Disposable disposable) {compositeDisposableNew = new CompositeDisposable();//compositeDisposableNew.add(disposable);mMaps.put("da", compositeDisposableNew);}public void setCompositeDisposableNew(CompositeDisposable compositeDisposableNew, Disposable disposable) {this.compositeDisposableNew = compositeDisposableNew;//compositeDisposableNew.add(disposable);mMaps.put("2211d", compositeDisposableNew);}

这个时候再进行测试,发现即使没有使用 map.remove ,内存泄露也不存在了。所以,我推测在不使用 map.remove 的情况下导致泄露的原因是: Disposable 被静态Map长期引用。

一般的类(CompositeDisposable )在被静态Map引用的时候,不会引起内存泄露,这是因为CompositeDisposable 并不和Activity的生命周挂钩;Disposable 被静态Map长期引用,引起内存泄露,是不是说明Disposable 其实是和Activity的生命周期有关,或者说隐式的持有Activity的引用呢?

Disposable 是个interface,不会找源代码,特此记录,希望大佬可以给指点~

2.Map内存泄露原因包含

2.1.Map是长生命周期,value含有短生命周期对象的强引用

Map是长生命周期:可以理解成Map是静态的,或者是在单例中;
短生命周期对象:指的是Activity或者Service或者其他,他的生命周期和上述的Map比,就是短生命周期
当短生命周期对象要回收,长生命周期持有短生命周期的强引用,就会让短生命周期对象的回收失败,因为Java中,只要一个对象被强引用引用,那么GC就不会回收它。

Map是短生命周期不会引起内存泄露:

public class MapTestModel {private Map<Object, View> map = new HashMap<>();private Map<Object, Context> mapC = new HashMap<>();public MapTestModel() {}public void add(View view) {map.put("haha", view);}public void addC(Context context) {mapC.put("haha", context);}}

在Activity中:

    MapTestModel mapTestModel = new MapTestModel();private void test_Model_view() {mapTestModel.add(textView);mapTestModel.addC(this);}

在Activity关闭的时候,这样不会引起内存泄露。

2.2.value未被移除

如我们上述的例子 1.1,因为Map中,含有Disposable ,所以需要使用 map.remove 。这种场景会有移除失败的风险:原因是Map的key是object,如果object发生变更,导致地址变更,那么移除的时候,就会移除失败。

HashMap内存泄漏场景:https://blog.csdn.net/u014389822/article/details/22424135?locationNum=2

3.使用LeakCanary找Map的内存泄露

当使用LeakCanary的时候,如果发现log上,有Map的value显示出来

    ├─ com.yoshin.tspsdk.leakcanary.LeakTextManager instance│    Leaking: UNKNOWN│    ↓ LeakTextManager.compositeDisposableNew│                      ~~~~~~~~~~~~~~~~~~~~~~

就可以考虑一下是不是上述原因。

因为存的这个value可能你都不知道是不是含有短生命周期的强引用~

4.结束

就这样了,等再有新的理解,再更新


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部