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;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.leak_activity);
compositeDisposableNew2 = new CompositeDisposable();
Observable.just("1231").subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(String s) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
findViewById(R.id.tv_test_4).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LeakTextManager.getInstance().create(disposable);
}
});
findViewById(R.id.tv_test_5).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CompositeDisposable compositeDisposableNew = new CompositeDisposable();
LeakTextManager.getInstance().setCompositeDisposableNew(compositeDisposableNew, disposable);
}
});
findViewById(R.id.tv_test_6).setOnClickListener(new View.OnClickListener() {
@Override
public 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 LEAKS
References 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 LEAKS
Library Leaks are leaks coming from the Android Framework or Google libraries.
====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: HUAWEI
LeakCanary version: 2.2
App process name: com.yoshin.tspsdk
Analysis duration: 3369 ms
Heap dump file path: /data/user/0/com.yoshin.tspsdk/files/leakcanary/2021-05-19_17-16-02_919.hprof
Heap 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发生变更,导致地址变更,那么移除的时候,就会移除失败。
3.使用LeakCanary找Map的内存泄露
当使用LeakCanary的时候,如果发现log上,有Map的value显示出来
├─ com.yoshin.tspsdk.leakcanary.LeakTextManager instance
│ Leaking: UNKNOWN
│ ↓ LeakTextManager.compositeDisposableNew
│ ~~~~~~~~~~~~~~~~~~~~~~
就可以考虑一下是不是上述原因。
因为存的这个value可能你都不知道是不是含有短生命周期的强引用~
4.结束
就这样了,等再有新的理解,再更新