最常见的Android内存优化方式及防止泄漏造成OOM总结篇( 二 )


对于程序员来说,GC基本是透明的,不可见的 。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行 。因为,不同的JVM实现者可能使用不同的算法管理GC 。通常,GC的线程的优先级别较低 。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC 。但通常来说,我们不需要关心这些 。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性 。
Java 内存泄漏的典型例子:
Vector v = new Vector(10);for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的 。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null 。
常见内存泄漏(本篇重点)
1、集合类泄漏
如果一个集合类是全局性变量(比如类中的静态变量或全局性map即有静态引用又或者final指向它)只有添加元素的方法,而没有相应的清除机制,就会占用内存只增不减,造成内存泄漏 。比如我们通常用HashMap做一些缓存之类的事,这种情况就多留点心,做好相应删除机制 。
2、单例造成泄漏
由于单例的静态性使得生命周期跟应用的生命周期一样长,很容易造成内存泄漏 。
典型的例子
public class AppManager { private static AppManager instance; private Context context;private AppManager(Context context) { this.context = context; }public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1:如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题 。
2:如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了
3、非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:
public class MainActivity extends AppCompatActivity {private static TestResource mResource = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if(mManager == null){mManager = new TestResource();}}class TestResource {//…} }这样就在Activity内部创建了一个非静态内部类的单例TestResource,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收 。
正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的Context 。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下:Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列 。而对于 Dialog 而言,只有在 Activity 中才能创建 。
4、匿名内部类线程异步导致泄漏
在继承实现Activity/Fragment/View时,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,没有任何措施一定会导致泄露 。
举个栗子:
public class MainActivity extends Activity {...{ Runnable re1 = new MyRunable();Runnable re2 = new Runnable() {@Overridepublic void run() {}}; }


推荐阅读