在面试中,经常会被问到内存优化或者内存泄漏的相关概念,这些老生常谈的问题,你是真的懂了吗?
笔者今天就想要彻底搞明白这些,还清当初欠下的技术债。
内存泄漏问题:
定义:当你不再需要某个实例后,但是这个对象却仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。这个情况就叫做内存泄露(Memory Leak)。
常见的场景:
- 非静态内部类,比如handler使用问题。
- 静态的成员变量的使用
- 静态类持有activity的引用
- 各种回调,监听,资源使用在onDestory需要置空
非静态内部类:
通常使用handler,我们会使用一个主线程 ,new handler 然后重写handlerMessage()就完了,
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
这样就是会造成内存的泄漏,分析一下:
这个handler其实是一个inner class,不是static,这样这个handler 会默认持有 activity的引用,并且在使用非静态内部类的时候,会先创建外部类activity的对象,mHandler 不能独立存在,而静态的内部类是可以不依赖外部类的就被创建的,先列举一下static inner class 和no static inner class的区别
class对比 | static inner class | non-static inner class |
---|---|---|
与外部class引用关系 | 如果没有传入参数,就无引用关系 | 自动获得强引用(implicit reference) |
被调用时需要外部实例 | 不需要(比如Bulider类) | 需要 |
能否调用外部class中的变量与方法 | 不能 | 能 |
生命周期 | 自主的生命周期 | 依赖于外部类,甚至比外部类更长 |
假如我们在A界面用了非静态内部类的handler,此时finish掉A跳转到B界面,但A中的handler里面还有消息在处理,导致 handler无法释放,而handler没有被释放却又持有了外部类的activity,则activity A也不能释放,这样反复多次后,就会造成A占用本该释放的内存无法释放使得内存不够用,形成memoryLeak。
处理方式:写出静态的内部类,并通过弱引用(会在下一次GC时释放相关联的对象)持有外部引用,
public static class MyHandler extends Handler {
private WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
,另外,对于handler,我们可以在onDestory()中,调用removeCallbacksAndMessages清空消息队列,使得handler得以释放
/**
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*/
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
这种方式是一种手段对于handler而言,最好还是静态内部类弱引用吧。
静态的成员变量的使用
静态变量的生命周期是和类加载机制相关,当类被加载后,变量就会被创建,直到类加载器classsLoader被卸载才会回收。当acitivity中存在静态变量后,即使这个acitivity被回收,这个静态变量无法被回收,那这个静态变量占用的内存是不是就相当于被泄漏了呢?或者重复多次,就会创建多个这个变量,然后就有多份这个静态变量占用的内存没被回收?
这种情况算不算内存泄漏呢?按照定义是的,实际上不是那么回事。想想static 这个修饰符,静态变量是类变量,不是类实例变量,不依赖具体对象,只会创造一份,并不会创建多份,即使你多次创建activity,这个静态变量依旧只有一份。现在想想人家staitc 本来就不想随着对象回收而被回收,但内存泄漏研究的是实例对象,压根不挨着,所以静态变量使用造成内存泄漏只是理论上的,实际上微乎其微,因为照这样说,就不要用static 了,不用 public static final Sting TAG = ""; 这种定义了。
注意:这里的静态成员变量和上面的静态内部类是不冲突的,一个是变量,一个是类;一个是说静态变量自己不能被回收,activity是可以回收的,一个是说handler不能被回收导致acitivity不能被回收,二者是不一样的。这里非常具有迷惑性,我也是花费好久搞明白
静态类持有activity的引用
比如单例模式,因为单例模式用了static 修饰自己,这样生命周期和application一样了,当我们在创建单例对象的时候如果传入了acitivity的context,那么当acitivity被释放的时候应该被回收,却因为被单例对象持有其context无法被释放,这样就造成了acitvity占用的内存不能被释放。这样来类比,就是一些静态类持有了经常需要释放的对象的引用后,就会发生内存泄漏。
各种回调,监听,资源使用在onDestory需要置空
比如一些广播的注册,EventBus,butterKnife 第三方框架之类的,或者一些流的操作,file ,http连接,最好需要断开连接,对象置空。