1.集合类泄漏

  集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。我们都喜欢通过 HashMap 做一些缓存之类的事,这种情况就要多留一些心眼。

2.static 成员

  如果static成员比它外部的类生命周期长,如果在static成员时引用了外部类,那么外部类不会以被释放。

2.1 static成员易造成内存泄漏

  如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。
  这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,以为如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。
  这里修复的方法是:
    不要在类初始时初始化静态成员。可以考虑lazy初始化。 架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。

2.2.单例造成的内存泄漏

  由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如下面一个典型的例子,

  1. 1 public class AppManager {
  2. 2 private static AppManager instance;
  3. 3 private Context context;
  4. 4 private AppManager(Context context) {
  5. 5 this.context = context;
  6. 6 }
  7. 7 public static AppManager getInstance(Context context) {
  8. 8 if (instance == null) {
  9. 9 instance = new AppManager(context);
  10. 10 }
  11. 11 return instance;
  12. 12 }
  13. 13 }

  这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

  • 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
  • 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

  正确的方式应该改为下面这种方式:

  1. 1 public class AppManager {
  2. 2 private static AppManager instance;
  3. 3 private Context context;
  4. 4 private AppManager(Context context) {
  5. 5 this.context = context.getApplicationContext();// 使用Application 的context
  6. 6 }
  7. 7 public static AppManager getInstance(Context context) {
  8. 8 if (instance == null) {
  9. 9 instance = new AppManager(context);
  10. 10 }
  11. 11 return instance;
  12. 12 }
  13. 13 }

  或者这样写,连 Context 都不用传进来了:
  在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,

  1. 1 ...
  2. 2
  3. 3 context = getApplicationContext();
  4. 4
  5. 5 ...
  6. 6 /**
  7. 7 * 获取全局的context
  8. 8 * @return 返回全局context对象
  9. 9 */
  10. 10 public static Context getContext(){
  11. 11 return context;
  12. 12 }
  13. 13
  14. 14 public class AppManager {
  15. 15 private static AppManager instance;
  16. 16 private Context context;
  17. 17 private AppManager() {
  18. 18 this.context = MyApplication.getContext();// 使用Application 的context
  19. 19 }
  20. 20 public static AppManager getInstance() {
  21. 21 if (instance == null) {
  22. 22 instance = new AppManager();
  23. 23 }
  24. 24 return instance;
  25. 25 }
  26. 26 }
  27. 27

2.3.非静态内部类创建静态实例造成的内存泄漏

  有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

  1. 1   public class MainActivity extends AppCompatActivity {
  2. 2 private static TestResource mResource = null;
  3. 3 @Override
  4. 4 protected void onCreate(Bundle savedInstanceState) {
  5. 5 super.onCreate(savedInstanceState);
  6. 6 setContentView(R.layout.activity_main);
  7. 7 if(mManager == null){
  8. 8 mManager = new TestResource();
  9. 9 }
  10. 10 //...
  11. 11 }
  12. 12 class TestResource {
  13. 13 //...
  14. 14 }
  15. 15 }

  这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
  将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用。

3.匿名内部类易产生内存泄漏

  看下面2个情况:

3.1 Runable/Thread造成的内存泄漏

  android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露

  1. 1   public class MainActivity extends Activity {
  2. 2 ...
  3. 3 Runnable ref1 = new MyRunable();
  4. 4 Runnable ref2 = new Runnable() {
  5. 5 @Override
  6. 6 public void run() {
  7. 7
  8. 8 }
  9. 9 };
  10. 10 ...
  11. 11 }

  ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存:
    可以看到,ref1没什么特别的。
    但ref2这个匿名类的实现对象里面多了一个引用:

  
  this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露。

3.2.Handler 造成的内存泄漏

  Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
  由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。举个例子:

  1. 1   public class SampleActivity extends Activity {
  2. 2
  3. 3 private final Handler mLeakyHandler = new Handler() {
  4. 4 @Override
  5. 5 public void handleMessage(Message msg) {
  6. 6 // ...
  7. 7 }
  8. 8 }
  9. 9
  10. 10 @Override
  11. 11 protected void onCreate(Bundle savedInstanceState) {
  12. 12 super.onCreate(savedInstanceState);
  13. 13
  14. 14 // Post a message and delay its execution for 10 minutes.
  15. 15 mLeakyHandler.postDelayed(new Runnable() {
  16. 16 @Override
  17. 17 public void run() { /* ... */ }
  18. 18 }, 1000 * 60 * 10);
  19. 19
  20. 20 // Go back to the previous Activity.
  21. 21 finish();
  22. 22 }
  23. 23 }

  在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。
  修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:

  1. 1   public class SampleActivity extends Activity {
  2. 2
  3. 3 /**
  4. 4 * Instances of static inner classes do not hold an implicit
  5. 5 * reference to their outer class.
  6. 6 */
  7. 7 private static class MyHandler extends Handler {
  8. 8 private final WeakReference<SampleActivity> mActivity;
  9. 9
  10. 10 public MyHandler(SampleActivity activity) {
  11. 11 mActivity = new WeakReference<SampleActivity>(activity);
  12. 12 }
  13. 13
  14. 14 @Override
  15. 15 public void handleMessage(Message msg) {
  16. 16 SampleActivity activity = mActivity.get();
  17. 17 if (activity != null) {
  18. 18 // ...
  19. 19 }
  20. 20 }
  21. 21 }
  22. 22
  23. 23 private final MyHandler mHandler = new MyHandler(this);
  24. 24
  25. 25 /**
  26. 26 * Instances of anonymous classes do not hold an implicit
  27. 27 * reference to their outer class when they are "static".
  28. 28 */
  29. 29 private static final Runnable sRunnable = new Runnable() {
  30. 30 @Override
  31. 31 public void run() { /* ... */ }
  32. 32 };
  33. 33
  34. 34 @Override
  35. 35 protected void onCreate(Bundle savedInstanceState) {
  36. 36 super.onCreate(savedInstanceState);
  37. 37
  38. 38 // Post a message and delay its execution for 10 minutes.
  39. 39 mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
  40. 40
  41. 41 // Go back to the previous Activity.
  42. 42 finish();
  43. 43 }
  44. 44 }

  综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。

4.避免 override finalize()

  • finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 虚拟机调用GC的时间不确定 Finalize daemon线程被调度到的时间不确定
  • finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是:
    含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。
  • 含有Finalize方法的object需要至少经过两轮GC才有可能被释放。

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

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

6.一些不良代码造成的内存压力

  有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
  比如: Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。 构造 Adapter 时,没有使用缓存的 convertView ,每次都在创建新的 converView。这里推荐使用 ViewHolder。

7.总结

  • 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就不考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
  • 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
  • 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
  1.   将内部类改为静态内部类
  2.   静态内部类中使用弱引用来引用外部类的成员变量
  • Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
  • 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
  • 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
  • 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

Android内存管理(13)常见产生内存泄漏的原因的更多相关文章

  1. 5个Android开发中比较常见的内存泄漏问题及解决办法

    android中一个对象已经不需要了,但是其他对象还持有他的引用,导致他不能回收,导致这个对象暂存在内存中,这样内存泄漏就出现了.   内存泄漏出现多了,会是应用占用过多的没存,当占用的内存超过了系统 ...

  2. Objective-C 【多个对象内存管理(野指针&内存泄漏)】

    ------------------------------------------- 多个对象内存管理(野指针&内存泄漏) (注:这一部分知识请结合"单个对象内存管理"去 ...

  3. 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)

    1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...

  4. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  5. Go语言内存管理(一)内存分配

    Go语言内存管理(一)内存分配 golang作为一种"高级语言",也提供了自己的内存管理机制.这样一方面可以简化编码的流程,降低因内存使用导致出现问题的频率(C语言使用者尤其是初学 ...

  6. 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  7. JVM内存管理:深入Java内存区域与OOM

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝 ...

  8. 常用Actoin算子 与 内存管理 、共享变量、内存机制

    一.常用Actoin算子 (reduce .collect .count .take .saveAsTextFile . countByKey .foreach ) collect:从集群中将所有的计 ...

  9. 待解决问题:c++栈对象的析构、虚拟内存与内存管理的关系、内存管理的解决方案。

    待解决问题:c++栈对象的析构.虚拟内存与内存管理的关系.内存管理的解决方案.

  10. 七.OC基础加强--1.内存管理 2.野指针,内存泄露 3.set方法的内存管理 4.@property参数 5.@class和循环retain的使用 6.NSString的内存管理

    1,内存管理简单介绍 1,为什么要有内存管理? malloc selloc dealloc```需要回头复习 一般的内存 4s 是512m内存:6 是1024m内存: 当内存过大时,会耗尽内存.出现程 ...

随机推荐

  1. springcloud(十三):Ribbon客户端负载均衡实例

    一.采用默认的负载均衡策略:RoundRobinRule 轮询策略 1.修改提供者原的控制类 在之前的eureka-client-provider项目的CenterController.java中加入 ...

  2. Vue如何tab切换高亮最简易方法

    以往我们实现tab切换高亮通常是循环遍历先把所有的字体颜色改变为默认样式,再点亮当前点击的选项,而我们在vue框架中实现tab切换高亮显示并不需要如此,只需要将当前点击选项的index传入给一个变量, ...

  3. IDEA下tomcat中web项目乱码,控制台乱码解决指南

    若是由于过滤器,request ,response等原因,不适用. 原文作者:http://www.kafeitu.me/tools/2013/03/26/intellij-deal-chinese- ...

  4. RSA的共模攻击

    实验吧题目:http://www.shiyanbar.com/ctf/1834 参考:http://hebin.me/2017/09/07/%e8%a5%bf%e6%99%aectf-strength ...

  5. Eclipse不编译解决方案

    原文链接:http://blog.csdn.net/huahuagongzi99999/article/details/7719882    转来自己用 这两天Eclipse 不编译了,无论怎么更改保 ...

  6. hdu 2546 0-1背包

    #include<stdio.h> #include<string.h> #define N 1100 int dp[N],a[N]; int main() { int n,m ...

  7. 5-14 电话聊天狂人 (25分) HASH

    给定大量手机用户通话记录,找出其中通话次数最多的聊天狂人. 输入格式: 输入首先给出正整数NN(\le 10^5≤10​5​​),为通话记录条数.随后NN行,每行给出一条通话记录.简单起见,这里只列出 ...

  8. Codeforces 300E(数学)

    题意:给定k个数字,求最小的正整数n,使得“n的阶乘”是“这k个数字的阶乘的积”的倍数.1<=k<=1e6,数字ai满足1<=ai<=1e7 分析:如果我们能对着k个数字的阶乘 ...

  9. NetCore实现全局异常捕捉统一处理

    做net项目时候,在Global.asax文件中可以通过Application_Error方法全局捕获异常并处理后统一跳转到自定义的错误页面. 下面是我个人在NetCore项目中实现全局捕获异常并统一 ...

  10. Codeforces 196 D. The Next Good String

    D. The Next Good String time limit per test 2 seconds memory limit per test 256 megabytes input stan ...