相关文章
Android性能优化系列
Java虚拟机系列

前言

内存泄漏向来都是内存优化的重点,它如同幽灵一般存于我们的应用当中,有时它不会现身,但一旦现身就会让你头疼不已。因此,如何避免、发现和解决内存泄漏就变得尤为重要。这一篇我们先来学习如何避免内存泄漏。

1.什么是内存泄漏

我们知道,每个应用程序都需要内存来完成工作,为了确保Android系统的每个应用都有足够的内存,Android系统需要有效地管理内存分配。当内存不足时,Android运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法,
在Java虚拟机(三)垃圾标记算法与Java对象的生命周期这篇文章中讲到了根搜索算法,如下图所示。

未命名文件.png

从上图看以看出,Obj4是可达的对象,表示它正被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然互相引用,但是因为他们到GC Roots是不可达的所以它们仍旧会标记为可回收的对象。

内存泄漏就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。此时,如果Obj4是一个没有用的对象,但它仍与GC Roots是可达的,那么Obj4就会内存泄漏。
内存泄漏产生的原因,主要分为三大类:
1.由开发人员自己编码造成的泄漏。
2.第三方框架造成的泄漏。
3.由Android 系统或者第三方ROM造成的泄漏。
其中第二种和第三种有时是不可控的,但是第一种是可控的,既然是可控的,我们就要尽量在编码时避免造成内存泄漏,下面就来列举出常见的内存泄漏的场景。

2.内存泄漏的场景

2.1 非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。

public class SecondActivity extends AppCompatActivity {private static Object inner;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button) findViewById(R.id.bt_next);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {createInnerClass();finish();}});}void createInnerClass() {class InnerClass {}inner = new InnerClass();//1}
}复制代码

当点击Button时,会在注释1处创建了非静态内部类InnerClass的静态实例inner,该实例的生命周期会和应用程序一样长,并且会一直持有SecondActivity 的引用,导致SecondActivity无法被回收。

2.2 匿名内部类的静态实例

和前面的非静态内部类一样,匿名内部类也会持有外部类实例的引用。

public class AsyncTaskActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_async_task);button = (Button) findViewById(R.id.bt_next);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startAsyncTask();finish();}});}void startAsyncTask() {new AsyncTask<Void, Void, Void>() {//1@Overrideprotected Void doInBackground(Void... params) {while (true) ;}}.execute();}
}复制代码

在注释1处实例化了一个AsyncTask,当AsyncTask的异步任务在后台执行耗时任务期间,AsyncTaskActivity 被销毁了,被AsyncTask持有的AsyncTaskActivity实例不会被垃圾收集器回收,直到异步任务结束。
解决办法就是自定义一个静态的AsyncTask,如下所示。

public class AsyncTaskActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_async_task);button = (Button) findViewById(R.id.bt_next);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startAsyncTask();finish();}});}void startAsyncTask() {new MyAsyncTask().execute();}private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {@Overrideprotected Void doInBackground(Void... params) {while (true) ;}}
}复制代码

与AsyncTask类似的还有TimerTask,这里就不再举例。

2.3 Handler内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。

public class HandlerActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);button = (Button) findViewById(R.id.bt_next);final Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}};button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mHandler.sendMessageDelayed(Message.obtain(), 60000);finish();}});}
}复制代码

Handler 是非静态的匿名内部类的实例,它会隐性引用外部类HandlerActivity 。上面的例子就是当我们点击Button时,HandlerActivity 会finish,但是Handler中的消息还没有被处理,因此HandlerActivity 无法被回收。
解决方法就是要使用一个静态的Handler内部类,Handler持有的对象要使用弱引用,并且在Activity的Destroy方法中移除MessageQueue中的消息,如下所示。

public class HandlerActivity extends AppCompatActivity {private Button button;private MyHandler myHandler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);button = (Button) findViewById(R.id.bt_next);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {myHandler.sendMessageDelayed(Message.obtain(), 60000);finish();}});}public void show() {}private static class MyHandler extends Handler {private final WeakReference<HandlerActivity> mActivity;public MyHandler(HandlerActivity activity) {mActivity = new WeakReference<HandlerActivity2>(activity);}@Overridepublic void handleMessage(Message msg) {if (mActivity != null && mActivity.get() == null) {mActivity.get().show();}}}@Overridepublic void onDestroy() {if (myHandler != null) {myHandler.removeCallbacksAndMessages(null);}super.onDestroy();}
}复制代码

MyHandler是一个静态的内部类,它持有的 HandlerActivity对象使用了弱引用,并且在onDestroy方法中将Callbacks和Messages全部清除掉。
如果觉得麻烦,也可以使用避免内存泄漏的Handler开源库WeakHandler。

2.4 未正确使用Context

对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,比如如下的单例模式:

public class AppSettings { private Context mAppContext;private static AppSettings mAppSettings = new AppSettings();public static AppSettings getInstance() {return mAppSettings;}public final void setup(Context context) {mAppContext = context;}
}复制代码

mAppSettings作为静态对象,其生命周期会长于Activity。当进行屏幕旋转时,默认情况下,系统会销毁当前Activity,因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄露。
解决方法就是使用Application的Context:

public final void setup(Context context) {mAppContext = context.getApplicationContext();
}复制代码

2.5 静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。

public class SecondActivity extends AppCompatActivity {private static Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button) findViewById(R.id.bt_next);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});}}复制代码

2.6 WebView

不同的Android版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在着很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

2.7 资源对象未关闭

资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

2.8 集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

2.9 Bitmap对象

临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。

2.10 监听器未关闭

很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。

参考资料
Eight Ways Your Android App Can Leak Memory
Memory Leak Patterns in Android
Handler导致内存泄露分析
Android App 内存泄露之Handler
[译]Android内存泄漏的八种可能(上)
[译]Android防止内存泄漏的八种方法(下)
Android 应用内存泄漏的定位、分析与解决策略
《Android应用性能优化最佳实践》


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Android相关原创技术干货。
扫一扫下方二维码或者长按识别二维码,即可关注。

Android内存优化(三)避免可控的内存泄漏相关推荐

  1. Android性能优化(三):响应优化

    Android性能优化(三):响应优化 性能优化系列文章: Android性能优化(一):APP启动优化 Android性能优化(二):UI布局优化 Android性能优化(三):响应优化 Andro ...

  2. 【Android 内存优化】垃圾回收算法 ( 内存优化总结 | 常见的内存泄漏场景 | GC 算法 | 标记清除算法 | 复制算法 | 标记压缩算法 )

    文章目录 一. 内存优化总结 二. 常见的内存泄漏场景 三. 内存回收算法 四. 标记-清除算法 ( mark-sweep ) 五. 复制算法 六. 标记-压缩算法 一. 内存优化总结 内存泄漏原理 ...

  3. Android 内存优化实操,定位内存问题

    文章目录 一.内存泄漏定位 1.观察法: 2.使用内存分析工具 2-1.收集内存快照 2-2.hprof文件转换 2-3.Mat分析内存 二.内存抖动 三.优化内存空间 1.减少不必要的内存开销 2. ...

  4. Google官方 详解 Android 性能优化【史诗巨著之内存篇】

    尊重博主原创,如需转载,请附上本文链接http://blog.csdn.net/chivalrousman/article/details/51553114#t16 为什么关注性能 对于一款APP,用 ...

  5. Android系统性能优化(69)---含内存优化、布局优化

    Android性能优化:含内存优化.布局优化 前言 在 Android开发中,性能优化策略十分重要 因为其决定了应用程序的开发质量:可用性.流畅性.稳定性等,是提高用户留存率的关键 本文全面讲解性能优 ...

  6. 如何查看spark消耗的内存_Spark优化(三)----数据本地化及内存调优

    1. 数据本地化的级别: 1.PROCESS_LOCAL 2.NODE_LOCAL 3.NO_PREF 4.RACK_LOCAL 5.ANY 1) PROCESS_LOCAL task要计算的数据在本 ...

  7. java内存优化详解_jvm堆内存优化详解

    在日常的运维工作中用到tomcat,都需要对tomcat中的jvm虚拟机进行优化,只有知道需要优化参数的具体用处,才能深刻体会优化jvm的意义所在. 在平常的工作中我们谈对jvm的优化,主要是针对ja ...

  8. 内存优化-使用tcmalloc分析解决内存泄漏和内存暴涨问题

    其实我一直很想写关于tcmalloc的内存泄漏检测的文章,只是一直记不起来该如何下笔,有时项目太忙,在整理这方便的思考过少,另外遇到的问题也不是很多,直到最近用tcmalloc帮A项目排查一些很棘手的 ...

  9. 安全管家安卓_内存优化管家v1.0下载-内存优化管家app最新版下载

    内存优化管家是一款安卓手机多功能清理工具,包含了文件垃圾.缓存垃圾.广告垃圾和内存垃圾等分类清理功能,使用方法简单,一键扫描手机,即可进行不同类型的垃圾划分, 用户可以根据需求进行筛选清理,除此之外, ...

  10. redis持久化、内存优化、过期、LRU内存

    为什么80%的码农都做不了架构师?>>>    1.过期(expire命令) 设置了失效时间的元素,对于DEL/SET/GETSET/*STORE这些会删除或者重新设置元素的命令,如 ...

最新文章

  1. 一、multiprocessing.pool.RemoteTraceback
  2. 轻松理解汉诺塔问题(图解java描述)
  3. [转]XPS转JPG转换器
  4. C++ string线程不安全
  5. SAP 凭证冲销可以使用BAPI_ACC_DOCUMENT_REV_POST或者BDC录制FB08来做
  6. 20165334 《java程序设计》第2周学习总结
  7. 启明云端带你一起撸ESP32开发板,玩转语音、彩屏
  8. 《C++必知必会》读书笔记2
  9. Mac OS X 10.10如何打开虚拟内存
  10. 系统在iis6上部署
  11. 结对编程:黄金分割游戏
  12. Sun公司开源游戏服务器Project Darkstar Server——(Sun game server , 简称 sgs)学习笔记(二):多人游戏...
  13. 模二多项式环 及 BCH码 的纯python实现和一些问题
  14. 医疗行业的信息化建设
  15. fastposter v2.6.2 发布 程序员专属海报生成器
  16. 智能云时代,谁来守护我们的安全?
  17. 豆瓣的python库安装源
  18. 单片机c语言什么是ea,单片机ie是什么?怎么用?
  19. 计算机主板没电默认时间,电脑时间不能自动更新了怎么办?每次开机都要重新设置时间...
  20. python对文件进行tar和gz格式的压缩和解压缩(亲测,可用)

热门文章

  1. jQuery学习总结之基础知识----持续更新中
  2. FAQ about AJAX-part II
  3. 通过python获取苹果手机备份文件中的照片,视频等信息采集
  4. SSL连接并非完全问题解决
  5. Swift进阶学习笔记
  6. 23种设计模式(一)单例模式
  7. docker 标记和推送镜像
  8. vs2015 企业版、专业版如何破解(秘钥)
  9. Android设计模式-观察者模式
  10. linux 服务器安装字体