您的位置:首页 > 移动开发 > Android开发

Android内存泄露分析之-内存泄露的原因

2017-11-24 23:09 218 查看
首先我们要知道java运行时内存的分配策略,它们分别是:静态分配、栈式分配、堆式分配,而三种存储策略所对应的内存空间分别是:静态存储区(方法去)、栈区、堆区。

静态存储区:主要存放static修饰的静态数据。这块内存在程序编译

时就已经分配好,并且在程序的整个运行期间都存在。

栈区:主要存放方法体中的局部变量(包括基本数据类型、对象的引用),当所在的方法执行完毕后这些变量会释放所持有的内存。

堆区:主要存放对象的实例和数组,通常是new出来的实例对象。这部分

内存将有java垃圾回收器回收。我们所说的内存泄露一般发生在这

个内存区域。

GC如何判断某个对象需要被释放?

我们知道java内存的释放工作是由GC来完成的,GC为了能够正确释放对象,它会监控每一个对象的运行状态,包括对象的申请、引用、被引用、被赋值等操作。一旦监控到某个对象没有被引用,那么就判断这个对象可以被回收了。

GC如何判断某个对象是否被引用?

我们可以通过有向图的方式来理解,具体算法我们这里不做分析。



我们可以把main线程看做有向图的根顶点,将引用关系看做是有向边,有向边从引用者指向被引用对象。根顶点可达的对象都是有效对象,即不被回收对象。跟顶点不可达的对象,即不在被引用,那么GC将会回收这些对象。上图中的对象Obj1为可达对象,Obj2则为不可达对象,因此Obj2会被GC回收。

怎样会造成内存泄漏?

上面说过,有向图中不可达的对象会被GC回收,可达的对象不会被回收,如果某些可达的对象是无用的,GC却又不会即时回收,那么内存泄露就是出现。例如:

private static List list = new ArrayList();
public static void main(String[] args){
for(int i = 0 ; i < 3 ; i++){
Object obj = new Object();
list.add(obj);
obj = null;
}

}


上述代码申请了一个static类型的集合list,那么类在加载时就会为其分配静态内存空间,它的生命周期跟程序的运行周期一样。因此,短生命周期的obj虽然置为null,释放了引用本身,但其对象一直被长生命周期的list所引用着,因此GC无法对其进行释放,这里就出现了内存泄露。解决方案:将list置为null,这样就不会持有obj对象的引用。

内存泄露的根本原因

从上面实例可以看出,长生命周期对象持有短生命周期对象的引用会造成内存泄露,这就是造成内存泄露的根本原因。

内存泄露的场景

造成内存泄露的场景其实很多,我们平时在写代码的时候稍不注意就有可能造成内存泄露。下面对出现频率较高的情况予以总结:

1.单例造成的内存泄露

public class LeakMemory {
private static LeakMemory leakMemory ;
private Context context;

private LeakMemory(Context context){
this.context = context;
}

public static LeakMemory getInstance(Context context){
if (leakMemory == null){
leakMemory = new LeakMemory(context);
}
return leakMemory;
}
}


LeakMemory通过静态方法传入context得到其对象,由于其对象是静态的,因此它的生命周期跟程序生命周期一致,而它又持有外部context的引用,如果这个context是ApplicationContext那么不会有什么问题,如果context是Activity,那么长生命周期持有外部端生命周期Acitivity的引用就会造成内存泄露。

正确做法:

要么传入Application或者ApplicationContext。

要么将Context以弱引用的方式传入,并将context = null。

2.非静态内部类引用外部类

public class LeakMemoryActivity extends Activity{
private static A a;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (a == null){
a = new A();
}
}

class A{

}
}


因为非静态内部类默认会持有外部类的引用,我们在activity中初始化了静态的内部类a,a属于长生命周期,它的生命周期跟程序的运行周期一样长。那么当activity关闭需要被释放时,因为a一直在引用它,因此得不到释放,造成了内存泄露。

正确做法:将A置为static,这样它就不会一直持有activity的引用。

3.Handler造成的内存泄露

public class LeakMemoryActivity extends Activity{
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//...
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Message msg = Message.obtain();
msg.obj =1;
handler.sendMessageDelayed(msg,10000);

finish();
}
}


这个例子跟上面例子差不多,都是因为非静态内部类持有外部类的引用。但这里的具体原因是:handler将message消息push进了消息队列MessageQueue中,然后延迟10秒钟发送。此时activity被finish掉时,延迟执行任务的message还存在于主线程中,他持有handler的引用,而handler又持有activity的引用,因此activity得不到释放,造成内存泄露。

正确做法:将handler置为static,在activity的Destroy或者Stop时移除消息队列MessageQueue中的消息(通过removeCallbacks(..)等方法)。另外,如果handler内部需要引用到activity,那么应该将activity已弱引用的方式传入handler中,这个时候可以写一个Hanlder的实现类,在构造方法中将弱引用的activity传入。

4.静态集合类引起的内存泄漏

private static List list = new ArrayList();
public static void main(String[] args){
for(int i = 0 ; i < 3 ; i++){
Object obj = new Object();
list.add(obj);
obj = null;
}

}


list为静态的,属于长生命周期,因此它所引用的object不能被释放。

正确做法是将list置为null。

5.资源未关闭造成的泄露

对于使用了BroadcastReceiver、ContentObserver、File、Cursor、Bitmap等资源,应该在Activity销毁时及时关闭或者注销

好了,以上是平时可能遇到的内存泄漏情况。至于在开发中如何追踪可能出现内存泄露的代码,我会另起篇幅来探究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内存泄露 android