Android常见⾯试题——内存泄漏原因及解决办法
前⾔
⾯试中最常问的就是:“你了解Android内存泄漏和Android内存溢出的原因吗,请简述⼀下” ,然后⼤多数的⼈都能说出原因及其例⼦和解决办法,但是实际项⽬中稍微不注意还是会导致内存泄漏,今天就来梳理⼀下那些是常见的内存泄漏写法和解决⽅法。
原因
内存泄漏的原理很多⼈都明⽩,但是为了加强⼤家的防⽌内存泄漏的意识,我再来说⼀遍。说到内存泄漏的原理就必须要讲⼀下Java的GC 的。Java之所以这么流⾏不仅仅是他⾯向对象编程的⽅式,还有⼀个重要的原因是因为,它能帮程序员免去释放内存的⼯作,但Java并没有我们想象的那么智能,它进⾏内存清理还得依靠固定的判断逻辑。
Java的GC可分为
引⽤计数算法
给对象添加⼀个引⽤计数器,每当有⼀个地⽅引⽤它时,计数器值就加1;当引⽤失效时,计数器值就减
1;在任何时刻计数器的值为0的对象就是不可能再被使⽤的,也就是可被回收的对象。这个原理容易理解并且效率很⾼,但是有⼀个致命的缺陷就是⽆法解决对象之间互相循环引⽤的问题。如下图所⽰
可达性分析算法
针对引⽤计数算法的致命问题,可达性分析算法能够轻松的解决这个问题。可达性算法是通过从GC root往外遍历,如果从root节点⽆法遍历该节点表明该节点对应的对象处于可回收状态,如下图中obj1、obj2、obj3、obj5都是可以从root节点出发所能到达的节点。反观obj4、obj6、obj7却⽆法从root到达,即使obj6、obj7互相循环引⽤但是还是属于可回收的对象最后被jvm清理。
看了这些知识点,我们再来寻内存泄漏的原因,Android是基于Java的⼀门语⾔,其垃圾回收机制也是基于Jvm建⽴的,所以说Android 的GC也是通过可达性分析算法来判定的。但是如果⼀个存活时间长的对象持有另⼀个存活时间短的对象就会导致存活时间短的对象在GC时被认定可达⽽不能被及时回收也就是我们常说的内存泄漏。Android对每个App内存的使⽤有着严格的限制,⼤量的内存泄漏就可
能导致OOM,也就是在new对象请求空间时,堆中没有剩余的内存分配所导致的。
android retrofit既然知道了原理那么平时什么会出现这种问题和怎么合理的解决这种问题呢。下⾯来按实例说话。
内存泄漏的例⼦
Handler
说到Handler这个东西,⼤家平时肯定没少⽤这玩意,但是要是⽤的不好就⾮常容易出现问题。举个例⼦
public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
toast("handlerLeakcanary");
}
};
private void handlerLeakcanary(){
Message message = new Message();
handler.sendMessageDelayed(message,TIME);
}
⽼实说写过代码的⼈肯定很多。其中不乏了解内存泄漏原理的⼈。但是平时需要多的时候⼀不⼩⼼就可能写下这⽓⼈的代码。
了解Handler机制的⼈都明⽩,但message被Handler send出去的时候,会被加⼊的MessageQueue中,Looper会不停的从MessageQueue中取出Message并分发执⾏。但是如果Activity 销毁了,Handler发送的message没有执⾏完毕。那么Handler就不会被回收,但是由于⾮静态内部类默认持有
外部类的引⽤。Handler可达,并持有Activity实例那么⾃然jvm就会错误的认为Activity可达不就⾏GC。这时我们的Activity就泄漏,Activity作为App的⼀个活动页⾯其所占有的内存是不容⼩视的。那么怎么才能合理的解决这个问题呢
1、使⽤弱引⽤
Java⾥⾯的引⽤分为四种类型强引⽤、软引⽤、弱引⽤、虚引⽤。如果有不明⽩的可以先去了解⼀下。
public static class MyHandler extends Handler{
WeakReference<ResolveLeakcanaryActivity> reference;
public MyHandler(WeakReference<ResolveLeakcanaryActivity> activity){
reference = activity;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (()!=null){
<().toast("handleMessage");
}
}
}
引⽤了弱引⽤就不会打扰到Activity的正常回收。但是在使⽤之前⼀定要记得判断弱引⽤中包含对象是否为空,如果为空则表明表明Activity被回收不再继续防⽌空指针异常
2、使⽤veMessages();
知道原因就很好解决问题,Handler所导致的Activity内存泄漏正是因为Handler发送的Message任务没有完成,所以在onDestory中可以将handler中的message都移除掉,没有延时任务要处理,activity的
⽣命周期就不会被延长,则可以正常销毁。
单例所导致的内存泄漏
在Android中单例模式中经常会需要Context对象进⾏初始化,如下简单的⼀段单例代码⽰例
public class MyHelper {
private static MyHelper myHelper;
private Context context;
private MyHelper(Context context){
}
public static synchronized MyHelper getInstance(Context context){
if (myHelper == null){
myHelper = new MyHelper(context);
}
return myHelper;
}
public void doSomeThing(){
}
}
这样的写法看起来好像没啥问题,但是⼀旦如下调⽤就会产⽣内存溢出
public void singleInstanceLeakcanary(){
}
⾸先单例中有⼀个static实例,实例持有Activity,但是static变量的⽣命周期是整个应⽤的⽣命周期,肯定是会⽐单个Activity的⽣命周期长的,所以,当Activity finish时,activity实例被static变量持有不能释放内存,导致内存泄漏。
解决办法:
1.使⽤getApplicationContext()
private void singleInstanceResolve() {
}
2.改写单例写法,在Application⾥⾯进⾏初始化。
匿名内部类导致的异常
/**
* 匿名内部类泄漏包括Handler、Runnable、TimerTask、AsyncTask等
*/
public void anonymousClassInstanceLeakcanary(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
这个和Handler内部类导致的异常原理⼀样就不多说了。改为静态内部类+弱引⽤⽅式调⽤就⾏了。静态变量引⽤内部类
private static Object inner;
public void innearClassLeakcanary(){
class InnearClass{
}
inner = new InnearClass();
}
因为静态对象引⽤了⽅法内部类,⽅法内部类也是持有Activity实例的,会导致Activity泄漏
解决⽅法就是通过在onDestory⽅法中置空static变量
⽹络请求回调接⼝
Retrofit retrofit = new Retrofit.Builder()
.ate())
.baseUrl("gank.io/api/data/")
.build();
Api mApi = ate(Api.class);
Call<AndroidBean> androidBeanCall = Data(20,1);
@Override
public void onResponse(Call<AndroidBean> call, Response<AndroidBean> response) {
toast("requestLeakcanary");
}
@Override
public void onFailure(Call<AndroidBean> call, Throwable t) {
}
});

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。