Android 内存泄露

前言

对于 c++ 来说,内存泄漏就是new出来的对象没有 delete,俗称野指针;而对于 java 来说,就是 new 出来的 object 放在 Heap 上无法被GC回收

对象的引用方式

  • 强引用(StrongReference):new 的对象没释放,JVM 哪怕发生 OOM 错误也不会回收该对象
  • 软引用(SoftReference):GC时,当内存不够使用时才回收
  • 弱引用(WeakReference):GC时,不管内存是否够用都会回收
  • 虚引用(PhantomReference):和没有任何引用一样,随时都可能被回收

Java 内存

Java是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)

  • 栈:栈中只存放基本类型和对象的引用(不是对象),LIFO
  • 堆:堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
  • 方法区:又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量

垃圾回收机制

垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达。垃圾回收用于释放不可到达的对象所占据的内存。
实现思想:我们将栈定义为root,遍历栈中所有的对象的引用,再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。

Android 内存泄露_第1张图片
垃圾回收机制

内存泄露 VS 内存溢出

内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用
内存泄露 (memory leak):是指程序在申请内存后,无法释放已申请的内存空间

内存泄露原因

在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中

常见的内存泄露

  • Bitmap使用
    图片Size。美工给的切图直接使用,很容易造成内存泄漏。所以我们就必须从源头上减少内存的开销。特别是要展示一些缩略图或者对图片质量要求不怎么高的时候,就更应该进行设置。我们可以使用BItmapFactory.Options设置inSampleSize。该参数可以设置显示图的宽高分别为原始图片大小的几分之一

      BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
      bitmapFactoryOptions.inJustDecodeBounds = true;
      bitmapFactoryOptions.inSampleSize = 3;
      options.inJustDecodeBounds = false;
      Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap,bitmapFactoryOptions); 
    

    图片像素。除了图片的大小外,图片的像素也对内存的消耗有影响,所以如果需求对图片的质量要求不是特别高就可以改下默认值,从而减少开销

    • ALPHA_8:每个像素占用1byte内存
    • ARGB_4444:每个像素占用2byte内存
    • ARGB_8888:每个像素占用4byte内存(默认)
    • RGB_565:每个像素占用2byte内存

    图片回收。在图片使用完之后,我们需要对图片进行回收,而不是等系统进行回收

      if (null != bitmap && !bitmap.isRecycled()) {
          bitmap.recyle();
          bitmap = null;
          System.gc(); // 只是建议进行垃圾回收,实际会不会回收无法预知
      }
    
  • 关闭对象

    • Cursor回收:Activity的onDestory()中对cursor进行关闭
    • 注销BroadcaseReceiver:广播一定要unregisterReceiver
    • 关闭I / O:用完close()
  • Activity

    • Context:尽量使用ApplicationContext而不是Activity,因为引用的实例的生命周期很有可能超出Activity的生命周期,从而造成Activity无法回收,导致内存泄漏
    • Listener:注册Listener时若传入Activity的引用,onDestroy时记得removeListener
    • Handler:Handler和Activity的生命周期是不一样的,所以在Activity销毁时mHandler.removeCallbacks(mRunnable);
    • 总结:凡是使用Activity对象(包括this指针)的,当Activity 销毁时记得解绑、销毁。
  • 其它

  • Activity中尽量不要定义静态常量

  • 切记不要在循环中频繁 new 对象

工具

  • eclipse 中引入插件:MAT
  • Android Studio:Android Monitor
  • 项目集成 : LeakCanary

你可能感兴趣的:(Android 内存泄露)