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.结束
就这样了,等再有新的理解,再更新
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
