内存泄露

内存泄露

Android应用的内存泄露,其实就是Java虚拟机的堆内存泄漏.java

转载出处http://blog.csdn.net/ccj659

1.知识储备

1.Java内存模型

 
相关内存对象模型,参照博客精讲Java内存模型android

1) 寄存器(register)。这是最快的保存区域,这是主要因为它位于处理器内部。然而,寄存器的数量十分有限,因此寄存器是须要由编译器分配的。咱们对此没有直接的控制权,也不可能在本身的程序里找到寄存器存在的任何踪影。git

(2) 堆栈(stack)在执行函数(方法)时,函数一些内部变量的存储均可以放在栈上面建立,函数执行结束的时候这些存储单元就会自动被释放掉。位于通用RAM(随机访问存储器)中。可经过它的“堆栈指针” 得到处理的直接支持。堆栈指针若向下移,会建立新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。github

(3) 堆(heap)。一种通用性的内存池(也在RAM区域),堆是不连续的内存区域,堆空间比较灵活也特别大。其中保存了Java对象(对象里面的成员变量也在其中)。在堆里分配存储空间时会花掉更长的时间!也叫作动态内存分配。数据库

(4) 静态存储(static storage)。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java 对象自己永远都不会置入静态存储空间,随着JVM的生命周期结束而结束,即当app彻底退出,他才会释放安全

(5) 常数存储(constant storage)。常数值一般直接置于程序代码内部。这样作是安全的,由于它们永远都不会改变。网络

(6) 非RAM 存储(non-storage-RAM)。若数据彻底独立于一个程序以外,则程序不运行时仍可存在,并在程序的控制范围以外。其中两个最主要的例子即是“ 流式对象”和“固定对象” 。对于流式对象,对象会变成字节流,一般会发给另外一台机器。而对于固定对象,对象保存在磁盘中。app


2.Gc回收流程

1.当eden满了,触发young GC;

2.young GC作2件事:一,去掉一部分没用的object;二,把老的还被引用的object发到survior里面,等下几回GC之后,survivor再放到old里面。

3.当old满了,触发full GC。full GC很消耗内存,把old,young里面大部分垃圾回收掉。这个时候用户线程都会被block。

3.Gc回收总结

1.JVM堆的大小决定了GC的运行时间。若是JVM堆的大小超过必定的限度,那么GC的运行时间会很长。

2.对象生存的时间越长,GC须要的回收时间也越长,影响了回收速度。

3.大多数对象都是短命的,因此,若是能让这些对象的生存期在GC的一次运行周期内,wonderful!

4.应用程序中,创建与释放对象的速度决定了垃圾收集的频率。

5.若是GC一次运行周期超过3-5秒,这会很影响应用程序的运行,若是能够,应该减小JVM堆的大小了。

6.前辈经验之谈:一般状况下,JVM堆的大小应为物理内存的80%。

3.内存抖动

内存抖动这个术语可用于描述在极短期内分配给对象的过程.eclipse

例如,当你在循环语句中配置一系列临时对象,或者在绘图功能中配置大量对象时,这至关于内循环,当屏幕须要从新绘制或出现动画时,你须要一帧帧使用这些功能,不过它会迅速增长你的堆的压力。jvm

Memory Monitor 内存抖动图例:


2.内存泄漏对程序形成的影响

1.直接:消耗内存,形成系应用自己的内存不足OutOfMemory.

一个android应用程序,其实就是一个jvm虚拟机实例,而一个jvm的实例,在初始的时候,大小不等 16M,32M,64M(根据手机厂商和版本不一样而不一样),固然大小也能够修改,参考修改博客

2.间接:gc回收频繁 形成应用卡顿ANR.

GC回收时间过长致使卡顿 
首先,当内存不足的时候,gc会主动回收没用的内存.可是,内存回收也是须要时间的.

上图中,android在画图(播放视频等)的时候,draw到界面的对象,和gc回收垃圾资源之间高频率交替的执行.就会产生内存抖动.

不少数据就会污染内存堆,立刻就会有许多GCs启动,因为这一额外的内存压力,也会产生忽然增长的运算形成卡顿现象

任何线程的任何操做都会须要暂停,等待GC操做完成以后,其余操做才可以继续运行,因此垃圾回收运行的次数越少,对性能的影响就越少

3.内存泄露的缘由

内存泄漏的本质:再也不用到的对象,被错误引用,而没法被回收

未引用对象能够被垃圾回收机制回收,而被引用对象不能被垃圾回收机制回收。 
当内存不足,gc会回收垃圾内存 
垃圾内存是 没有别人使用的内存,好的内存

内存泄漏 是 正在被别人使用的的内存,不属于垃圾内存

堆引用内存泄漏(Heap leak)

1.静态变量持有 已经没有用的对象,致使对象没法被回收.例如静态集合类引发内存泄露

 2.单例中持有的引用,当activity从新构建后,单例持有的是上一个activity实例.致使上一个没法被回收.

 3.事件监听器和回调.若是一个类注册了监听器,但当该类再也不被使用后没有注销监听器,可能会发生内存泄漏。

 4.静态内部类,持有 对象.

 5.Handler 内存泄漏

系统资源泄露(Resource Leak)

主要指程序使用系统分配的资源好比 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,致使系统资源的浪费,严重可致使系统效能下降,系统运行不稳定。在try代码块里建立链接,在finally里释放链接,就可以避免此类内存泄漏。

1.bitmap资源未释放

2.IO流未关闭

3.Cursor使用完后未释放

4.各类链接(网络,数据库,socket等)

4.内存泄露的分析工具


在android studio 中有如下几种工具,来进行内存泄漏优化分析(eclipse也有相似工具).

1.Memory Monitor 内存监视器.

2.Dump java heap

3.Android Device Monitor(eclipse系列工具类)

4.第三方库LeakCanary(极其简单)

leakcanary的github地址


5.内存泄露的实例解决方案

与其说解决内存泄漏,更应该说是 避免内存泄露 .由于内存泄漏一旦产生,即便须要重启JVM,也就是重启应用,内存从新开始计算.即便这样,也无法解决

1.单例形成的内存泄露

[java] view plain copy
  1. /** 
  2.  * Created by ccj on 2016/11/3. 
  3.  */  
  4.   
  5. public class SingleExample {  
  6.   
  7.     private static SingleExample mExample;  
  8.     private Context context;  
  9.   
  10.     private SingleExample(Context context) {  
  11.         this.context = context;  
  12.     }  
  13.   
  14.     /** 
  15.      * 当MainActivity销毁再重建后,此时的context,不会走 if (mExample == null) ,而是直接返回. 
  16.      * 此时的Context 仍是上一个activity实例的Context,因此,上一个activity实例并未被释放,形成内存泄漏 
  17.      *  
  18.      * 此时,只须要将application的上下文,做为context便可解决问题 
  19.      * @param context 
  20.      * @return 
  21.      */  
  22.     public static SingleExample getExampleInstance(Context context) {  
  23.         if (mExample == null) {  
  24.             mExample = new SingleExample(context);  
  25.         }  
  26.         return mExample;  
  27.   
  28.     }  
  29.   
  30.   
  31. }  


2.非静态内部类(匿名内部类) 的内存泄漏

非静态内部类实例:

[java] view plain copy
  1. public class MainActivity extends Activity {  
  2.   
  3.         //会持有MainActivity实例。MainActivity.this.a  
  4.         public void load(){  
  5.         new Thread(new Runnable() {  
  6.             @Override  
  7.             public void run() {  
  8.             while(true){  
  9.                 try {  
  10.                 int b=a;  
  11.                 Thread.sleep(500);  
  12.                 } catch (InterruptedException e) {  
  13.                 e.printStackTrace();  
  14.                 }  
  15.             }  
  16.             }  
  17.         }).start();  
  18.         }  
  19.     }  


解决方案:

将非静态内部类修改成静态内部类。由于静态内部类不会隐士持有外部类

3.Handler 形成的内存泄漏

Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。

软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

Handler 实例:

[java] view plain copy
  1. /*:在 Activity 中避免使用非静态内部类,好比上面咱们将 Handler 声明为静态的, 
  2.     则其存活期跟 Activity 的生命周期就无关了。同时经过弱引用的方式引入 Activity, 
  3.     避免直接将 Activity 做为 context 传进去, 
  4.     推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空 
  5. */  
  6. public class SampleActivity extends Activity {  
  7.     private final Handler mLeakyHandler = new Handler() {  
  8.     @Override  
  9.     public void handleMessage(Message msg) {  
  10.       // ...  
  11.     }  
  12.     }  
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.     super.onCreate(savedInstanceState);  
  16.     // Post a message and delay its execution for 10 minutes.  
  17.     mLeakyHandler.postDelayed(new Runnable() {  
  18.       @Override  
  19.       public void run() { /* ... */ }  
  20.     }, 5000);  
  21.     // Go back to the previous Activity.  
  22.     finish();  
  23.     }  
  24. }  


解决方案

[java] view plain copy
  1. //改进机制  
  2.   
  3. /*固然在Activity销毁时候也应该取消相应的任务AsyncTask.cancel(),避免任务在后台执行浪费资源*/。  
  4.     public class MainActivity extends AppCompatActivity {  
  5.         private MyHandler mHandler = new MyHandler(this);  
  6.         private TextView mTextView ;  
  7.         private static class MyHandler extends Handler {  
  8.             private WeakReference<Context> reference;  
  9.             public MyHandler(Context context) {  
  10.                 reference = new WeakReference<>(context);  
  11.             }  
  12.             @Override  
  13.             public void handleMessage(Message msg) {  
  14.                 MainActivity activity = (MainActivity) reference.get();  
  15.                 if(activity != null){  
  16.                     activity.mTextView.setText("");  
  17.                 }  
  18.             }  
  19.         }  
  20.   
  21.         @Override  
  22.         protected void onCreate(Bundle savedInstanceState) {  
  23.             super.onCreate(savedInstanceState);  
  24.             setContentView(R.layout.activity_main);  
  25.             mTextView = (TextView)findViewById(R.id.textview);  
  26.             loadData();  
  27.         }  
  28.   
  29.         private void loadData() {  
  30. //...request  
  31.             Message message = Message.obtain();  
  32.             mHandler.sendMessage(message);  
  33.         }  
  34.         //注意释放  
  35.         @Override  
  36.         protected void onDestroy() {  
  37.             super.onDestroy();  
  38.             mHandler.removeCallbacksAndMessages(null);  
  39.         }  
  40.     }  


4.监听器注册形成的内存泄漏

在观察者模式中, 有一个统一的观察者collector集合, 
事件监听器和回调.若是一个类注册了监听器,但当该类再也不被使用后没有注销监听器,可能会发生内存泄漏。例如,系统的传感器sensor监听器, 
窗口改变监听WindowFocusChangeListener等等.

监听器实例:

系统级别的监听,例如重力感应监听sensorManager.registerListener(),若是不及时取消注册,就会形成内存泄漏.

首先看Sensor中的官方注释

[java] view plain copy
  1. * Always make sure to disable sensors you don't need, especially when your activity is paused. Failing to do so can drain the battery in just a few hours. Note that the system will <i>not</i> disable sensors automatically when the screen turns off.  

[java] view plain copy
  1. SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);  
  2.        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);  
  3. //监听      
  4.    sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);  


实例1解决方案:

[java] view plain copy
  1. protected void onPause() {  
  2.           super.onPause();  
  3.           mSensorManager.unregisterListener(this);  
  4.     }  


观察者模式实例2:

[java] view plain copy
  1. //本身的观察者模式.  
  2. public class ListenerCollector {  
  3.     static private WeakHashMap<View,MyView.MyListener> sListener = new WeakHashMap<>();  
  4.     public void setsListener(View view, MyView.MyListener listener){ sListener.put(view,listener);}  
  5.     //解决方案  
  6.     public static void clearListeners(){  
  7.         //hashmap移除监听。  
  8.         sListener.clear();  
  9.     };  
  10. }  

[java] view plain copy
  1. public class MyView extends View{  
  2.     public  MyView(Context context){  
  3.         super(context);  
  4.         init();  
  5.     }  
  6.   
  7.     public interface MyListener{  
  8.         public void myListenerCallback();  
  9.     }  
  10.   
  11.     private void init(){  
  12.         ListenerCollector collector = new ListenerCollector();  
  13.         collector.setsListener(this,myListener);  
  14.     }  
  15.   
  16.     private MyListener myListener = new MyListener() {  
  17.         @Override  
  18.         public void myListenerCallback() {  
  19.             System.out.print("有被调用");  
  20.         }  
  21.     };  
  22.   
  23. }  


[java] view plain copy
  1. //activity调用处  
  2.   @Override  
  3.     protected void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         MyView myView = new MyView(this);  
  6.         setContentView(myView);  
  7.   
  8.     }  


实例2解决方案

[java] view plain copy
  1. @Override  
  2.     protected void onStop() {  
  3.         super.onStop();  
  4.         ListenerCollector.clearListeners();  
  5.     }  


5.资源未关闭形成的内存泄漏

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


6.内存泄漏总结

一、对于生命周期比Activity长的对象若是须要应该使用ApplicationContext

二、在涉及到Context时先考虑ApplicationContext,固然它并非万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景以下: 
这里写图片描述 

其中:NO1表示Application和Service能够启动一个Activity,不过须要建立一个新的task任务队列。而对于Dialog而言,只有在Activity中才能建立

三、对于须要在静态内部类中使用非静态外部成员变量(如:Context、View ),能够在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

四、对于生命周期比Activity长的内部类对象,而且内部类中使用了外部类的成员变量.将内部类改成静态内部类,静态内部类中使用弱引用来引用外部类的成员变量

五、对于再也不须要使用的对象,显示的将其赋值为null,好比使用完Bitmap后先调用recycle(),再赋为null

六、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期