• 集合类 集合类如果仅仅有添加元素的方法,而没有相应的删除...不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的

    集合类
    集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

    单例模式
    不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被JVM正常回收,导致内存泄漏。
    如果需要Context,尽量引用Application,而不用Activity。

    Android组件或特殊集合对象的使用
    BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在Activity onDestory或者某类生命周期结束之后一定要unregister或者close掉,否则这个Activity类会被system强引用,不会被内存回收。不要直接对Activity进行直接引用作为成员变量,如果不得不这么做,请用private WeakReference mActivity来做,相同的,对于Service等其他有自己生命周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄漏的可能。

    Handler
    Handler的生命周期与Activity不一致
    由于Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的。
    当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
    当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
    只要Handler发送的Message尚未被处理,则该Message及发送它的Handler对象将被线程MessageQueue一直持有。因此这种实现方式一般很难保证跟View或者Activity的生命周期保持一致,故很容易导致无法正确释放。

    handler引用Activity阻止了GC对Acivity的回收
    在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
    如果外部类是Activity,则会引起Activity泄露。
    当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

    如上所述,Handler的使用要尤为小心,否则将很容易导致内存泄漏的发生。

    错误示例:

    private final Handler mHandler = new Handler() { 
     @Override          
     public void handleMessage(Message msg) {              
     // ...         
     }     
    };

    解决办法:
    使用显形的引用,1.静态内部类。 2. 外部类
    使用弱引用 2. WeakReference

    正确示例:

    private static class MyHandler extends Handler {         
     private final WeakReference<HandlerActivity2> mActivity;          
     public MyHandler(HandlerActivity2 activity) {  
      mActivity = new WeakReference<HandlerActivity2>(activity);              
     }          
     @Override     
     public void handleMessage(Message msg) {      
     if (mActivity.get() == null) {                  
     return;              
     }              
     mActivity.get().todo();     
         }   
      @Override    
      public void onDestroy() {  
          //  If null, all callbacks and messages will be removed.       
             mHandler.removeCallbacksAndMessages(null); 
               }

    http://blog.csdn.net/zhuanglonghai/article/details/38233069

    ThreadHandler
    使用ThreadHandler步骤:
    http://www.cnblogs.com/hnrainll/p/3597246.html
    创建一个HandlerThread,即创建了一个包含Looper的线程

    HandlerThread handlerThread = new HandlerThread("test");
    handlerThread.start(); //创建HandlerThread后一定要记得start()
    //获取HandlerThread的Looper
    
    Looper looper = handlerThread.getLooper();
    //创建Handler,通过Looper初始化
    
    Handler handler = new Handler(looper);

    通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。
    如果我们在Activity的onCreate中进行上面的初始化,不再进行其他工作,那么就有可能造成内存泄漏。
    因为不过Activity finish后,进程没有退出,那么创建的test会一直存在。

    解决办法:
    在onDestory中调用handlerThread.quit()或handlerThread.quitSafely();

    Thread内存泄漏
    线程也是造成内存泄漏的一个重要的源头。线程产生的内存泄漏主要原因在于线程生命周期的不可控。比如线程是Activity的内部类,则线程对象中保存了Activity的一个引用,当线程的run函数耗时较长没有结束时,线程对象是不会被销毁的,因此它引用的老的Activity也不会被销毁,因此就出现了内存泄漏的问题。

    一些不良代码造成的内存压力
    这些代码并不造成内存泄漏,但是它们,或是对没使用的内存没有进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。

    Bitmap没调用recycle()
    Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后将它设置为null。因为加载Bitmap对象的内存空间,一部分是java的,一部分C的(因为Bitmap分配的底层是通过JNI调用的)。而这个recycle()就是针对C部分的内存释放。

    构造Adapter时,没有使用缓存的convertView。
    http://blog.csdn.net/u010878994/article/details/51553415

    展开全文
  • 最近在整理Android内存泄漏相关内容,目前整理出了以下八种情形,后期还会继续补充,请持续关注~单例造成的内存泄漏非静态内部类创建静态实例造成的内存泄漏Handler造成的内存泄漏线程造成的内存泄漏资源未关闭造成...

    最近在整理Android内存泄漏相关内容,目前整理出了以下八种情形,后期还会继续补充,请持续关注~

    1. 单例造成的内存泄漏
    2. 非静态内部类创建静态实例造成的内存泄漏
    3. Handler造成的内存泄漏
    4. 线程造成的内存泄漏
    5. 资源未关闭造成的内存泄漏
    6. 使用ListView时造成的内存泄漏
    7. 集合容器中的内存泄漏
    8. WebView造成的泄漏

    一、单例造成的内存泄漏

        由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

    二、非静态内部类创建静态实例造成的内存

        例如,有时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现如下写法:

    public class MainActivity extends AppCompatActivity {
       private static TestResource mResource = null;
       @Override
      protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
           if(mResource == null){
              mResource = new TestResource();
           }
          //...
       }
      
     class TestResource {
      //...
       }
    }
    

        这样在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据。虽然这样避免了资源的重复重复创建,但是这种写法却会造成内存泄漏。因为非静态内部类默认会持有外部类的引用,而非静态的内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这样导致了该静态实例一直会持有该Actitity的引用,从而导致Activity的内存资源不能被正常回收。

    解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。


    三、Handler造成的内存泄漏

    示例:创建匿名内部类的静态对象

    public class MainActivity extends AppCompatActivity {
       private final Handler handler = new Handler() {
        @Override
       public void handleMessage(Message msg) {
            // ...
        }
      };
    
      @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          new Thread(new Runnable() {
              @Override
              public void run() {
                 // ...
                 handler.sendEmptyMessage(0x123);
               }
         });
      }
    }
    

    1、从Android的角度

    当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue关联起来。所有发送到MessageQueue的Message都会持有Handler的引用,所以Looper会据此回调Handler的handleMessage()方法会处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。

    2、Java角度

        在Java中,非静态内部类和匿名内部类都会潜在持有它们所属的外部类的引用,但是静态内部类不会。

    对上述的示例进行分析,当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是mainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。

    解决方法:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。


    四、线程造成的内存泄漏

    示例:AsyncTask和Runnable

    AsyncTask和Runnable都使用了匿名内部类,那么他们将持有其所在Activity的隐式引用。如果任务在Activity小会之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。

    解决方法:将Asynctask和Runnable类独立出来或者使用静态内部类,这样可以避免内存泄漏。


    五、资源未关闭造成的内存泄漏

        对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。

    1、比如在Activity中register了一个BroadcastReceiver,但在Activity结束后没有unregister该BroadcastReceiver。

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

    3、对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出的时候一定要确保我们的资源性对象已经关闭。

    4、Bitmap对象不再使用时调用recycle()释放内存。2.3以后bitmap应该是不需要手动recycle了,内存已经在java层。


    六、使用ListView时造成的内存泄漏

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

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

    解决方法:在构造Adapter时,使用缓存的convertView。


    七、集合容器中的内存泄漏

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

    解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。


    八、WebView造成的泄漏

        当我们不要使用WebView对象时,应该调用它的deatory()函数来小会它,并释放其占用的内存,否则其长期占用内存也不能被回收,从而造成内存泄漏。

    解决方法:为WebView另外开启一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。    








    展开全文
  • Android内存泄漏(线程造成的内存泄漏和资源未关闭造成的内存泄漏



    一.线程造成的内存泄漏

    对于线程造成的内存泄漏,也是平时比较常见的leakCanary官方Demo就是线程成造成的内存泄漏,使用了AsyncTask去执行异步线程,现在我们换个写法,直接使用Thread:

    1. 新建工程,配置好leakCanary环境

    2. 直接在MainActivity添加如下代码:

    红色方框内的代码,可能每个人都这样写过。

    OK ,我们执行一下,然后做如下操作:

    1 MainActivity页面打开后,在20秒内点击手机返回键

    2. 等待10秒

    操作完成,leakCanary检测出内存泄漏。



    分析原因:和上面几个案例的原因类似,不知不觉又搞了一个匿名内部类Runnable,对当前Activity都有一个隐式引用。如果Activity在销毁的时候Runable内部的任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:


    上面代码中,自定义了静态的内部类MyRunable,实现了Runable ,然后在使用的时候实例化它。

    运行代码后做如上相同操作,发现leakCannary没有检测出内存泄漏。

     


    二. 资源未关闭造成的内存泄漏

    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的代码,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

     

     

    总结

    以上Android开发中常见的内存泄漏问题及解决办法,能对内存泄漏有一定的认识和见解,是同学们面试时的一个极其有利的加分项, 内存泄漏是很多有一定开发经验的程序员都会犯的错误,掌握这些,代表你确确实实做过一些东西,并有一定的总结。



    展开全文
  • 这篇文章主要给大家介绍了关于Android内存泄漏的轻松解决方法,文中通过示例代码介绍的非常详细,对各位Android具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 前言 内存管理的目的就是让我们在开发...

    这篇文章主要给大家介绍了关于Android内存泄漏的轻松解决方法,文中通过示例代码介绍的非常详细,对各位Android具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    前言

    内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题。内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」。

    如果程序发生了内存泄露,则会带来以下这些问题

    • 应用可用的内存减少,增加了堆内存的压力
    • 降低了应用的性能,比如会触发更频繁的 GC
    • 严重的时候可能会导致内存溢出错误,即 OOM Error

    下面我们从基础说起

    基础知识

    Java 的内存分配简述

    • 方法区(non-heap):编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量;
    • 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;
    • 堆区(heap):通常用来存放 new 出来的对象。由 GC 负责回收。

    Java四种不同的引用类型

    • 强引用(Strong Reference):JVM 宁愿抛出 OOM,也不会让 GC 回收存在强引用的对象。
    • 软引用(Soft Reference) :一个对象只具有软引用,在内存不足时,这个对象才会被 GC 回收。
    • 弱引用(weak Reference):在 GC 时,如果一个对象只存在弱引用,那么它将会被回收。
    • 虚引用(Phantom Reference):任何时候都可以被 GC 回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为 GC 回收 Object 的标志。

    与 Android 中的差异:在 2.3 以后版本中,即使内存够用,Android 系统会优先将 SoftReference 的对象提前回收掉, 其他和 Java 中是一样的。
    因此谷歌官方建议用LruCache(least recentlly use 最少最近使用算法)。会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定。

    什么是内存泄漏?

    • 对于 C++ 来说,内存泄漏就是 new 出来的对象没有 delete,俗称野指针;
    • 而对于 java 而言,就是存放在堆上的 Object 无法被 GC 正常回收。

    内存泄漏根本原因

    长生命周期的对象持有短生命周期对象**强/软引用**,导致本应该被回收的短生命周期的对象却无法被正常回收。

    例如在单例模式中,我们常常在获取单例对象时需要传一个 Context 。单例对象是一个长生命周期的对象(应用程序结束时才终结),而如果我们传递的是某一个 Activity 作为 context,那么这个 Activity 就会因为引用被持有而无法销毁,从而导致内存泄漏。

    内存泄漏的危害

    • 运行性能的问题: Android在运行的时候,如果内存泄漏将导致其他组件可用的内存变少,一方面会使得GC的频率加剧,在发生GC的时候,所有进程都必须进行等待,GC的频率越多,从而用户越容易感知到卡顿。另一方面,内存变少,将可能使得系统会额外分配给你一些内存,而影响整个系统的运行状况。
    • 运行崩溃问题: 内存泄露是内存溢出(OOM)的重要原因之一,会导致 Crash。如果应用程序在消耗光了所有的可用堆空间,那么再试图在堆上分配新对象时就会引起 OOM(Out Of Memory Error) 异常,此时应用程序就会崩溃退出。

    内存泄漏的典型案例

    永远的单例(Singleton)

    由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,一不小心让单例无限制的持有 Activity 的强引用就会导致内存泄漏。

    解决方案

    把传入的 Context 改为同应用生命周期一样长的 Application 中的 Context。

    通过重写 Application,提供 getContext 方法,那样就不需要在获取单例时传入 context。

    public class BaseApplication extends Application{
     private static ApplicationContext sContext;
     @Override
     public void onCreate(){
     super.onCreate();
     sContext = getApplicationContext();
     }
     public static Context getApplicationContext(){
     return sContext;
     }
    }

    Handler引发的内存泄漏

    由于 Handler 属于 TLS(Thread Local Storage)变量,导致它的生命周期和 Activity 不一致。因此通过 Handler 来更新 UI 一般很难保证跟 View 或者 Activity 的生命周期一致,故很容易导致无法正确释放。

    例如:

    public class HandlerBadActivity extends AppCompatActivity {
     private final Handler handler = new Handler(){//非静态内部类,持有外部类的强引用
     @Override
     public void handleMessage(Message msg) {
     super.handleMessage(msg);
     }
     };
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_handler_bad);
     // 延迟 5min 发送一个消息
     handler.postDelayed(new Runnable() {
     //内部会将该 Runable 封装为一个 Message 对象,同时将 Message.target 赋值为 handler
     @Override
     public void run() {
     //do something
     }
     }, 1000 * 60 * 5);
     this.finish();
     }
    }

    上面的代码中发送了了一个延时 5 分钟执行的 Message,当该 Activity 退出的时候,延时任务(Message)还在主线程的 MessageQueue 中等待,此时的 Message 持有 Handler 的强引用(创建时通过 Message.target 进行指定),并且由于 Handler 是 HandlerBadActivity 的非静态内部类,所以 Handler 会持有一个指向 HandlerBadActivity 的强引用,所以虽然此时 HandlerBadActivity 调用了 finish 也无法进行内存回收,造成内存泄漏。

    解决方法

    将 Handler 声明为静态内部类,但是要注意**如果用到 Context 等外部类的 非static 对象,还是应该使用 ApplicationContext 或者通过弱引用来持有这些外部对象**。

    public class HandlerGoodActivity extends AppCompatActivity {
     private static final class MyHandler extends Handler{//声明为静态内部类(避免持有外部类的强引用)
     private final WeakReference<HandlerGoodActivity> mActivity;
     public MyHandler(HandlerGoodActivity activity){
     this.mActivity = new WeakReference<HandlerGoodActivity>(activity);//使用弱引用
     }
     @Override
     public void handleMessage(Message msg) {
     HandlerGoodActivity activity = mActivity.get();
     if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判断 activity 是否为空,以及是否正在被销毁、或者已经被销毁
     removeCallbacksAndMessages(null);
     return;
     }
     // do something
     }
     }
     private final MyHandler myHandler = new MyHandler(this);
    }

    慎用 static 成员变量

    static 修饰的变量位于内存的方法区,其生命周期与 App 的生命周期一致。 这必然会导致一系列问题,如果你的 app 进程设计上是长驻内存的,那即使 app 切到后台,这部分内存也不会被释放。

    解决方法

    不要在类初始化时初始化静态成员,也就是可以考虑懒加载。架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。

    当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下:

    功能 Application Service Activity
    Start an Activity NO1 NO1 YES
    Show a Dialog NO NO YES
    Layout Inflation YES YES YES
    Start an Service YES YES YES
    Bind an Service YES YES YES
    Send a Broadcast YES YES YES
    Register BroadcastReceiver YES YES YES
    Load Resource Values YES YES YES
    • NO1 表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列。
    • 对于 Dialog 而言,只有在 Activity 中才能创建。

    使用系统服务引发的内存泄漏

    为了方便我们使用一些常见的系统服务,Activity 做了一些封装。比如说,可以通过 getPackageManager在 Activtiy 中获取 PackageManagerService,但是,里面实际上调用了 Activity 对应的 ContextImpl 中的 getPackageManager 方法

    ContextWrapper#getPackageManager

    @Override
    public PackageManager getPackageManager() {
     return mBase.getPackageManager();
    }

    ContextImpl#getPackageManager

    @Override
    public PackageManager getPackageManager() {
     if (mPackageManager != null) {
     return mPackageManager;
     }
     IPackageManager pm = ActivityThread.getPackageManager();
     if (pm != null) {
     // Doesn't matter if we make more than one instance.
     return (mPackageManager = new ApplicationPackageManager(this, pm));//创建 ApplicationPackageManager
     }
     return null;
    }

    ApplicationPackageManager#ApplicationPackageManager

    ApplicationPackageManager(ContextImpl context,
        IPackageManager pm) {
     mContext = context;//保存 ContextImpl 的强引用
     mPM = pm;
    }
    
    private UserManagerService(Context context, PackageManagerService pm,
     Object packagesLock, File dataDir) {
     mContext = context;//持有外部 Context 引用
     mPm = pm;
     //代码省略
    }

    PackageManagerService#PackageManagerService

    public class PackageManagerService extends IPackageManager.Stub {
     static UserManagerService sUserManager;//持有 UMS 静态引用
     public PackageManagerService(Context context, Installer installer,
     boolean factoryTest, boolean onlyCore) {
      sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
     }
    }

    遇到的内存泄漏问题是因为在 Activity 中调用了 getPackageManger 方法获取 PMS ,该方法调用的是 ContextImpl,此时如果ContextImpl 中 PackageManager 为 null,就会创建一个 PackageManger(ContextImpl 会将自己传递进去,而 ContextImpl 的 mOuterContext 为 Activity),创建 PackageManager 实际上会创建 PackageManagerService(简称 PMS),而 PMS 的构造方法中会创建一个 UserManger(UserManger 初始化之后会持有 ContextImpl 的强引用)。
    只要 PMS 的 class 未被销毁,那么就会一直引用着 UserManger ,进而导致其关联到的资源无法正常释放。

    解决办法

    将getPackageManager()改为 getApplication()#getPackageManager() 。这样引用的就是 Application Context,而非 Activity 了。

    远离非静态内部类和匿名类

    因为使用非静态内部类和匿名类都会默认持有外部类的引用,如果生命周期不一致,就会导致内存泄漏。

    public class NestedClassLeakActivity extends AppCompatActivity {
    
     class InnerClass {//非静态内部类
    
     }
    
     private static InnerClass sInner;//指向非静态内部类的静态引用
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_nested_class);
     if (sInner == null) {
      sInner = new InnerClass();//创建非静态内部类的实例
     }
     }
    }

    非静态内部类默认会持有外部类的引用,而外部类中又有一个该非静态内部类的静态实例,该静态实例的生命周期和应用的一样长,而静态实例又持有 Activity 的引用,因此导致 Activity 的内存资源不能正常回收。

    解决方法

    将该内部类设为静态内部类 也可以将该内部类抽取出来封装成一个单例

    集合引发的内存泄漏

    我们通常会把一些对象的引用加入到集合容器(比如ArrayList)中,当我们不再需要该对象时(通常会调用 remove 方法),并没有把它的引用从集合中清理掉(其中的一种情况就是 remove 方法没有将不再需要的引用赋值为 null),下面以 ArrayList 的 remove 方法为例

    public E remove( int index) {
     // 数组越界检查
     RangeCheck(index);
     modCount++;
     // 取出要删除位置的元素,供返回使用
     E oldValue = (E) elementData[index];
     // 计算数组要复制的数量
     int numMoved = size - index - 1;
     // 数组复制,就是将index之后的元素往前移动一个位置
     if (numMoved > 0)
     System. arraycopy(elementData, index+1, elementData, index,
       numMoved);
     // 将数组最后一个元素置空(因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了),好让gc尽快回收
     elementData[--size ] = null; // Let gc do its work
     return oldValue;
    }

    WebView 引发的内存泄漏

    WebView 解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。
    由于占用的都是Native 堆内存,所以实际占用的内存大小不会显示在常用的 DDMS Heap 工具中( DMS Heap 工具看到的只是Java虚拟机分配的内存,即使Native堆内存已经占用了几百兆,这里显示的还只是几兆或十几兆)。只有使用 adb shell 中的一些命令比如 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()才能看到 Native 堆内存信息。

    据说由于 WebView 的一个 BUG,即使它所在的 Activity(或者Service) 结束也就是 onDestroy() 之后,或者直接调用 WebView.destroy()之后,它所占用这些内存也不会被释放。

    解决方法

    把使用了 WebView 的 Activity (或者 Service) 放在单独的进程里。

    • 系统在检测到应用占用内存过大有可能被系统干掉
    • 也可以在它所在的 Activity(或者 Service) 结束后,调用 System.exit(0),主动Kill掉进程。由于系统的内存分配是以进程为准的,进程关闭后,系统会自动回收所有内存。

    使用 WebView 的页面(Activity),在生命周期结束页面退出(onDestory)的时候,主动调用WebView.onPause()==以及==WebView.destory()以便让系统释放 WebView 相关资源。

    其他常见的引起内存泄漏原因

    Android 3.0 以下,Bitmap 在不使用的时候没有使用 recycle() 释放内存。

    非静态内部类的静态实例容易造成内存泄漏:即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。

    警惕线程未终止造成的内存泄露;譬如在 Activity 中关联了一个生命周期超过 Activity 的 Thread,在退出 Activity 时切记结束线程。

    一个典型的例子就是 HandlerThread 的 run 方法。该方法在这里是一个死循环,它不会自己结束,线程的生命周期超过了 Activity 生命周期,我们必须手动在 Activity 的销毁方法中中调用 thread.getLooper().quit() 才不会泄露。

    对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
    创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。

    避免代码设计模式的错误造成内存泄露;譬如循环引用,A 持有 B,B 持有 C,C 持有 A,这样的设计谁都得不到释放。

    总结

    以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

    阅读原文

    您可能感兴趣的文章:

    展开全文
  • Android内存泄漏(使用单例设计模式造成的内存泄漏


    使用单例模式造成的内存泄漏


        Android的单例模式在我们项目开发中经常会用到,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长, 这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

    Android中习惯使用单例的常见类: xxxManager , xxxHelper , xxxUtils 等

    我们举个例子:

    1. 新建一个工程。

    2. 配置好LeakCanary检测环境。

    3. 添加一个单例类AppManager,代码如下


    4 在MainActivity中使用此单例,代码如下:


    运行代码后做如下操作:

    1. 点手机返回键,退出MainActivity。

    2. 等待10秒

    做完如上操作后,LeakCanary提示MainActivity内存泄漏:


    我们来分析一下,为什么会内存泄漏呢?

    AppManager appManager=AppManager.getInstance(this);

    这句传入的是Activity的Context,我们都知道,Activty是间接继承于Context的,当这Activity退出时,Activity应该被回收, 但是单例中又持有它的引用,导致Activity回收失败,造成内存泄漏。

    为了以防误传Activity的Context , 我们可以修改一下单例的代码,如下:


    这样子修改,不管外面传入什么Context,最终都会使用Applicaton的Context,而我们单例的生命周期和应用的一样长,这样就防止了内存泄漏。

    修改完毕后,运行代码,重复以上操作,将会发现leakCanary没有检测出泄漏。




    展开全文
  • 1. 内存泄露简介 内存泄露,即Memory Leak,指程序中不再使用到的对象因某种原因从而无法被GC正常回收。发生内存泄露,会导致一些不再使用到的对象没有及时释放,这些对象占用了宝贵的内存空间,很容易导致后续需要...
  • java/Android内存泄漏和内存溢出详解java内存泄漏和溢出跟内存栈堆也是有一些关系,这里不解释! 这里主要讲解一下内存泄漏和溢出的区别和联系。 之前我跟别人说这两个的区别就说了:内存泄漏是因为内存对象一直被...
  • 内存使用中内存泄漏是开发Android应用中常见的,Android中可以使用LeakCanary来检测内存泄漏。   那么什么是内存泄漏内存泄漏简单解释就是:完全无用的对象,无法被被GC回收的现象就是内存泄漏。   内存...
  • 先讲一下内存泄露、内存溢出与内存抖动的含义 内存泄露就是一个对象已经使用完,可以被回收,但由于其他原因导致没有被GC导致内存泄露,危害?内存泄漏对于app没有直接的危害,即使app有发生内存泄漏的情况,也不...
  • android开发过程中,代码不规范,有时候很容易就会遭到内存泄露的侵袭。内存泄露,就是该死不死,有些对象或引用,本该释放的时候,因为某些原因释放不掉就会造成内存泄露。注意本文所写内容仅为本人自己的理解,...
  • android内存泄漏和内存溢出
  • 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会...
  • Android 内存泄漏总结

    2016-03-15 15:05:45
    Android 内存泄漏总结 android性能优化ATA内存管理内存泄漏 摘要 Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
  • 一般分析内存泄露, 首先运行程序,打开日志控制台,有一个标签Memory ,我们可以在这个界面分析当前程序使用的内存情况, 一目了然, 我们再也不需要苦苦的在logcat中寻找内存的日志了。图中蓝色区域,就是程序使用的内存...
  • Android内存泄露详解

    2016-08-01 16:30:38
    首先介绍一下内存泄露的概念:内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了...
  • Android内存泄露,大家在开发中一般都会遇到,特别是在快要发版的时候,各种泄露的问题都在等待解决。 为什么会产生内存泄漏?当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的...
  • Android内存泄漏(非静态内部类创建静态实例造成的内存泄漏
  • 转自:https://www.jianshu.com/p/ab4a7e353076如果一个无用对象(不需要...在Android开发中,一些不好的编程习惯会导致我们的开发的app存在内存泄露的情况。下面介绍一些在Android开发中常见的内存泄露场景及优化方...
  • 内存泄露大家应该不会陌生了,通常是因为该被释放的对象被占用,不能及时对其释放,导致GC无法正常回收。我们可以使用一些工具来监控和分析导致内存泄露的位置和原因。用Eclipse的同志应该都知道MAT(Memory Analysis...
  • 什么是内存泄露 Java使用有向图机制,通过GC自动检查内存中的对象,如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收。 ...
1 2 3 4 5 ... 20
收藏数 21,364
精华内容 8,545