什么是内存泄漏:
内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。
Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。
上图中的绿色部分,即是内存可以回收的部分。
内存泄漏分类一:activity关闭时资源无法回收
(activity内的变量生命周期超越了Activity,对象被生命周期长的对象引用,activity被静态集合引用导致activity不能释放)activity关闭时资源无法回收(activity内的变量生命周期超越了Activity,对象被生命周期长的对象引用,activity被静态集合引用导致activity不能释放)
1.单例造成的内存泄漏(Application和Activity的context生命周期问题)
举例
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance() {//此处修改了原文代码,请留意
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
分析
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
case1 传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;
case2 传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存应该需要被回收,但是又因为(生命周期和Application一样长的)单例对象持有该Activity的引用导致Activity无法回收。
关于Application的context与Activity的context的使用,请见鸿神的博客
如果采用上面的方式,在Activity1中这样使用:
AppManager.getInstance(this);
就会有问题,应该这样使用
AppManager.getInstance(this.getApplicationContext());
然而,好的程序不应该让调用者来决定究竟应该用什么参数。因此最好的修改是如下:
解决
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
2.使用静态的Android控件
举例
public class Static_Leak extends AppCompatActivity {
static TextView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_static__leak);
view = (TextView) findViewById(R.id.text);
}
}
分析
static变量在内存中是单独存在于内存块中的,控件(上面的例子的问题中指的是TextView )是持有Activity的引用的,这种情况下,Activity是没法被彻底销毁的,因为在内存中一直有一个引用(TextView 指向Activity),导致Activity也无法被回收,自然就会内存泄漏了
解决
方法1.不用static修饰
方法2.使用完回收,将View 指向Activity的引用去除
方法2的例子:
public class Static_Leak extends AppCompatActivity {
static TextView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_static__leak);
view = (TextView) findViewById(R.id.text);
}
@Override
protected void onDestroy() {
super.onDestroy();
view = null;
}
}
下面的三种情况都是非静态内部类导致的内存泄漏(都是非静态内部类引用了Activity导致Activity无法回收)
3.非静态内部类创建静态实例造成的内存泄漏(非静态内部类引发的血案)
举例
public class Static_Inner_leak extends AppCompatActivity {
static Demo sInstance = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_static__inner_leak);
if (sInstance == null) {
sInstance = new Demo();
}
}
class Demo {
void doSomething() {
}
}
}
分析
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏。因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
关于非静态内部类默认会持有外部类的引用,请看这里。
解决
正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext(原因见单例的内存泄漏) 。
4.线程造成的内存泄漏(非静态内部类引发的血案)
举例
public class Thread_leak extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thrad_leak);
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
}
}
分析
上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
解决
public class Thread_leak extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thrad_leak);
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
}
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
}
5.Handler造成的内存泄漏(非静态内部类引发的血案)
参见我的上一篇博客。
可见,三个非静态内部导致的内存泄漏的例子,解决方案都是改成静态内部类,让这个类不依赖Activity的生命周期(如果需要还要使用弱引用,还有注意Application和Activity的context的生命周期)。
内存泄漏分类二:没有回收,释放,解绑,清理等等
- 资源对象没关闭造成的内存泄露
( 例如对于使用了,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭,否则这些资源将不会被回收,造成内存泄漏。) - 没释放资源造成的内存泄露
- 注册某个对象后未反注册(例如 广播BraodcastReceiver,注册了一些ContentObserver,需要注销)
- 集合中对象没清理造成的内存泄漏(和上面的分类一,第二种的解决方案类似)
参考链接:
http://www.maiziedu.com/article/9126/
http://blog.csdn.net/cyq1028/article/details/19980369