赞
踩
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你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的使用,及其原理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。