内存泄漏(Memory Leak)
每个应用程序都需要内存来完成工作,为了确保Android 系统的每个应用都有足够的内存,Android 系统需要有效地管理内存分配。当内存不足时,Android 运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法。
从上图可知,Obj4是可达的对象,表示它被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然相互引用,但是因为它们到GCRoots是不可达,所以它们仍旧被标记为可回收的对象。
内存泄露就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。或者是已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露。
此时,如果Obj4 是一个没用的对象,但它仍与GCRoots是可达的,那么Obj4就会发生内存泄漏。
内存泄漏产生的原因,主要分为三大类:
- 由开发人员自己编码造成的泄漏
- 第三方框架造成的泄漏
- 由Android 系统或者第三方ROM造成的泄漏
内存检测工具
内存性能分析器是 Android Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。
Memory Profiler
LeakCanary是Android的内存泄漏检测库,来缩小每次泄漏的原因。
LeakCanary
Resource Canary主要是用来检测Activit级别的内存泄漏、以及重复创建的冗余Bitmap。
ResourceCanary
内存泄漏的场景
非静态内部类的静态实例
非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接地长期维持着外部类的引用,阻止被系统回收。
private static Object inner;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
Button btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createInnerClass();
}
});
}
private void createInnerClass(){
inner=new Inner();
}
class Inner{}
当点击Button 时,会在createInnerClass() 方法里创建非静态内部类Inner的静态实例inner,该实例的生命周期会和应用程序一样长,并且一直持有当前Activity的引用,导致当前Activity无法被回收。
多线程相关的匿名内部类/非静态内部类
匿名内部类也会持有外部类实例的引用。多线程相关的Thread类和实现Runnable 接口的类等,它们的匿名内部类/非静态内部类如果做耗时操作就可能发生内存泄漏。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
Button btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new MyRunnable()).start();
finish();
}
});
}
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
Runnable使用了匿名内部类,那么它将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。解决方法就是使用静态内部类,这样便可以避免内存泄漏。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
Button btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new MyRunnable()).start();
finish();
}
});
}
static class MyRunnable implements Runnable {
@Override
public void run() {
}
}
Handler 内存泄漏
Handler的Message被存储在MessageQueue中,有些Message并不能马上处理掉,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler是非静态的,则Handler 也会导致引用它的Activity 或者Service 不能被回收。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
Button btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendMessageDelayed(Message.obtain(),60000);
finish();
}
});
}
Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Handler 是非静态的匿名内部类的实例,它会隐性引用外部类Activity。点击Button时,Activity会结束,但是Handler 中的消息还没有被处理,因此Activity无法被回收。解决方案如下:
MyHandler mHandler=new MyHandler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
Button btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendMessageDelayed(Message.obtain(),60000);
finish();
}
});
}
static class MyHandler extends Handler{
private WeakReference<TestActivity> mActivity;
public MyHandler(TestActivity activity){
mActivity=new WeakReference<TestActivity>();
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
}
@Override
public void onDestroy(){
if(mHandler!=null){
mHandler.removeCallbackAndMessages(null);
}
}
未正确使用Context
对于不是必须使用Activity的Context 的情况(Dialog 的Context 必须使用Activity的Context),可以考虑使用Application Context来替代Activity的Context,这样可以避免Activity 泄漏。
public class AppSetting{
private Context mAppContext;
private static AppSetting mAppSetting=new AppSetting();
pubic static AppSetting getInstance(){
return mAppSetting;
}
public final void setup(Context context){
mAppContext=context;
}
}
mAppSetting作为静态对象,其生命周期长于Activity。当进行屏幕旋转旋转时,系统会销毁当前Activity。因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity 被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄漏。解决办法就是使用Application的 Context。
public final void setup(Context context){
mAppContext=context.getApplicationContext();
}
静态View
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收。解决的方法就是在onDestory 方法中将静态View 置为null。
private static Button btn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acttivity_test_resource_trace);
btn=findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
@Override
public void onDestroy(){
btn=null;
}
WebView
WebView 存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主程序进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。
资源对象未关闭
资源对象比如File、Cursor等,往往都使用了缓冲,会造成内存泄漏。因此,在资源对象不使用时,一定要确保它们已经关闭并将它们的引用置为Null,通常在finally 语句中进行关闭,防止出现异常时,资源未被释放的问题。
集合中对象没清理
通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static 的话,那情况更加严重。
Bitmap 对象
临时创建的某个相对比较大的Bitmap对象,在经过变换得到 新的Bitmap 对象之后,应该尽快回收原始的Bitmap,这样能够更快释放原始Bitmap所占用的空间。避免静态变量持有比较大的Bitmap 对象或者其他大的数据对象,如果持有,要尽快置空该静态变量。
监听器未关闭
很多系统服务(比如TelephoneManager、SensorManager)需要register 和unregister 监听器,需要确保在合适的时候及时unregister监听器。自己手动添加的Listener,要记得在合适的时候及时移除这个Listener。
小结:内存泄漏最终导致内存溢出。
内存溢出(Out of Memory Error)
内存溢出是指当对象的内存占用已经超出分配内存的空间大小,这时未经处理的异常就会抛出。
内存溢出原因
由于我们程序的失误,长期保持某些资源(如Context)的引用,垃圾回收器就无法回收它。该对象占用的内存就无法被使用,这就造成内存泄露。
保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。
|