Android内存泄漏

什么是内存泄漏?

生命周期较长的对象引用了生命周期较短的对象,导致生命周期较短的对象无法被GC及时回收,从而导致被占用的内存无法释放。

Failure to release unused objects from the memory
在这里插入图片描述

内存泄漏的危害

性能降低、导致OOM

从基础开始

RAM:Stack & Heap

Random access memory,是Android设备用于存储当前运行的应用程序及其数据的内存。RAM中有两个重要的角色:Stack、Heap。
在这里插入图片描述

Stack用于静态内存分配,Heap用于动态内存分配。

内存分配示例

在这里插入图片描述
接下来的图,代表了应用的Heap和Stack,展示了应用运行时每个对象指向和存储的位置:
在这里插入图片描述
在这里插入图片描述
Line 1 - JVM为main方法创建一个Stack内存
在这里插入图片描述
Line 2 - 创建一个局部变量,这个变量会在main方法的Stack中创建和存储
在这里插入图片描述
Line 3 - new了一个Object。创建的Object保存在Heap中,而Object在Heap中的内存地址保存在Stack中
在这里插入图片描述
Line 4 - 同Line 3
在这里插入图片描述
Line 5 - JVM为foo方法创建一个Stack内存
在这里插入图片描述
Line 6 - 在foo方法的Stack中创建一个Object,该Object存储在第5行传递的Object在Heap中的内存地址
在这里插入图片描述
Line 7 - 在foo方法的Stack中新建一个str,存储string pool在Heap中的内存地址
在这里插入图片描述
Line 8 - foo方法结束,在foo方法Stack中的Objects会被自动释放和回收
在这里插入图片描述
在这里插入图片描述
Line 9 - main方法结束,在main方法Stack中的Objects会被自动释放和回收
在这里插入图片描述

结论:Stack中的Object的存在是短暂的,方法结束后会被自动释放和回收。

Heap与Stack不同,如果要释放和回收Heap中的Objects,需要GC(Garbage Collector)的帮助。GC会自动检测无用的Objects,然后释放和回收。而GC是怎么工作的呢?

GC会寻找无用的或无法触达的Objects,当Heap中的某个Object无任何引用指向它的时候,GC会释放和回收它。
在这里插入图片描述
如图,GC Roots是引用树的根,树中的每个Object都有一个或多个根Object,只要应用程序或GC Roots可以触达那些Objects,就说明整棵树可触达。如果Object变得不可触达,将被视为无用的或无法触达的Object。

main方法结束,GC后:
在这里插入图片描述

常见内存泄漏

非静态内部类(匿名类)Leak

非静态内部类(匿名类)默认就持有外部类的引用,当非静态内部类(匿名类)对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。

  • Handler引起内存泄漏
    如果Handler中有 延迟任务 或者 等待执行的任务队列过长,可能因为Handler继续执行而导致Activity发生泄漏。
  • 非静态的Handler类会默认持有外部类的引用,如Activity等。
  • 还未处理完的消息(Message)中会持有Handler的引用。
  • 还未处理完的消息会处于消息队列中,即消息队列MessageQueue会持有Message的引用。
  • 消息队列MessageQueue位于Looper中,Looper的生命周期跟应用一致。
    引用链:Looper -> MessageQueue -> Message -> Handler -> Activity
    解决方法
  • 静态内部类+弱引用

静态内部类默认不持有外部类的引用,所以改成静态内部类即可。若Handler中需要Context,采用弱引用来持有Activity的引用。
在这里插入图片描述

  • Activity退出时,清除队列信息

清除队列信息后,Handler将会跟Activity生命周期同步。

在这里插入图片描述
2. 线程引起内存泄漏

在Activity销毁前,Thread的任务没有执行完毕。

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        new DownloadTask().start();
    }
    
    private class DownloadTask extends Thread {
        @Override
        public void run() {
           SystemClock.sleep(2000 * 10);
        }
    }
}

众所周知,内部类会持有外部类的引用,类似于:

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //....
        new DownloadTask(this).start();
    }
    
    private class DownloadTask extends Thread {
        
        private final ThreadActivity threadActivity;
        
        public DownloadTask(ThreadActivity threadActivity) {
            this.threadActivity = threadActivity;
        }

        @Override
        public void run() {
            SystemClock.sleep(2000 * 10);
        }
    }
}

正常流程:
在这里插入图片描述
在onCreate中start DownlaodTask后,线程在后台运行。用户等待20s,等 DownloadTask执行完,run()方法的stack会被释放,DownloadTask对外部类ThreadActivity的引用也会解除,如图:
在这里插入图片描述
当用户关闭ThreadActivity后,GC就可正常回收Heap中的DownloadTask和ThreadActivity。

异常流程:
若DownlaodTask未执行结束,用户关闭了ThreadActivity或屏幕旋转,导致ThreadActivity重建:
在这里插入图片描述
此时Stack释放了所有的Object,但是由于run()方法还在执行,DownloadTask无法释放。而DownloadTask持有外部类ThreadActivity的引用,导致GC无法从Heap中释放ThreadActivity,从而导致内存泄漏。若Activity引用了大量的UI资源,会造成严重的内存泄漏。

解决方法:

  • 静态内部类

静态内部类不持有外部类的引用

在这里插入图片描述

静态变量内存泄漏

静态变量的生命周期跟整个程序的生命周期一致。只要静态变量没有被销毁也没有置为null,其对象就一直被保持引用,也就不会被垃圾回收,从而出现内存泄漏。

  1. 静态变量持有Activity引用
public class MainActivity extends AppCompatActivity {
  private ActivityMainBinding binding;

  private static TestLeak sTest;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = ActivityMainBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());

    sTest = new TestLeak(this);
  }
public class TestLeak {
  private Context context;

  public TestLeak(Context context) {
    this.context = context;
  }
}

内存泄漏log:

 HEAP ANALYSIS RESULT
    ====================================
    1 APPLICATION LEAKS
    
    References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.
    
    127719 bytes retained by leaking objects
    Signature: f0d5226936119ad526c8706acdf0ccecdbf662fb
    ┬───
    │ GC Root: Local variable in native code
    │
    ├─ android.os.HandlerThread instance
    │    Leaking: NO (PathClassLoader↓ is not leaking)
    │    Thread name: 'LeakCanary-Heap-Dump'
    │    ↓ Thread.contextClassLoader
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (MainActivity↓ is not leaking and A ClassLoader is never leaking)
    │    ↓ ClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ Object[].[144]
    ├─ com.demo.leak.MainActivity class
    │    Leaking: NO (a class is never leaking)
    │    ↓ static MainActivity.sTest
    │                          ~~~~~
    ├─ com.demo.leak.TestLeak instance
    │    Leaking: UNKNOWN
    │    Retaining 127.7 kB in 2628 objects
    │    context instance of com.demo.leak.MainActivity with mDestroyed = true
    │    ↓ TestLeak.context
    │               ~~~~~~~
    ╰→ com.demo.leak.MainActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.demo.leak.MainActivity received Activity#onDestroy()
    ​     callback and Activity#mDestroyed is true)
    ​     Retaining 127.7 kB in 2627 objects
    ​     key = 99067c93-630f-4aca-a27e-926cd09f664d
    ​     watchDurationMillis = 5162
    ​     retainedDurationMillis = 158
    ​     mApplication instance of android.app.Application
    ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
    ====================================

解决方法:

  • 在静态变量无用时置为空
  • 使用Application的Context
  • 使用弱引用
WeakReference<Activity> weakReference = new WeakReference<>(this);
Activity activity = weakReference.get();
  1. 单例类

单例模式其生命周期跟应用一样,所以使用单例模式时传入的参数需要注意一下,避免传入Activity等对象造成内存泄漏。

public class Singleton {
  private static Singleton instance;
  private Context context;
  
  private Singleton(Context context) {
    this.context = context;
  }
  
  public static Singleton getInstance(Context context) {
    if (instance == null) {
      instance = new Singleton(context);
    }
    return instance;
  }
}

bindService/unbindService

若Activity中执行了bindService,而在Activity销毁时未执行unbindService,则造成内存泄漏。
说明:bindService可以跟Activity生命周期联动,在Activity销毁时系统会自动解绑Service(具体代码:LoadedApk.java的removeContextRegistrations()),但若未显式调用unbindService,仍然会内存泄漏。

324109 bytes retained by leaking objects
    Displaying only 1 leak trace out of 2 with the same signature
    Signature: 8420f46333a4dc28cc99f28a0bc2da2f3891f
    ┬───
    │ GC Root: System class
    │
    ├─ android.provider.FontsContract class
    │    Leaking: NO (Application↓ is not leaking and a class is never leaking)
    │    ↓ static FontsContract.sContext
    ├─ android.app.Application instance
    │    Leaking: NO (Application is a singleton)
    │    mBase instance of android.app.ContextImpl
    │    ↓ Application.mLoadedApk
    │                  ~~~~~~~~~~
    ├─ android.app.LoadedApk instance
    │    Leaking: UNKNOWN
    │    Retaining 341.2 kB in 7121 objects
    │    mApplication instance of android.app.Application
    │    Receivers
    │    ..Application@317996008
    │    ....VisibilityTracker@319156072
    │    ↓ LoadedApk.mServices
    │                ~~~~~~~~~
    ├─ android.util.ArrayMap instance
    │    Leaking: UNKNOWN
    │    Retaining 340.0 kB in 7100 objects
    │    ↓ ArrayMap.mArray
    │               ~~~~~~
    ├─ java.lang.Object[] array
    │    Leaking: UNKNOWN
    │    Retaining 340.0 kB in 7098 objects
    │    ↓ Object[].[0]
    │               ~~~
    ╰→ com.demo.leak.SettingsActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.demo.leak.SettingsActivity received
    ​     Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     Retaining 162.3 kB in 3366 objects
    ​     key = d6ccb489-a1a2-4196-b7ac-a591a81e5d5f
    ​     watchDurationMillis = 87872
    ​     retainedDurationMillis = 82869
    ​     mApplication instance of android.app.Application
    ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
    ====================================

解决方法:
Activity销毁时,显示调用unbindService

内存泄漏检测方法

LeakCanary

debugImplementation ‘com.squareup.leakcanary:leakcanary-android:2.7’
在这里插入图片描述

Profiler

查看MEMORY:
在这里插入图片描述
Capture heap dump:
在这里插入图片描述
在这里插入图片描述
Show activity/fragment Leaks:
在这里插入图片描述
Heamp Dump右侧四列的意思:
Allocations:Java堆中的实例个数
Native Size:Native层分配的内存大小
Shallow Size:Java堆中分配实际大小
Retained Size:这个类的所有实例保留的内存总大小(并非实际大小)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android内存泄露是指应用程序在使用完对象后,没有及时将其释放,导致这些对象无法被垃圾回收器回收,最终导致应用程序的内存占用不断增加,直至崩溃。以下是一些可能导致Android内存泄露的情况及解决方法: 1. 静态变量:如果在应用程序中使用了静态变量,并且这些变量引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的静态变量设为null。 2. 匿名内部类:如果在应用程序中使用了匿名内部类,并且这些内部类引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的匿名内部类引用设为null。 3. Handler:如果在应用程序中使用了Handler,并且这些Handler引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的Handler引用设为null。 4. Bitmap对象:如果在应用程序中使用了Bitmap对象,并且没有及时释放,就可能导致内存泄露。解决方法是在使用完Bitmap对象后,调用recycle()方法释放内存。 5. 资源对象:如果在应用程序中使用了资源对象,并且没有及时释放,就可能导致内存泄露。解决方法是在使用完资源对象后,调用其对应的释放方法,比如close()。 希望以上这些解决方法可以帮助您避免Android内存泄露的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值