zoukankan      html  css  js  c++  java
  • 关于android中内存泄漏的几种情况

    先做个标记,挖个坑,以后来填。

    参考的是大佬的博客:https://www.jianshu.com/p/ab4a7e353076.

    其实看了大佬的文章,我就看到了一句话:

    类A有一个成员变量B,如果B的生命周期比A长,那么在A想要回收的时候,发现B仍然持有A的引用。就会导致A无法回收,从而内存泄漏。

    当然大佬们帮我列举出了各种各样可能的情况,但是万变不离其宗。

    简单说吧,如果类成员的命比类实例还要长,那就要看好了,说不定就泄漏了。

    解决的方式无非也就是-让类的成员的命,比类实例短,或者一致。

    大佬列举出的方案是,使用静态内部类+弱引用,可以很好地避免内存泄漏。

    1) 就在我们常规使用Handler的时候,也有可能会出现内存泄漏的情况。 因为,创建handler对象的时候,handler会持有当前activity的引用。

     而 当前activity的生命周期 和 handler执行handlerMessage其实是两个独立的过程。

     如果在某一个handlerMessage执行的时候,activity被执行了finish将要被回收,那handler所持有的activity引用,就会阻止activity的回收,造成泄漏。

     这种情况的解决方法,就是 在创建handler的时候,使用静态内部类+activity弱引用的方式。(弱引用:当所持有的引用对象想要被释放时,不会阻止其释放,如果handler持有activity的弱引用,那么当activity被回收时,弱引用并不会阻止它回收,但是这么做也有一个后果,就是如果handlerMessage执行到一半,发现activity没了,那么后续的执行过程,也有可能就终止)

     

    复制代码
    public class MainActivity extends AppCompatActivity {
    
        private Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler = new MyHandler(this);
            start();
        }
    
        private void start() {
            Message msg = Message.obtain();
            msg.what = 1;
            mHandler.sendMessage(msg);
        }
    
        private static class MyHandler extends Handler {//用静态内部类的方式来写handler,
    
            private WeakReference<MainActivity> activityWeakReference;//并且用activity
    
            public MyHandler(MainActivity activity) {
                activityWeakReference = new WeakReference<>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = activityWeakReference.get();
                if (activity != null) {
                    if (msg.what == 1) {
                        // 做相应逻辑
                    }
                }
            }
        }
    }
    复制代码

     Timer和TimerTask 也有可能造成泄漏,原因,和上面提到的handler一样。 如果activity要被回收了,但是timerTask还在执行(还持有activity的引用),那么activity也无法正常回收。 处理方式,在适当的时候,将timer和timerTask先于activity释放。

    2)静态成员变量引起内存泄漏:

      静态成员变量所处的位置并不是堆,而是单独在一个静态池。静态池的生命周期和application一样长,是要比activity要长的。所以,当activity中有一个静态成员变量,它持有activity的引用时,在activity将要被回收,而静态成员变量持有activity引用导致无法回收,则造成泄漏。

      解决方法,在适当的时候,让将静态成员变量设置为null,让它不再持有activity的引用。

    3)集合中的 对象未清理,导致内存泄漏:

      常见的一个情况:如果一个你自定义了一个MyActivityManager.java,想用一个ArrayList来管理所有的Activity,事实上,所有的activity在实例化之后,都会被add到ArrayList中,这个ArrayList就会持有所有activity的引用。

      当,某个activity被回收之后,如果arrayList没有及时将该activity从集合中remove,也会阻止activity的回收。

      处理方式:在Activity的onDestroy方法里面,调用MyActivityManager.remove(this);来将自身remove掉。

    4)资源未释放或者未关闭:

      常见的是IO流,使用之后必须及时关闭,因为他们会占用缓存区的内存空间。如果不释放,就会一直占用。

    5)属性动画

      如果在activity中启动了属性动画(ObjectAnimator),但是在activity销毁的时候没有cancel掉动画。那么,虽然activity不可见了,但是属性动画依然会一直不停地执行下去,只是你看不到。

      这种时候,ObjectAnimator所处的控件会持有activity的引用,也会导致activity无法回收。

    6)webView

      webView也会造成内存泄漏,但是由于WebView我没有使用过,所以不多说了。

    总结出来,要避免内存泄漏:

    构造单例的时候,如果一定要使用Context,别用Activity.context,而是用Activity.context.getApplicationContext(); 

    如果要构造一个绝对安全的handler,记得使用静态内部类+弱引用。 或者在Activity销毁之前,确保handler的所有handlerMessage都执行终止。

    Timer,timerTask,属性动画,在activity销毁时,记得cancel或者释放。

    IO流 在Activity销毁之前,一定要close。

    在activity销毁之前,一定要先移除掉WebView。

    1.资源对象没关闭造成的内存泄漏

    描述:

    资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如 SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

    程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

    示例代码:

    1. Cursor cursor = getContentResolver().query(uri...); 
    2.  
    3. if (cursor.moveToNext()) { 
    4.  
    5. ... ... 
    6.  

    修正示例代码:

    1. Cursor cursor = null; 
    2.  
    3. try { 
    4.  
    5. cursor = getContentResolver().query(uri...); 
    6.  
    7. if (cursor != null &&cursor.moveToNext()) { 
    8.  
    9. ... ... 
    10.  
    11.  
    12. finally { 
    13.  
    14. if (cursor != null) { 
    15.  
    16. try { 
    17.  
    18. cursor.close(); 
    19.  
    20. catch (Exception e) { 
    21.  
    22. //ignore this 
    23.  
    24.  
    25.  

    2.构造Adapter时,没有使用缓存的convertView

    描述:

    以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:

    public View getView(int position, ViewconvertView, ViewGroup parent)

    来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,如果我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。 ListView回收list item的view对象的过程可以查看:

    android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。

    示例代码:

    1. public View getView(int position, ViewconvertView, ViewGroup parent) { 
    2.  
    3. View view = new Xxx(...); 
    4.  
    5. ... ... 
    6.  
    7. return view; 
    8.  

    修正示例代码:

    1. public View getView(int position, ViewconvertView, ViewGroup parent) { 
    2.  
    3. View view = null; 
    4.  
    5. if (convertView != null) { 
    6.  
    7. view = convertView; 
    8.  
    9. populate(view, getItem(position)); 
    10.  
    11. ... 
    12.  
    13. else { 
    14.  
    15. view = new Xxx(...); 
    16.  
    17. ... 
    18.  
    19.  
    20. return view; 
    21.  

    3.Bitmap对象不在使用时调用recycle()释放内存

    描述:

    有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。可以看一下代码中的注释:

    1. /** 
    2.  
    3. •Free up the memory associated with thisbitmap's pixels, and mark the 
    4.  
    5. •bitmap as "dead", meaning itwill throw an exception if getPixels() or 
    6.  
    7. •setPixels() is called, and will drawnothing. This operation cannot be 
    8.  
    9. •reversed, so it should only be called ifyou are sure there are no 
    10.  
    11. •further uses for the bitmap. This is anadvanced call, and normally need 
    12.  
    13. •not be called, since the normal GCprocess will free up this memory when 
    14.  
    15. •there are no more references to thisbitmap. 
    16.  
    17. */ 

    4.试着使用关于application的context来替代和activity相关的context

    这是一个很隐晦的内存泄漏的情况。有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长,而不是取决于activity的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。更多的请看这篇文章如何避免

    Android内存泄漏。

    5.注册没取消造成的内存泄漏

    一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。调用registerReceiver后未调用unregisterReceiver。

    比如:假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个 PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

    但是如果在释放 LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process 进程挂掉。

    虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。

    6.集合中对象没清理造成的内存泄漏

    我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

    一、 Android的内存机制  

    Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的.   那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。 线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。  


    二、Android的内存溢出  

    Android的内存溢出是如何发生的?   Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。  

    为什么会出现内存不够用的情况呢?我想原因主要有两个:  

    由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。  

    保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。  


    三、常见的内存泄漏  

    1.万恶的static    

    static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。

    所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。   
    public class ClassName {          

      private static Context mContext;     

      //省略  

    }   

    以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.   
    如何才能有效的避免这种引用的发生呢?     

      第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。      

      第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。      

      第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;  


    2.线程惹的祸  

    线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。  

    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();                

             //do somthing          

        }      

      }  

    }       

    这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的 横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。  

        由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束 时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

    这种线程导致的内存泄露问题应该如何解决呢?     

    第一、将线程的内部类,改为静态内部类。     

    第二、在线程内部采用弱引用保存Context引用。          

    另外,我们都知道Hanlder是线程与Activity通信的桥梁,我们在开发好多应用中会用到线程,有些人处理不当,会导致当程序结束时,线程并没有 被销毁,而是一直在后台运行着,当我们重新启动应用时,又会重新启动一个线程,周而复始,你启动应用次数越多,开启的线程数就越多,你的机器就会变得越 慢。 

    package com.tutor.thread;  

    import android.app.Activity;  

    import android.os.Bundle;  

    import android.os.Handler;  

    import android.util.Log;  

    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);              

             count++;              

             setTitle("" +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();     


    3.超级大胖子Bitmap 

    可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。 
        如何解决Bitmap带给我们的内存问题? 
        第一、及时的销毁。     

          虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要 及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。 
        第二、设置一定的采样率。     

         有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码: 

    private ImageView preview;  

    BitmapFactory.Options options = new BitmapFactory.Options();  

    options.inSampleSize = 2;

    //图片宽高都为原来的二分之一,即图片为原来的四分之一  

    Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); 

    preview.setImageBitmap(bitmap); 

    第三、巧妙的运用软引用(SoftRefrence)     

    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。     
    4.行踪诡异的Cursor     

    Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,

    而且虚拟机能够保证Cusor最终会被释放掉。     

    然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。

    并且Android明显是倾向于编程者手动的将Cursor close掉      

    5.构造Adapter时,没有使用缓存的 convertView  描述:   

    以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法: 

    public View getView(int position, View convertView, ViewGroup parent) 

    来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。   

    由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看: android.widget.AbsListView.java --> void addScrapView(View scrap) 方法。 

    示例代码: 

    public View getView(int position, View convertView, ViewGroup parent) {   

    View view = new Xxx(...);   

    ... ...   

    return view; 

    修正示例代码:  public View getView(int position, View convertView, ViewGroup parent) {   

    View view = null;   

    if (convertView != null) { 

       view = convertView;   

       populate(view, getItem(position));    ...   

    } else {   

       view = new Xxx(...);    ...   

    }   

       return view; 


    小结:  static:引用了大对象如context;

    线程:切屏时Activity因为线程引用而没有如期被销毁;

    handler有关,Activity意外终止但线程还在 

    Bitmap:要及时recycle,降低采样率 

    Cursor:要及时关闭 

    Adapter:没有使用缓存的convertView 

    更多:可以参考Android内存泄漏分析及调试的常见内存的使用

    http://blog.csdn.net/gemmem/article/details/13017999

     1、非静态内部类的静态实例容易造成内存泄漏

    public class MainActivityextends Activity
    {
             static Demo sInstance = null;
            
        @Override
        public void onCreate(BundlesavedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (sInstance == null)
            {
               sInstance= new Demo();
            }
        }
        class Demo
        {
        voiddoSomething()
        {
                   System.out.print("dosth.");
        }
        }
    }

    上面的代码中的sInstance实例类型为静态实例,在第一个MainActivity act1实例创建时,sInstance会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sInstance持有act1的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于lauchMode不是singleInstance的Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。

    2、activity使用静态成员

    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);  
    } 

    由于用静态成员sBackground 缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。因为在android 2.3系统上,它会导致activity销毁后无法被系统回收。

    label .setBackgroundDrawable函数调用会将label赋值给sBackground的成员变量mCallback。

    上面代码意味着:sBackground(GC Root)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。

    下面看看android4.0为了避免上述问题所做的改进。

    先看看android 2.3的Drawable.Java对setCallback的实现:

        public final void setCallback(Callback cb){

            mCallback = cb;

    }

    再看看android 4.0的Drawable.Java对setCallback的实现:

        public final void setCallback(Callback cb){

            mCallback = newWeakReference<Callback> (cb);

    }

    在android 2.3中要避免内存泄漏也是可以做到的, 在activity的onDestroy时调用

    sBackgroundDrawable.setCallback(null)。

    以上2个例子的内存泄漏都是因为Activity的引用的生命周期超越了activity对象的生命周期。也就是常说的Context泄漏,因为activity就是context。

    想要避免context相关的内存泄漏,需要注意以下几点:

    ·不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)

    ·如果可以的话,尽量使用关于application的context来替代和activity相关的context

    ·如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样。

    3、使用handler时的内存问题

    我们知道,Handler通过发送Message与其他线程交互,Message发出之后是存储在目标线程的MessageQueue中的,而有时候Message也不是马上就被处理的,可能会驻留比较久的时间。在Message类中存在一个成员变量 target,它强引用了handler实例,如果Message在Queue中一直存在,就会导致handler实例无法被回收,如果handler对应的类是非静态内部类 ,则会导致外部类实例(Activity或者Service)不会被回收,这就造成了外部类实例的泄露。 所以正确处理Handler等之类的内部类,应该将自己的Handler定义为静态内部类,并且在类中增加一个成员变量,用来弱引用外部类实例,如下:

     
    public class OutterClass
    {
            ......
            ......
            static class InnerClass
            {
            	private final WeakReference<OutterClass> mOutterClassInstance;
            	......
            	......
            }
    }

     HandlerThread的使用也需要注意:

      当我们在activity里面创建了一个HandlerThread,代码如下:

    public classMainActivity extends Activity
    {
        @Override
        public void onCreate(BundlesavedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND); 
            mThread.start();
    MyHandler mHandler = new MyHandler( mThread.getLooper( ) );
    …….
    …….
    …….
    }
        @Override
        public void onDestroy()
        {
        super.onDestroy();
        }
    }

    这个代码存在泄漏问题,因为HandlerThread实现的run方法是一个无限循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,HandlerThread线程的数量会随着activity重建次数的增加而增加。

    应该在onDestroy时将线程停止掉:mThread.getLooper().quit();

    另外,对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join();

     另外还有一种使用Handler的修改方法 Android内存泄露之Handler

    4、注册某个对象后未反注册

    注册广播接收器、注册观察者等等,比如:

    假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

      但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被GC回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。

    虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。

    5、集合中对象没清理造成的内存泄露

      我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

    比如某公司的ROM的锁屏曾经就存在内存泄漏问题:

    这个泄漏是因为LockScreen每次显示时会注册几个callback,它们保存在KeyguardUpdateMonitor的ArrayList<InfoCallback>、ArrayList<SimStateCallback>等ArrayList实例中。但是在LockScreen解锁后,这些callback没有被remove掉,导致ArrayList不断增大, callback对象不断增多。这些callback对象的size并不大,heap增长比较缓慢,需要长时间地使用手机才能出现OOM,由于锁屏是驻留在system_server进程里,所以导致结果是手机重启。

    6、资源对象没关闭造成的内存泄露

      资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

      程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在长时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

    7、一些不良代码成内存压力

    有些代码并不造成内存泄露,但是它们或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,增加vm的负担,造成不必要的内存开支。

    7.1,Bitmap使用不当

        第一、及时的销毁。

        虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

        第二、设置一定的采样率。

        有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:

      

    private ImageView preview;  
    BitmapFactory.Options options = newBitmapFactory.Options();  
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一  
    Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap); 

    第三、巧妙的运用软引用(SoftRefrence)

    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下:

    SoftReference<Bitmap>  bitmap_ref  = new SoftReference<Bitmap>(BitmapFactory.decodeStream(inputstream)); 
    ……
    ……
    if (bitmap_ref .get() != null)
              bitmap_ref.get().recycle();

    7.2、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable , vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃。

    四、内存泄漏调试: 

    (1).内存监测工具 DDMS --> Heap  无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方。Android tools中的DDMS就带有一个很不错的内存监测工具Heap(这里我使用eclipse的ADT插件,并以真机为例,在模拟器中的情况类似)。用 Heap监测应用进程使用内存情况的步骤如下: 

    1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的; 

    2. 将手机通过USB链接至电脑,链接时需要确认手机是处于“USB调试”模式,而不是作为“Mass Storage”; 

    3. 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息; 

    4. 点击选中想要监测的进程,比如system_process进程; 

    5. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标; 

    6. 点击Heap视图中的“Cause GC”按钮; 

    7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。 

    说明:  a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作; 

    b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化; 

    c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。    如何才能知道我们的程序是否有内存泄漏的可能性呢。

    这里需要注意一个值:Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断: 

    a) 不断的操作当前应用,同时注意观察data object的Total Size值; 

    b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对 象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平; 

    c) 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,    直到到达一个上限后导致进程被kill掉。 

    d) 此处已system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过3.55后进程就会被kill。      

    总之,使用DDMS的Heap视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。 


    (2).内存分析工具 MAT(Memory Analyzer Tool) 

    (一) 生成.hprof文件 

    (二) 使用MAT导入.hprof文件 

    (三) 使用MAT的视图工具分析内存

  • 相关阅读:
    是否可以在tomcat servlet中禁用jsessionid?
    一个屌丝程序猿的人生(一百二十一)
    postman 使用
    【Ubuntu】命令行安装deb安装包
    opencv的cascade分类器源码分析
    Face Detection – OpenCV, Dlib and Deep Learning ( C++ / Python )
    小样本目标检测研究现状
    图像特征提取三大法宝:HOG特征,LBP特征,Haar特征
    搞清楚nand flash和 nor flash 以及 spi flash 和cfi flash 的区别
    xhsell远程vmware ubuntu16.04
  • 原文地址:https://www.cnblogs.com/Alex80/p/13443433.html
Copyright © 2011-2022 走看看