深入内存泄露
  
  Android应用的内存泄露,其实就是java虚拟机的堆内存泄漏.
  
  当然,当应用有ndk,jni时,没有及时free,本地堆也会出现内存泄漏.
  
  本文只是针对JVM内存泄漏应用,进行阐述分析.
  
  1.知识储备
  
  1.Java内存模型
  
  相关内存对象模型,参照博客精讲Java内存模型
  
  1) 寄存器(register)。这是最快的保存区域,这是主要由于它位于处理器内部。然而,寄存器的数量十分有限,所以寄存器是需要由编译器分配的。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
  
  (2) 堆栈(stack)。在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。位于通用RAM(随机访问存储器)中。可通过它的“堆栈指针” 获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。
  
  (3) 堆(heap)。一种通用性的内存池(也在RAM区域),堆是不连续的内存区域,堆空间比较灵活也特别大。其中保存了Java对象(对象里面的成员变量也在其中)。在堆里分配存储空间时会花掉更长的时间!也叫做动态内存分配。
  
  (4) 静态存储(static storage)。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java 对象本身永远都不会置入静态存储空间,随着JVM的生命周期结束而结束,即当app完全退出,他才会释放。
  
  (5) 常数存储(constant storage)。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。
  
  (6) 非RAM 存储(non-storage-RAM)。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“ 流式对象”和“固定对象” 。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。
  
  2.GC回收机制
  
  引用自http://blog.csdn.net/jiafu1115/article/details/7024323
  
  首先JVM是对堆进行回收操作.
  
  1.JVM堆中分类
  
  (1) 新域young generation:存储所有新成生的对象
  
  (2) 旧域old generation:新域中的对象,经过了一定次数的GC循环后,被移入旧域
  
  (3) 永久域PermanentGeneration:存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。
  
  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.内存抖动
  
  内存抖动这个术语可用于描述在极短时间内分配给对象的过程.
  
  例如,当你在循环语句中配置一系列临时对象,或者在绘图功能中配置大量对象时,这相当于内循环,当屏幕需要重新绘制或出现动画时,你需要一帧帧使用这些功能,不过它会迅速增加你的堆的压力。
  
  Memory Monitor 内存抖动图例:
  
  2.内存泄漏对程序造成的影响
  
  1.直接:消耗内存,造成系应用本身的内存不足OutOfMemory.
  
  一个android应用程序,其实就是一个jvm虚拟机实例,而一个jvm的实例,在初始的时候,大小不等 16M,32M,64M(根据手机厂商和版本不同而不同),当然大小也可以修改,参考修改博客。
  
  2.间接:gc回收频繁 造成应用卡顿ANR.
  
  首先,当内存不足的时候,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.单例造成的内存泄露
  
  /**
  
  * Created by ccj on 2016/11/3.
  
  */
  
  public class SingleExample {
  
  private static SingleExample mExample;
  
  private Context context;
  
  private SingleExample(Context context) {
  
  this.context = context;
  
  }
  
  /**
  
  * 当MainActivity销毁再重建后,此时的context,不会走 if (mExample == null) ,而是直接返回.
  
  * 此时的Context 还是上一个activity实例的Context,所以,上一个activity实例并未被释放,造成内存泄漏
  
  *
  
  * 此时,只需要将application的上下文,作为context即可解决问题
  
  * @param context
  
  * @return
  
  */
  
  public static SingleExample getExampleInstance(Context context) {
  
  if (mExample == null) {
  
  mExample = new SingleExample(context);
  
  }
  
  return mExample;
  
  }
  
  }
  
  2.非静态内部类(匿名内部类) 的内存泄漏
  
  非静态内部类实例:
  
  public class MainActivity extends Activity {
  
  //会持有MainActivity实例。MainActivity.this.a
  
  public void load(){
  
  new Thread(new Runnable(www.dfgjyl.cn ) {
  
  @Override
  
  public void run(www.gcyl152.com/) {
  
  while(true)www.gcyL157.com{
  
  try {
  
  int b=a;
  
  Thread.sleep(500);
  
  } catch (InterruptedException e) {
  
  e.printStackTrace();
  
  }
  
  }
  
  }
  
  }).start();
  
  }
  
  }
  
  解决方案:
  
  将非静态内部类修改为静态内部类。因为静态内部类不会隐士持有外部类
  
  3.Handler 造成的内存泄漏
  
  Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。
  
  在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。
  
  软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
  
  Handler 实例:
  
  /*:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,
  
  则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,
  
  避免直接将 Activity 作为 context 传进去,
  
  推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空
  
  */
  
  public class SampleActivity extends Activity {
  
  private final Handler www.yigouyule2.cn mLeakyHandler www.michenggw.com= new Handler() {
  
  @Override
  
  public void handleMessage(Message www.mcyllpt.com msg) {
  
  // ...
  
  }
  
  }
  
  @Override
  
  protected void onCreate(Bundle savedInstanceState) {
  
  super.onCreate(savedInstanceState);
  
  // Post a message and delay its execution for 10 minutes.
  
  mLeakyHandler.postDelayed(new Runnable() {
  
  @Override
  
  public void run() { /* ... *www.mhylpt.com/ }
  
  }, 5000);
  
  // Go back to the previous Activity.
  
  finish();
  
  }
  
  }
  
  解决方案
  
  //改进机制
  
  /*当然在Activity销毁时候也应该取消相应的任务AsyncTask.cancel(),避免任务在后台执行浪费资源*/。
  
  public class MainActivity extends AppCompatActivity {
  
  private MyHandler mHandler = new MyHandler(this);
  
  private TextView mTextView ;
  
  private static class MyHandler extends Handler {
  
  private WeakReference<Context> reference;
  
  public MyHandler(Context context) {
  
  reference = new WeakReference<>(context);
  
  }
  
  @Override
  
  public void handleMessage(Message msg) {
  
  MainActivity activity = (MainActivity) reference.get();
  
  if(activity != null){
  
  activity.mTextView.setText("");
  
  }
  
  }
  
  }
  
  @Override
  
  protected void onCreate(Bundle savedInstanceState) {
  
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  mTextView = (TextView)findViewById(R.id.textview);
  
  loadData();
  
  }
  
  private void loadData() {
  
  //...request
  
  Message message = Message.obtain();
  
  mHandler.sendMessage(message);
  
  }
  
  //注意释放
  
  @Override
  
  protected void onDestroy() {
  
  super.onDestroy();
  
  mHandler.removeCallbacksAndMessages(null);
  
  }
  
  }
  
  4.监听器注册造成的内存泄漏
  
  在观察者模式中, 有一个统一的观察者collector集合,
  
  事件监听器和回调.如果一个类注册了监听器,但当该类不再被使用后没有注销监听器,可能会发生内存泄漏。例如,系统的传感器sensor监听器,
  
  窗口改变监听WindowFocusChangeListener等等.
  
  监听器实例:
  
  系统级别的监听,例如重力感应监听sensorManager.registerListener(),如果不及时取消注册,就会造成内存泄漏.
  
  首先看Sensor中的官方注释
  
  * 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.
  
  SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
  
  Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
  
  //监听
  
  sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
  
  实例1解决方案:
  
  protected void onPause() {
  
  * super.onPause();
  
  * mSensorManager.unregisterListener(this);
  
  * }
  
  观察者模式实例2:
  
  //自己的观察者模式.
  
  public class ListenerCollector {
  
  static private WeakHashMap<View,MyView.MyListener> sListener = new WeakHashMap<>();
  
  public void setsListener(View view, MyView.MyListener listener){ sListener.put(view,listener);}
  
  //解决方案
  
  public static void clearListeners(){
  
  //hashmap移除监听。
  
  sListener.clear();
  
  };
  
  }
  
  public class MyView extends View{
  
  public MyView(Context context){
  
  super(context);
  
  init();
  
  }
  
  public interface MyListener{
  
  public void myListenerCallback();
  
  }
  
  private void init(){
  
  ListenerCollector collector = new ListenerCollector();
  
  collector.setsListener(this,myListener);
  
  }
  
  private MyListener myListener = new MyListener() {
  
  @Override
  
  public void myListenerCallback() {
  
  System.out.print("有被调用");
  
  }
  
  };
  
  }
  
  //activity调用处
  
  @Override
  
  protected void onCreate(Bundle savedInstanceState) {
  
  super.onCreate(savedInstanceState);
  
  MyView myView = new MyView(this);
  
  setContentView(myView);
  
  }
  
  实例2解决方案
  
  @Override
  
  protected void onStop() {
  
  super.onStop();
  
  ListenerCollector.clearListeners();
  
  }
  
  5.资源未关闭造成的内存泄漏
  
  对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
  
  6.内存泄漏总结
  
  1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
  
  2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
  
  这里写图片描述
  
  其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建
  
  3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
  
  4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量.将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量
  
  5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
  
  6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

_063_Android_Android内存泄露的更多相关文章

  1. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

  2. 查看w3wp进程占用的内存及.NET内存泄露,死锁分析

    一 基础知识 在分析之前,先上一张图: 从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程. 在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方 ...

  3. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  4. 基于HTML5的WebGL应用内存泄露分析

    上篇(http://www.hightopo.com/blog/194.html)我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动, ...

  5. android:布局、绘制、内存泄露、响应速度、listview和bitmap、线程优化以及一些优化的建议!

    1.布局优化 首先删除布局中无用的控件和层级,其次有选择地使用性能较低的viewgroup,比如布局中既可以使用RelativeLayout和LinearLayout,那我们就采用LinearLayo ...

  6. js内存泄露的几种情况详细探讨

    内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使 ...

  7. 使用Xcode7的Instruments检测解决iOS内存泄露

    文/笨笨的糯糯(简书作者)原文链接:http://www.jianshu.com/p/0837331875f0著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 作为一名iOS开发攻城狮, ...

  8. 使用Visual Leak Detector for Visual C++ 捕捉内存泄露

    什么是内存泄漏? 内存泄漏(memory leak),指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段 ...

  9. java内存溢出和内存泄露

    虽然jvm可以通过GC自动回收无用的内存,但是代码不好的话仍然存在内存溢出的风险. 最近在网上搜集了一些资料,现整理如下: —————————————————————————————————————— ...

随机推荐

  1. C. Arcade dp二维费用背包 + 滚动数组 玄学

    http://codeforces.com/gym/101257/problem/C 询问从左上角走到右下角,每次只能向右或者向左,捡起三种物品算作一个logo,求最多能得到多少个logo. 设dp[ ...

  2. ABP教程(四)- 开始一个简单的任务管理系统 - 实现UI端的增删改查

    接上一篇:ABP教程(三)- 开始一个简单的任务管理系统 – 后端编码 1.实现UI端的增删改查 1.1添加增删改查代码 打开SimpleTaskSystem.sln解决方案,添加一个“包含视图的MV ...

  3. hadoop的安装和配置

    hadoop安装 在Apache Hadoop主页的下载页面https://hadoop.apache.org/releases.html选择版本进行下载: 下载下来的是压缩包: 将压缩包使用Xftp ...

  4. iOS 二维码的生成 QREncoder

    生成二维码: 在生成二维码的库中QREncoder最为常见,但是由于中文字符的特殊性,生成中文的时候有时会出现一定的错误,所以建议使用libqrencode,是一个纯C编写的类库. 以libqrenc ...

  5. Kotlin学习的一些心得

    1.工程manifest中的application节点有冲突时,添加 xmlns:tools="http://schemas.android.com/tools" tools:re ...

  6. PMP项目管理学习笔记(5)——整合管理之制定项目章程

    关于两个输入 在很多过程中,会用到这两个输入: 企业环境要素 是关于你的公司如何开展业务所需要知道的所有信息. 在你计划项目时,有很多关于公司的信息会非常有用,你需要知道各个不同部门是如何运作的,你所 ...

  7. 纯手写的css3正方体旋转效果

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. 洛谷 P1011 车站

    题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为a人.从第3站起( ...

  9. (转)Spring4.2.5+Hibernate4.3.11+Struts2.3.24整合开发

    http://blog.csdn.net/yerenyuan_pku/article/details/52902851 前面我们已经学会了Spring4.2.5+Hibernate4.3.11+Str ...

  10. HashSet LinkedHashSet TreeSet 分析

    1.HashSet分析 hashset 底层是hash表,就是hashMap,是无序的,唯一的.也就是说,它的底层其实就是一个HashMap  key 值的组成值.所以具有唯一性. public Ha ...