1.内存的分配策略概述

程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)堆区栈区

  1. 静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。
  2. 堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。
  3. 栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

可以引申出以下概念:

  1. 局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。因为它们属于方法中的变量,生命周期随方法而结束。
  2. 成员变量全部存储于堆中(包括基本数据类型,引用和引用的对象实体),因为它们属于类,类对象终究是要被new出来使用的。
  3. 我们所说的内存泄露,只针对堆内存,他们存放的就是引用指向的对象实体

2.产生内存泄漏的原因

在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间,也随之带来了内存泄漏的可能。

虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。GC过程与对象的引用类型是严重相关的

以下就引出java中的引用类型(虚引用使用少,这里不再介绍)

  1. 强引用:如果一个对象具有强引用,则垃圾回收器不会回收它,只有当进程结束的时候才会回收它
    例如:Persion persion = new Perison(“张三”,27); 就是典型的强引用
  2. 弱引用:如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
    例如:Persion persion = new Perison("张三", 27); WeakReference<Persion> weakReference = new WeakReference<>(persion); //将强引用对象置空后就只剩下弱引用 persion = null;
  3. 软引用:如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以一直被程序使用
    例如:Persion persion = new Perison("张三", 27); SoftReference<Persion> softReference = new SoftReference<>(persion); //将强引用对象置空后就只剩下软引用 persion = null;例如:假设我们在APP中加载大量的图片,为了节约用户流量和提高用户体验,我们需要一般采用三级缓存,这里需要大量的内存,很容易发生内存溢出(Out Of Memory 即OOM)的情况。这里可以考虑使用软引用或者弱引用来避免OOM。
    采用软引用还是弱引用可参考该对象的使用频率:
    如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

总结:内存泄漏的原因

堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的根本原因。

内存泄漏积累到一定程度就会引发OOM

那么如何避免OOM呢

减小内存占用:

  1. 使用更加轻量的数据结构:使用ArrayMap/SparseArray代替HashMap等传统数据结构
  2. 避免在Android里面使用枚举类型(Enum)
  3. 减小Bitmap对象的内存占用:解码格式选择RBG_565,选择合适的缩放比例(inSampleSize)

内存对象的重复利用

  1. 注意在ListView/GridView/RecyclerView等出现大量重复子组件的视图里面对ConvertView的复用
  2. Bitmap对象的复用:在ListView/GridView/RecyclerView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。
  3. 自定义View:避免在onDraw方法里面执行对象的创建
  4. 避免在循环体里面创建新对象
  5. StringBuilder和StringBuffer的使用:大量字符串拼接的时候String的“+”是很耗时的,其中StringBuilder线程不安全,StringBuffer线程安全

避免对象的内存泄露

  1. 考虑使用Application Context而不是Activity Context
  2. 注意临时Bitmap对象的及时回收
  3. 注意监听器的注销
  4. 注意WebView的泄漏
  5. 注意Cursor对象是否及时关闭