转自:http://blog.csdn.net/a910626/article/details/50849760
在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对其进行回收的,但是此时仍然有其他引用指向这个活在堆内存中的对象,那么gc就不会认为这个对象是一个垃圾,那么就不会对其进行回收,所以它会一直活在堆内存中占用内存,这就导致了内存泄漏。
总结一下,导致内存泄漏的原因就是有一些我们永远不会使用的对象,仍然有引用指向它(当然这是在强引用的情况下),那么就不满足gc回收的条件,从而一直活在堆内存中导致内存泄漏,这样的对象多了占用大量内存就会导致App发生oom。
举几个例子:比如使用EventBus,肯定是要执行register(),那么在Fragment或Activity finish的时候,一定不要忘记执行unregister()方法。
public class MainActivity extends AppCompatActivity {
private final Handler myHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//doSomething
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myHandler.postDelayed(new Runnable() {
@Override
public void run() {
//doSomething
}
},60*10*1000);
}
}
由于myHandler延时10分钟就会发送一条消息,当activity finish之后,延时发送的消息会在主线程的消息队列中存活10分钟直到被looper拿到然后给到handler处理。此消息(new Runnable)隐式持有其外部类handler的引用,myHandler又隐式的持有其外部类Activity的引用,直到消息被处理完之后,这个引用都不会被释放。因此Activity即使finish,但仍然不会被gc回收。
引用的顺序MessageQueue->Message->Runnable->Handler->Activity,从这个引用链得到Activity与MessageQueue关联,所以Activity对象不能被gc回收,从而导致内存泄漏。
解决方式:
为了解决Handler隐式的持有外部类引用,我们应当将Handler定义在一个新文件或在Activity中使用静态内部类。因为静态内部类不会持有外部类的引用,这样当Activity finish时,Handler不会持有Activity的引用就不会导致Activity内存泄漏。如果需要在Handler内部调用外部Activity的方法,正确的做法是让Handler持有一个Activity的弱引用(WeakReference),这样当gc扫描的时候,这个弱引用的对象就会被回收。
解决了Handler隐式持有外部类Activity引用,Runnable在之前的代码中作为匿名内部类隐式持有Handler引用,所以我们在Activity内部定义一个静态变量引用Runnable类,这是因为匿名类的静态实例不会隐式持有他们外部类的引用。
public class MainActivity extends AppCompatActivity {
private final MyHandler mHandler = new MyHandler(this);
private static Runnable sRunnable = new Runnable() {
@Override
public void run() {
//doSomething
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 60 * 10);
this.finish();
}
private static class MyHandler extends Handler {
private final WeakReference mActivity;
public MyHandler(MainActivity activity) {
this.mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
//doSomething
}
}
}
}
我们也可以这样做:
在Activity的onDestroy方法中干掉handler中所有的callback和message:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
在Android中最常见的操作就是当有耗时操作的时候我们不能在主线程执行这些操作,否则有可能造成ANR,主线程主要是UI操作的主战场。
比如网络请求或者数据库查询这些耗时操作我们需要自己另外开启线程,在子线程中执行这些耗时操作。当我们需要开启的子线程比较少的时候,直接new Thread(Runnable)就可以了。如果你经常这样做的话就说明你没有注意到有可能会产生内存泄漏的问题。
如果Activity结束了,而Thread还在跑,同样会导致Activity内存泄漏,这是因为new Thread作为非静态内部类对象都会隐式持有一个外部类对象的引用,我们所创建的线程就是Activity中的一个内部类,持有Activity对象的引用,所以当Activity 结束了,而子线程还在跑就会导致Activity内存泄漏。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testThread();
}
private void testThread() {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
SystemClock.sleep(1000);
}
}
}).start();
}
}
new Thread()是匿名内部类,且非静态。所以会隐式持有外部类的一个引用,只要非静态匿名类对象没有被回收,Activity就不会被回收。
解决方式:
同样把Thread定义为静态的内部类,这样就不会持有外部类的引用。
LeakActivity.java
public class LeakActivity extends AppCompatActivity {
private TestManager testManager = TestManager.getInstance();
private MyListener listener=new MyListener() {
@Override
public void doSomeThing() {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testManager.registerListener(listener);
}
}
TestManager.java
public class TestManager {
private static final TestManager INSTANCE = new TestManager();
private MyListener listener;
public static TestManager getInstance() {
return INSTANCE;
}
public void registerListener(MyListener listener) {
this.listener = listener;
}
public void unregisterListener() {
listener = null;
}
}
interface MyListener {
void doSomeThing();
}
在LeakActivity中TestManager.getInstance()创建对象实例,TestManager中采用单例模式返回一个Testmanager实例变量。
引用链:TestManager->listener->Activity
TestManager中的实例变量是static静态变量,静态变量和类的生命周期是一样的。类加载的时候,静态变量就被加载,类销毁时,静态变量也会随之销毁。
因为INSTANCE是一个单例,所以和app的生命周期是一样的。当app进程销毁时,堆内存中的INSTANCE对象才会被释放,INSTANCE的生命周期非常的长。
而又可以看到代码中Activity里面创建了listener非静态内部类,所以listener就持有外部类Activity的引用。随着testManager.registerListener(listener)执行,TestManager中的listener就持有Activity中listener对象,由此形成了一个引用链。关键在于INSTANCE是一个静态变量,往往Activity finish的时候,INSTANCE还活着,而INSTANCE依然持有Activity的引用,所以造成了Activity内存泄漏。
所以,解决方式:要在Activity的onDestroy()方法中注销注册的listener
@Override
protected void onDestroy() {
testManager.unregisterListener();
super.onDestroy();
}
将TestManager中listener与Activity中的listener关联断开。
出现内存泄露的主要原因是生命周期的不一致造成的:在Android中,长时间运行的任务和Acyivity生命周期进行协调会有点困难,如果你不加以小心的话会导致内存泄漏。
内存泄漏的主要原因在于一个生命周期长的东西间接引用了一个生命周期短的东西,会造成生命周期短的东西无法被回收。反过来,如果是一个生命周期短的东西引用了一个生命周期长的东西,是不会影响生命周期短的东西被回收的。
对象都是有生命周期的,对象的生命周期有的是进程级别的,有的是Activity所在的生命周期,随Activity消亡;有的是Service所在的生命周期,随Service消亡。很多情况下判断对象是否合理存在的一个很重要的理由就是它实际的生命周期是否符合它本来的生命周期。很多Memory Leak的发生,很大程度上都是生命周期的错配,本来在随Activity销毁的对象变成了进程级别的对象,Memory Leak就无法避免了。