Android性能优化之内存泄漏及解决方案

举报
yd_221104950 发表于 2020/12/04 23:25:28 2020/12/04
【摘要】 内存泄漏的本质 申请了的内存在不再使用时无法回收。 Android应用程序内存泄漏的含义 Android系统为每个应用程序都分配了相应限额的内存。当应用程序中产生的内存泄漏较多时,将会导致应用程序运行所需要的内存超过系统为其分配的限额,这时应用程序就会Crash(崩溃)。 常见引发内存泄漏的情况 集合类static关键字修饰的成员变量非静态内部类 / 匿名类资...

内存泄漏的本质

申请了的内存在不再使用时无法回收。

Android应用程序内存泄漏的含义

Android系统为每个应用程序都分配了相应限额的内存。当应用程序中产生的内存泄漏较多时,将会导致应用程序运行所需要的内存超过系统为其分配的限额,这时应用程序就会Crash(崩溃)。

常见引发内存泄漏的情况

  • 集合类
  • static关键字修饰的成员变量
  • 非静态内部类 / 匿名类
  • 资源对象使用后未关闭

引发内存泄漏的具体情况及解决方案

(1)集合类

具体表现
集合List仍然引用该对象,所以垃圾回收器GC依然不可回收该对象

 // 通过循环申请Object对象和将申请的对象逐个放入到集合List List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object o = new Object(); objectList.add(o); o = null; } // 虽释放了集合元素引用的本身:o=null) // 但集合List仍然引用该对象,故垃圾回收器GC依然不可回收该对象

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

解决方案
集合类添加集合元素对象后,在使用后必须从集合中删除。

 // 集合类添加集合元素对象后,在使用后必须从集合中删除 // 释放objectList objectList.clear(); objectList=null;

  
 
  • 1
  • 2
  • 3
  • 4

完整例子

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 集合引发的内存溢出 // 通过循环申请Object对象和将申请的对象逐个放入到集合List List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object o = new Object(); objectList.add(o); o = null; } // 虽释放了集合元素引用的本身:o=null) // 但集合List仍然引用该对象,故垃圾回收器GC依然不可回收该对象 // 解决方案 // 集合类添加集合元素对象后,在使用后必须从集合中删除 // 释放objectList objectList.clear(); objectList=null; }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

(2)static关键字修饰的成员变量

具体表现:
被static关键字修饰的成员变量的生命周期等于应用程序的生命周期。如果static关键字修饰的成员变量引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄漏,如:

public class StaticReferenceClass { // 定义1个静态变量 private static Context mContext; public StaticReferenceClass(Context context) { // 引用的是Activity的context mContext = context; } // 当Activity需销毁时,由于mContext的生命周期等于应用程序的生命周期,所以Activity无法被回收,从而出现内存泄露。
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

解决方案:
方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context。

// 方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context
StaticReferenceClass staticReferenceClass = new StaticReferenceClass(getApplication());

  
 
  • 1
  • 2

方法二: 使用弱引用(WeakReference)代替强引用持有实例

public class StaticReferenceClass { // 定义1个静态变量 // 使用弱引用(WeakReference) 代替 强引用 持有实例 private static WeakReference<Context> mContext; public StaticReferenceClass(Context context) { // 引用的是Activity的context mContext = new WeakReference<>(context); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

特殊例子——单例: 单例的生命周期等于应用程序的生命周期。
如果一个对象已不需再使用,而单例对象却还继续持有该对象的引用时,那么该对象将不能被正常回收,从而导致内存泄漏。如:

import android.content.Context;
public class MySingleClass {

// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM private static MySingleClass ourInstance; private Context mContext; public static MySingleClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new MySingleClass(context); } return ourInstance; } private MySingleClass(Context context) { this.mContext = context; // 传递的是Activity的context }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

单例的解决办法:
因为单例模式引用的对象的生命周期等于应用的生命周期。所以应传递Application的Context,因Application的生命周期等于整个应用的生命周期,解决后的代码如下:

import android.content.Context;
public class MySingleClass {

// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM private static MySingleClass ourInstance; private Context mContext; public static MySingleClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new MySingleClass(context); } return ourInstance; } private MySingleClass(Context context) { this.mContext = context.getApplicationContext(); // 传递的是Activity的context }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

(3)非静态内部类 / 匿名类

非静态内部类 / 匿名类默认持有外部类的引用;而静态内部类则不会。有三类情况:

  • 非静态内部类的实例是静态引起的
  • 多线程以非静态内部类或匿名类使用引起的
  • 资源没有主动释放或注销引起的

1.当非静态内部类的实例是静态时
如果非静态内部类所创建的实例是静态的,那么它的生命周期等于应用的生命周期,又因为非静态内部类默认持有外部类的引用,所以会导致外部类无法释放,最终造成内存泄漏,如:
具体表现:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity { // 非静态内部类的实例的引用 // 注:设置为静态实例 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 保证非静态内部类的实例只有1个 if (innerClass == null) { innerClass = new InnerClass(); } } // 非静态内部类的定义 private class InnerClass { //... }

}
// 造成内存泄露的原因:
// a. 当MainActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期等于应用App的生命周期,持有外部类MainActivity的引用
// b. 所以MainActivity无法被GC回收,从而导致内存泄漏


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

解决方案:

  • 1.将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
public class MainActivity extends AppCompatActivity { // 非静态内部类的实例的引用 // 注:设置为静态 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 保证非静态内部类的实例只有1个 if (innerClass == null) { innerClass = new InnerClass(); } } // 非静态内部类的定义 private static class InnerClass { //... }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 2.将该内部类抽取出来封装成一个单例
public class InnerClass { private static final InnerClass ourInstance = new InnerClass(); public static InnerClass getInstance() { return ourInstance; } private InnerClass() {}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 3.尽量避免非静态内部类所创建的实例是静态的。若需使用Context,建议使用Application的 Context。

import android.content.Context;
public class InnerClass { private static InnerClass ourInstance = null; private Context mContext; public static InnerClass getInstance() { return ourInstance; } public static InnerClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new InnerClass(context); } return ourInstance; } private InnerClass(Context context) { this.mContext = context.getApplicationContext(); // 传递的是Activity的context }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.多线程:AsyncTask、实现Runnable接口、继承Thread类
当AsyncTask、实现Runnable接口、 继承Thread类等多线程以非静态内部类 / 匿名类(即线程类属于非静态内部类/匿名类)方式来使用时,当工作线程正在处理任务,而外部类需销毁时,由于工作线程实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄漏。以Thread类来举例,其他依次类推。
具体表现:

// 非静态内部类方式
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过创建的内部类 实现多线程 new InnerClassThread().start(); } // 自定义的Thread子类 private class InnerClassThread extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
// 匿名内部类方式
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过匿名内部类 实现多线程 new Thread(new Runnable(){ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

分析
工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用,当工作线程运行时,如果外部类MainActivity需销毁时,将会因为此时工作线程类实例持有外部类的引用,而使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄漏。
解决方案:
a.使用静态内部类:静态内部类默认不持有外部类的引用,将Thread的子类设置成静态内部类,如:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过创建的内部类,实现多线程 new InnerClassThread().start(); } // 自定义的Thread子类 private static class InnerClassThread extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

b.当外部类结束生命周期时,强制结束线程
使得工作线程实例的生命周期与外部类的生命周期同步,具体做法是当外部类结束生命周期时,强制结束线程。但是Thread.stop这个方法官方已标识为弃用状态,所以比较好的办法就是让线程自己停止自己。有两种处理方法:1、让线程任务执行完成,顺利结束退出。2、设置终止标志位,在循环的时候进行终止标志位检测,如果设置为终止状态则return结束线程,如:


public class MainActivity extends AppCompatActivity { private boolean isStop = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 执行多线程 new Thread(new Runnable() { @Override public void run() { while(true){ if(isStop){//当需要结束线程的时候终止线程 //doSomething  进行一些收尾工作 return; } System.out.println(Thread.currentThread().getName()); } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); isStop = true; }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这个标识位的放置的位置是很值得考虑的,你应该将它放到一个可以保证原子操作的地方。

(4)消息传递机制:Handler引发的内存溢出
请参考《Android性能优化之Handler内存溢出》
(5)资源对象使用后未关闭
对于资源如广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等的使用,如果在Activity销毁时,没有及时关闭或注销它们,那么这些资源将不会被回收,从而造成内存泄漏。
解决方案
在Activity销毁时 及时关闭或注销这些资源,如:

 @Override protected void onDestroy() { super.onDestroy(); // 对于广播BraodcastReceiver:注销注册 unregisterReceiver() // 对于文件流File:关闭流 InputStream / OutputStream.close() // 对于数据库游标cursor:使用后关闭游标 cursor.close() // 对于图片资源Bitmap:Android分配给图片的内存只有8M,如果1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null  Bitmap.recycle(); Bitmap = null; // 对于动画(属性动画) // 将动画设置成无限循环播放repeatCount = “infinite”后 // 在Activity退出时记得停止动画 }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

其他导致内存溢出的情况

  • 不再使用的WebView对象,如果不销毁,将一直占用着内存,无法回收,将导致内存溢出。
    解决方法:在不使用时,要主动销毁WebView对象。

谢谢阅读。

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/96289318

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。