概述
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
首先使用leakcanary来检测内存泄漏
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
使用当前版本的leakcanary无需install直接可以使用(下一篇文章将会具体讲解leakcanary的使用及其原理)
第一种非静态内部类创建静态实例导致的内存泄漏
非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
public class TestLeakActivity extends Activity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// MyApplication.getRefWatcher(this).watch(this);
setContentView(R.layout.activity_test_leak);
initData();
}
private void initData() {
if (mResource == null){
mResource = new TestResource();
}
findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private class TestResource{
}
}
当应用启动之后,关闭按钮发现leakcanary打印出如下(No是没有Yes是有内存的泄漏,UNKNOWN表示可能出现了内存泄漏)
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
105602 bytes retained by leaking objects
Signature: d8d8d1ffbf342e9bb5d82c1f67b33795b6105
┬───
│ 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 (TestLeakActivity↓ is not leaking and A ClassLoader is never leaking)
│ ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (TestLeakActivity↓ is not leaking)
│ ↓ Object[].[519]
├─ com.example.myapplication.TestLeakActivity class
│ Leaking: NO (a class is never leaking)
│ ↓ static TestLeakActivity.mResource
│ ~~~~~~~~~
├─ com.example.myapplication.TestLeakActivity$TestResource instance
│ Leaking: UNKNOWN
│ Retaining 105614 bytes in 1636 objects
│ this$0 instance of com.example.myapplication.TestLeakActivity with mDestroyed = true
│ ↓ TestLeakActivity$TestResource.this$0
│ ~~~~~~
╰→ com.example.myapplication.TestLeakActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 105602 bytes in 1635 objects
key = 6382ce72-b981-4a84-a5f7-fd1971479092
watchDurationMillis = 12949
retainedDurationMillis = 7919
mApplication instance of com.example.myapplication.MyApplication
mBase instance of android.app.ContextImpl, not wrapping known Android context
====================================
0 LIBRARY LEAKS
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
====================================
从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。
打印出来的log发现在TestLeakAcitvity执行完ondestory是有一个UNKNOWN内存的泄漏TestResource.this
解决方案
可以将非静态内部类改成静态内部类
private static class TestResource{
}
第二种单例模式导致的内存泄漏
创建一个单例
public class Singleton {
private static Singleton sInstance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (sInstance == null) {
sInstance = new Singleton(context);
}
return sInstance;
}
public void test(){
mContext.getContentResolver();
}
}
在Activity中执行
public class TestLeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
Singleton.getInstance(this).test();
}
}
执行完单例之后看一下leakcanary
LeakCanary: 1 APPLICATION LEAKS
LeakCanary: ┬───
LeakCanary: │ GC Root: Local variable in native code
LeakCanary: │ ...
LeakCanary: ├─ com.example.myapplication.Singleton instance
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ Retaining 112915 bytes in 1674 objects
LeakCanary: │ mContext instance of com.example.myapplication.TestLeakActivity with mDestroyed = true
LeakCanary: │ ↓ Singleton.mContext
LeakCanary: │ ~~~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received
LeakCanary: Activity#onDestroy() callback and Activity#mDestroyed is true)
LeakCanary: Retaining 112903 bytes in 1673 objects
LeakCanary: key = 297d72a4-5e9d-41bf-baba-6856105c73f0
LeakCanary: watchDurationMillis = 5176
LeakCanary: retainedDurationMillis = 139
LeakCanary: mApplication instance of com.example.myapplication.MyApplication
LeakCanary: mBase instance of android.app.ContextImpl, not wrapping known Android context
LeakCanary: ====================================
LeakCanary: 0 LIBRARY LEAKS
发现UNKNOW 出现地方为Singleton中的mContext,说明当前的mContext可能没有释放掉,但是后续又看到YES说明当前确实没有释放掉
解决方案
将context变成ApplicationContext,当应用关掉之后,会自动回收ApplicationContext
private Singleton(Context context) {
this.mContext = context.getApplicationContext();
}
第三种动画
public class TestLeakActivity extends Activity {
private ImageView mAlphaImage;
private ValueAnimator warningAnimation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
initData();
startValueAnimatorAnim(mAlphaImage);
}
private void initData() {
mAlphaImage = (ImageView)findViewById(R.id.alphaImage);
findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
}
/**
* 在一段时间内生成连续的值完成view的缩放
* @param v
*/
public void startValueAnimatorAnim(final View v) {
//不改变属性大小,只在一段事件内生成连续的值
ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
animator.setDuration(50000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//百分比对应的值
float value = (float) animation.getAnimatedValue();
v.setScaleX(0.5f + value / 200);
v.setScaleY(0.5f + value / 200);
}
});
animator.start();
}
}
发生的条件是在50秒内关闭当前应用,就会出现内存泄漏
LeakCanary: Found 1 paths to retained objects
...
LeakCanary: ┬───
LeakCanary: │ GC Root: System class
LeakCanary: │
LeakCanary: │ ...
LeakCanary: ├─ com.example.myapplication.TestLeakActivity$3 instance
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
LeakCanary: │ ↓ TestLeakActivity$3.this$0
LeakCanary: │ ~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
说明该动画的AnimatorUpdateListener 监听还持有TestLeakActivity
解决方案
@Override
protected void onDestroy() {
super.onDestroy();
if (animator!=null){
animator.cancel();
}
}
原因是当前还持有事件监听的引用,执行完cancel会去清除当前监听的引用。
第四种Handler导致的内存泄漏
本地测试发现,这种是不一定会内存泄漏的,除非MessageQueue还持有当前应用的activity 才会内存泄漏,具体出现原因以及修改方案我从网上找到一篇写的比较好的文章
路径如下(Handler为什么可能会造成内存泄漏以及可用的四种解决方法)
第五种匿名内部类引起的内存泄漏
public class TestLeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
发生条件在15秒内关闭程序就会发生内存泄漏
LeakCanary: 1 APPLICATION LEAKS
LeakCanary: ┬───
LeakCanary: │ GC Root: Local variable in native code
LeakCanary: │
LeakCanary: ├─ java.lang.Thread instance
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ Thread name: 'Thread-1'
LeakCanary: │ ↓ Thread.target
LeakCanary: │ ~~~~~~
LeakCanary: ├─ com.example.myapplication.TestLeakActivity$1 instance
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ Anonymous class implementing java.lang.Runnable
LeakCanary: │ ↓ TestLeakActivity$1.this$0
LeakCanary: │ ~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
说明当前Runnable 还持有TestLeakActivity
解决方案
public class TestLeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
new Thread(new MyRunnable()).start();
}
private static class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
静态内存类不会持有当前的引用
推荐内部类(AsyncTask Timer 等内存泄漏文章例子:android-内部类导致的内存泄漏实战解析)
第六种对象的注册但是没有反注册导致的内存泄漏
public class TestLeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
initData();
}
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.i("LeakCanary", "message is " + event.getMessage());
// 更新界面
textView.setText(event.getMessage());
}
private void initData() {
textView = (TextView)findViewById(R.id.text);
findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
onBackPressed();
}
});
}
}
发生条件注册EventBus 但是没有反注册
D LeakCanary: 1 APPLICATION LEAKS
D LeakCanary: ┬───
D LeakCanary: │ GC Root: Local variable in native code
D LeakCanary: ├─ org.greenrobot.eventbus.EventBus class
D LeakCanary: │ Leaking: NO (a class is never leaking)
D LeakCanary: │ ↓ static EventBus.defaultInstance
D LeakCanary: │ ~~~~~~~~~~~~~~~
D LeakCanary: ├─ org.greenrobot.eventbus.EventBus instance
D LeakCanary: │ Leaking: UNKNOWN
D LeakCanary: │ ↓ EventBus.typesBySubscriber
D LeakCanary: │ ~~~~~~~~~~~~~~~~~
D LeakCanary: ├─ java.util.HashMap instance
D LeakCanary: │ Leaking: UNKNOWN
D LeakCanary: │ ↓ HashMap.table
D LeakCanary: │ ~~~~~
D LeakCanary: ├─ java.util.HashMap$Node[] array
D LeakCanary: │ Leaking: UNKNOWN
D LeakCanary: │ ↓ HashMap$Node[].[0]
D LeakCanary: │ ~~~
D LeakCanary: ├─ java.util.HashMap$Node instance
D LeakCanary: │ Leaking: UNKNOWN
D LeakCanary: │ ↓ HashMap$Node.key
D LeakCanary: │ ~~~
D LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
D LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
解决方案
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
像这种反注册的例子(广播,otto,以及ContentProvider等等,都需要在onDestory里面反注册)
第七种静态集合引发的内存泄漏
public class TestLeakActivity extends Activity {
private static List<Activity> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_leak);
list = new ArrayList<>();
for (int i=0;i<100;i++){
list.add(this);
}
}
}
当关闭应用程序发生内存泄漏
LeakCanary: 1 APPLICATION LEAKS
LeakCanary: ┬───
LeakCanary: │ GC Root: Local variable in native code
LeakCanary: ├─ java.util.ArrayList instance
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ ↓ ArrayList.elementData
LeakCanary: │ ~~~~~~~~~~~
LeakCanary: ├─ java.lang.Object[] array
LeakCanary: │ Leaking: UNKNOWN
LeakCanary: │ ↓ Object[].[0]
LeakCanary: │ ~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
发现当前ArrayList持有Activity
解决方案
protected void onDestroy() {
if (list!=null){
list.clear();
}
}
第八种资源未关闭
本地测试没有复现出来内存泄漏,可能是我的资源比较少,或者比较小,释放的快
在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理。
以上就是总结的内存泄漏!下一章将讲解LeakCanary的使用,及其原理