申请了的内存在不再使用时无法回收。
Android系统为每个应用程序都分配了相应限额的内存。当应用程序中产生的内存泄漏较多时,将会导致应用程序运行所需要的内存超过系统为其分配的限额,这时应用程序就会Crash(崩溃)。
具体表现:
集合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依然不可回收该对象
解决方案:
集合类添加集合元素对象后,在使用后必须从集合中删除。
// 集合类添加集合元素对象后,在使用后必须从集合中删除
// 释放objectList
objectList.clear();
objectList=null;
完整例子:
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;
}
}
具体表现:
被static关键字修饰的成员变量的生命周期等于应用程序的生命周期。如果static关键字修饰的成员变量引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄漏,如:
public class StaticReferenceClass {
// 定义1个静态变量
private static Context mContext;
public StaticReferenceClass(Context context) {
// 引用的是Activity的context
mContext = context;
}
// 当Activity需销毁时,由于mContext的生命周期等于应用程序的生命周期,所以Activity无法被回收,从而出现内存泄露。
}
解决方案:
方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context。
// 方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context
StaticReferenceClass staticReferenceClass = new StaticReferenceClass(getApplication());
方法二: 使用弱引用(WeakReference)代替强引用持有实例
public class StaticReferenceClass {
// 定义1个静态变量
// 使用弱引用(WeakReference) 代替 强引用 持有实例
private static WeakReference<Context> mContext;
public StaticReferenceClass(Context context) {
// 引用的是Activity的context
mContext = new WeakReference<>(context);
}
}
特殊例子——单例: 单例的生命周期等于应用程序的生命周期。
如果一个对象已不需再使用,而单例对象却还继续持有该对象的引用时,那么该对象将不能被正常回收,从而导致内存泄漏。如:
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
}
}
单例的解决办法:
因为单例模式引用的对象的生命周期等于应用的生命周期。所以应传递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.当非静态内部类的实例是静态时
如果非静态内部类所创建的实例是静态的,那么它的生命周期等于应用的生命周期,又因为非静态内部类默认持有外部类的引用,所以会导致外部类无法释放,最终造成内存泄漏,如:
具体表现:
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回收,从而导致内存泄漏
解决方案:
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 {
//...
}
}
public class InnerClass {
private static final InnerClass ourInstance = new InnerClass();
public static InnerClass getInstance() {
return ourInstance;
}
private InnerClass() {}
}
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
}
}
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();
}
}
分析
工作线程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();
}
}
}
}
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;
}
}
这个标识位的放置的位置是很值得考虑的,你应该将它放到一个可以保证原子操作的地方。
(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退出时记得停止动画
}
谢谢阅读。