Android面试题之——内存泄露

一、内存泄露的定义
    内存泄露是指不再使用的内存仍然占用着内存空间,因为程序中仍然保存着对它的引用,而使得GC无法将它回收或得到及时释放,从而造成的内存空间浪费的问题,称为内存泄露。

二、内存泄露的根本原因
   长生命周期的对象持有短生命周期对应的引用,因为短生命周期对象可能不再使用,而因为长生命周期对象持有着对其的引用,因此GC无法将其进行回收。

三、内存泄露(Memory Leak)与内存溢出(OOM)之间的区别
    内存溢出(OOM)是指程序在申请内存时,没有足够的内存空间供其使用,出现OOM
    Android Dalvik虚拟机的内存大致可以分为三类:Java Heap Object,Bitmap Memory, Native Object;Dalvik虚拟机在启动的时候,就是通过读取系统属性dalvik.vm.heapsize的值来获得Java Object Heap的最大值(一般 为16M),当应用使用的内存超过16M时,便会发生OOM;而Bitmap Memory是专门用来处理图像的,Bitmap直接在Java native Object中进行分配的,故平时在进行图片开发时,才会对图片的处理如此谨慎,避免发生OOM;

    内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,即发生OOM;


四、造成内存泄露的情况包括有:
1)静态集合类造成的内存泄露;静态变量存储在方法区中,其生命周期与程序相同,而其内部又保存着对对象的引用,当这些对象其实不会再使用时,也依然无法释放;  
     因此当该静态集合类不再使用,或者静态集合类中保存的对象确定不会再使用时,要注意设为null;

static Vector v = new Vector(10);
for (int i = 1; i < 100; i++) { 
    Object o = new Object();
    v.add(o);
    o = null; // 设置null也无法将其内存回收,因为Vector中仍保存着对该内存的引用
}

2)在观察者模式中,添加对一个对象的监听器,当对象不再使用时,而未对监听器进行释放。
个在Android中情况尤为严重,比如 我们希望在锁屏界面 (LockScreen) 中,监听系统中的电话服务以获取一些信息 ( 如信号强度等 ) 则可以在 LockScreen 中定义一个 PhoneStateListener 的对象,同时将它注册到 TelephonyManager 服务中。
对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。
还有如调用了registerReceiver方法,而没有调用unregisterReceiver方法,造成的内存泄露等;

3)单例模式引起的内存泄露:
class A {}

class Test {
    private static Test instance = new Test(new A());
    private A a;
    private Test(A a) {
        this.a = a;
    }

    public Test getInstance() {
        return instance;
    }
}
设计上出现的问题,因为静态变量存储在方法区,因此instance的生命周期与应用相同,而instance中又保存了对A对象的引用,使得即使A对象不再使用,也无法对其进行释放。
4)变量生命周期使用不当:
class Server{
    private String msg;
    public void recvMsg(){
        readMsg(msg);
        save(msg);
        // 这里msg被保存之后已经无用,但是其生命周期将与Server保持一致,不能被回收,如果msg太大,将会造成不可预测的意外,安全的做法是加一行代码:msg=null;
    }
}
1、变量的作用域需要合理设置,类的成员变量生命周期与类对象相同,对于一些只需要短生命周期的变量,则会造成内存空间的浪费;

而这个也需要与内存抖动之间进行平衡比较,避免短时间内创建大量的临时性变量而造成内存抖动。

2、同理对于static静态变量,也应当遵循谨慎的原则进行使用;

3、在Android中最突出的问题便是对Context的引用造成的内存泄露。因为Activity对象本身引用的资源较多,其无法进行释放造成的资源浪费效果将会很明显;因此应当注意避免让长生命周期的对象引用Activity,可以尝试引用Application Context;


5)内部类与外部类之间的引用造成的内存泄露;由于内部类保存着对外部类的引用,若未对其进行释放,外部类的内存空间也无法释放;

1、最典型的便是Handler造成的内存泄露

public class TestActivity extends Activity {
    private MyHandler myHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // MyHandler是内部类,初始化MyHandler,其内部会隐式地保存对其外部类即Activity的引用
        myHandler = new MyHandler();

        // postDelayed一个Runnable,该操作会send一个Message到MessageQueue中,而Message对象msg中的
        // target变量将会指向myHandler,即msg保存有对myHandler的引用

        // 又MessageQueue是针对线程存在,当msg入队列还未得到处理时,Activity被关闭,此时msg依然存在与MessageQueue中
        // 又由前面得知msg保存着对myHandler的引用,而myHandler保存着对Activity的应用;因此Activity关闭后,其内存仍然无法得到回收,即造成内存泄露
        myHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
            }
        }, 100000);
        // 关闭Activity
        finish();
    }

    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {

        }
    }
}
   由于 MyHandler 是内部类,初始化 MyHandler ,其内部会隐式地保存对其外部类即 Activity 的引用;

    使用MyHandler postDelayed一个Runnable,该操作会send一个MessageMessageQueue中,而Message对象msg中的target变量将会指向myHandler,即msg保存有对myHandler的引用
    又MessageQueue是针对线程存在,当msg入队列还未得到处理时,Activity被关闭,此时msg依然存在与MessageQueue中;

    又由前面得知msg保存着对myHandler的引用,而myHandler保存着对Activity的应用;因此Activity关闭后,其内存仍然无法得到回收,即造成内存泄露。


2、解决Handler内存泄露的方法:使用弱引用及静态内部类的方法;

public class TestActivity extends Activity {
    private MyHandler myHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        myHandler = new MyHandler(this);
    }

    private static class MyHandler extends Handler {
        // 与外部Activity建立弱引用关系
        private WeakReference<TestActivity> mWeak;

        public MyHandler(TestActivity activity) {
            // 初始化
            mWeak = new WeakReference<TestActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            TestActivity activity = mWeak.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

    // 处理事务逻辑
    public void handleMessage (Message msg) {
        // ......
    }
}

6)涉及到ListView优化中的未使用convertView ,过多使用自己创建的View;

1、错误用法:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = inflater.inflate(R.layout.activity_main, parent, false);
    // ......
    return view;
}

2、由ListView的源码及工作原理 ,来看一下ListView的源码:

ListView填充及获取View的方法如下:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
                            boolean selected) {
    View child;
    if (!mDataChanged) {
        // Try to use an exsiting view for this position  
        child = mRecycler.getActiveView(position);
        if (child != null) {
            // Found it -- we're using an existing child  
            // This just needs to be positioned  
            setupChild(child, position, y, flow, childrenLeft, selected, true);
            return child;
        }
    }
    // Make a new view for this position, or convert an unused view if possible  
    child = obtainView(position, mIsScrap);
    // This needs to be positioned and measured  
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
    return child;
}
由RecycleBin机制,ListView会首先尝试获取ActiveView[]中存储的View;然后再通过obtainView来获取View;

View obtainView(int position, boolean[] isScrap) {
    isScrap[0] = false;
    View scrapView;
    scrapView = mRecycler.getScrapView(position);
    View child;
    if (scrapView != null) {
        child = mAdapter.getView(position, scrapView, this);
        if (child != scrapView) {
            mRecycler.addScrapView(scrapView);
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        } else {
            isScrap[0] = true;
            dispatchFinishTemporaryDetach(child);
        }
    } else {
        child = mAdapter.getView(position, null, this);
        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }
    }
    return child;
}
 obtainView方法中首先通过getScrapView获取ScrapView[]中存储的View,ScrapView中存储的View便是划出界面废弃的View存储起来,便于再次重复利用;

可以看到在child = mAdapter.getView(position, scrapView, this);方法中来获得View,而scrapView即对应重写BaseAdapter方法中的convertView变量;不使用convertView则会造成废弃的View无法重复利用,同时大量创建的View,当View移出界面之后,而无法得到及时回收,造成内存泄露。

3、正确的用法

private ViewHolder viewHolder;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.activity_main, parent);
        viewHolder = new ViewHolder();
        viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
        viewHolder.textView = (TextView) convertView.findViewById(R.id.textView);
        convertView.setTag(viewHolder);
        
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }
    // ........
    viewHolder.imageView.setImageDrawable(null);

    return convertView;
}

static class ViewHolder {
    TextView textView;
    ImageView imageView;
}

7)资源没有关闭造成的内存泄露:

1、Cursor、File等资源型对象

资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。
它们的 缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。
如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。

2、WebView对象:要及时调用destory()方法销毁该对象;

3、Bitmap的回收:Bitmap占用的内存空间较大,在不对其使用时,要注意调用recycle()方法对其进行回收。具体参考图片优化
4、InputSteam,OutputStream等输入输出缓冲流在使用完成后要注意进行关闭。
5、动画未关闭:属性动画中有一类无限循环的动画,若Activity销毁时并未关闭动画,则动画会一直运行,由于属性动画持有View对象,View对象持有其相关的Activity对象,会造成Activity无法释放而内存泄露

五、处理内存泄露的方法:
除了针对上述内存泄露的原因进行相应的处理外,还可以通过mat工具对内存泄露进行分析。
http://blog.csdn.net/yulianlin/article/details/50393872 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值