内存泄漏

原文出处:https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md



内存泄露

JavaC++一个很大的区别就是Java有垃圾回收机制GC(Garbage Collection)自动管理内存的回收。但是我们在实际的项目中仍然会遇到内存泄露的问题。Java中对内存对象得访问时通过引用的方式,通过一个内存对象得引用变量来访问到对应的内存地址中的对象。GC会从代码栈的引用变量开始追踪,从而判断哪些内存是正在使用,如果无法跟踪到某一块堆内存,那么GC就认为这块内存不再使用了。

定义

内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重会提示你Out of memory

原因

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景

危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制FC

Android手机给应用分配的内存通常是8兆左右,如果处理内存处理不当很容易造成OutOfMemoryError

OutOfMemoryError主要由以下几种情况造成:

  1. 数据库Cursor没关。
    当我们操作完数据库后,一定要调用close()释放资源。

  2. 构造Adapter没有使用缓存ContentView

    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        ViewHolder vHolder = null;  
        //如果convertView对象为空则创建新对象,不为空则复用  
        if (convertView == null) {  
            convertView = inflater.inflate(..., null);  
            // 创建 ViewHodler 对象  
            vHolder = new ViewHolder();  
            vHolder.img= (ImageView) convertView.findViewById(...);  
            vHolder.tv= (TextView) convertView  
                    .findViewById(...);  
            // 将ViewHodler保存到Tag中  
            convertView.setTag(vHolder);  
        } else {  
            //当convertView不为空时,通过getTag()得到View  
            vHolder = (ViewHolder) convertView.getTag();  
        }  
        // 给对象赋值,修改显示的值  
        vHolder.img.setImageBitmap(...);  
        vHolder.tv.setText(...);  
        return convertView;  
    }  
    
    static class ViewHolder {  
        TextView tv;  
        ImageView img;  
    }  
    
  3. 未取消注册广播接收者
    registerReceiver()unregisterReceiver()要成对出现,通常需要在ActivityonDestory()方法去取消注册广播接收者。

  4. IO流未关闭注意用完后及时关闭

  5. Bitmap使用后未调用recycle()。

  6. Context泄漏。 这是一个很隐晦的OutOfMemoryError的情况。先看一个Android官网提供的例子:

    private static Drawable sBackground;  
    @Override  
    protected void onCreate(Bundle state) {  
        super.onCreate(state);  
        TextView label = new TextView(this);  
        label.setText("Leaks are bad");  
        if (sBackground == null) {  
            sBackground = getDrawable(R.drawable.large_bitmap);  
        }  
        label.setBackgroundDrawable(sBackground);  
        setContentView(label);  
    }  
    

    这段代码效率很快,但同时又是极其错误的:
    Drawable拥有一个TextView的引用,而TextView又拥有Activity(Context类型)的引用,Drawable拥有了更多的对象引用。即使Activity被销毁,内存仍然不会被释放。
    另外,对Context的引用超过它本身的生命周期,也会导致Context泄漏。所以尽量使用Application这种Context类型。所以如果打算保存一个长时间的对象时,要使用getApplicationContext()。最近遇到一种情况引起了Context泄漏,就是在Activity销毁时,里面有其他线程没有停。

    总结一下避免Context泄漏应该注意的问题:

    • 使用getApplicationContext()类型。
    • 注意对Context的引用不要超过它本身的生命周期。
    • 慎重的使用static关键字。
    • Context里如果有线程,一定要在onDestroy()里及时停掉。
  7. 线程线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

    public class MyActivity extends Activity {     
        @Override     
        public void onCreate(Bundle savedInstanceState) {         
            super.onCreate(savedInstanceState);         
            setContentView(R.layout.main);         
            new MyThread().start();     
        }       
        private class MyThread extends Thread{         
        @Override         
            public void run() {             
            super.run();             
            //耗时的操作       
            }     
        } 
    }  
    

    假设MyThreadrun函数是一个很费时的操作,当调用finish的时候Activity会销毁掉吗?
    事实上由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThreadrun函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

  8. Handler的使用,在Activity退出的时候注意移除(尤其是循环的时候)

    public class ThreadDemo extends Activity {  
        private static final String TAG = "ThreadDemo";  
        private int count = 0;  
        private Handler mHandler =  new Handler();  
    
        private Runnable mRunnable = new Runnable() {  
    
            public void run() {  
                //为了方便 查看,我们用Log打印出来   
                Log.e(TAG, Thread.currentThread().getName() + " " +count);    
                //每2秒执行一次   
                mHandler.postDelayed(mRunnable, 2000);  
            }   
        };  
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);   
            //通过Handler启动线程   
            mHandler.post(mRunnable);  
        } 
    }
    
    //所以我们在应用退出时,要将线程销毁,我们只要在Activity中的,onDestory()方法处理一下就OK了,如下代码所示:
    @Override  
      protected void onDestroy() {  
        mHandler.removeCallbacks(mRunnable);  
        super.onDestroy();  
      } 
    
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值