在上一篇文章中我们对 Android 中内存有了一个基本的了解, 在本文继续介绍有关内存溢出的相关点. 当内存泄漏超过一定的界限, 必然会引起内存溢出, 有些内存泄漏在开发中是比较常见的, 接下来通过介绍几种常见额内存泄漏情形, 以便在开发过程中采取必要的措施以此防止内存泄漏.
如下是 Android 开发者在开发中比较常见的几种内存泄漏, 并给出了相对应的防止内存泄漏的解决方式.
单例模式引起的内存泄漏
单例模式可以说在 Android 开发过程中使用最多的一种设计模式, 所以由该模式导致的内存泄漏也是比较常见的.
在这里介绍两种由单例模式导致的内存泄漏, 其实引起内存泄漏的原因都是一样的.
在构造单例模式的 getInstance()方法中传入了一个 Context 对象, 但是 Context 是某个 Activity 调用者自己.
在单例模式中的成员变量是一个监听器 Listener 或者说是一个回调 Callback, 但是该监听器监听器 Listener 或者回调 Callback 的实现类是一个 Activity 或者 Fragment.
- public class AppManager {
- private static volatile AppManager instance;
- private Context context;
- private Callback callback;
- private AppManager(Context context) {
- this.context = context;
- }
- public static AppManager getInstance(Context context) {
- if (instance == null) {
- synchronized (AppManager.class) {
- if (instance == null) {
- instance = new AppManager(context);
- }
- }
- }
- return instance;
- }
- public void setCallback(Callback callback) {
- this.callback = callback;
- }
- ...
- }
上面就是一个典型的会造成内存泄漏的单利模式示例, Context 和 Callback 都是作为单利模式的一个成员变量传入的. 如果仅仅是作为 AppManager 中某个方法的入参, 这种情形是不会导致内存泄漏的, 因为方法被调用后, 方法中局部变量会被自动释放. 然而作为成员变量或者属性则不同了, 当调用 getInstance()方法的时候如果传入一个 Activity 或者 Fragment, 那么 AppManager 就会持有 Activity, 其实传入 Context 与某个 Activity 或者 Fragment 实现了 AppManager 的 Callback 一样. 由于单例模式的生命周期跟整个应用的生命周期一样长, 所以上面介绍的两种情形都会导致 AppManager 持有 Activity 或者 Fragment 引用, 当 JVM 进行垃圾回收的时候, 导致 Activity 或者 Fragment 无法被 GC 回收, 从而导致内存泄漏.
针对第一种情况, 有些开发者可能会说只要 Context 传入的是 getApplicationContext()就可以因 Activity 导致的内存泄漏, 但是不是所有的情况下都可以将 Context 与 getApplicationContext()相互替换的.
数字 1: 启动 Activity 在这些类中是可以的, 但是需要创建一个新的 task. 一般情况不推荐.
数字 2: 在这些类中去 layout inflate 是合法的, 但是会使用系统默认的主题样式, 如果你自定义了某些样式可能不会被使用.
数字 3: 在 receiver 为 null 时允许, 在 4.2 或以上的版本中, 用于获取黏性广播的当前值.(可以无视)
第二种方式中, 除非我们的单例模式就是给应用的 Application 使用的, 否则只要某个 Activity 或者 Fragment 实现了该 Callback, 都会导致该 Activity 无法被 GC 回收释放掉导致内存泄漏, 最后导致 OOM.
通过上面介绍, 我们知道如果作为单例模式 AppManager 某个方法的入参, 当方法使用之后, 局部变量会自动释放, 这种自动释放机制是 Java 语言本身就具有的. 但是成员变量被释放的前提是 AppManager 实例对象被垃圾回收器回收后才释放, 由于 AppManager 的实例对象是静态变量, 所以必须在应用退出之后才会被释放, Context 和 Callback 作为 AppManager 的成员变量, 是我们在使用过程中自己设置的, 如果想要在应用没有退出之前就让系统自动释放是不可能的, 所以需要我们在使用后自己手动释放. 在 Java 中将对象释放很简单, 只需要赋值为 null 即可, 这样一旦垃圾回收器执行 GC 时机会自动回收, 所以为了在单例模式中不引起内存泄漏, 只需要再添加一个 clear()方法, 在 clear()方法中将 Context 或者 Callback 手动赋值为 null, 然后在 Activity 或者 Fragment 的 onDestory()方法中调用一下 AppManager.getInstance().clear().
Handler 引起的内存泄漏
在开发过程中 Handler 是最常用的用于处理子线程和主线程数据交互的工具. 由于 Android 采用单线程 UI 模型, 开发者无法在子线程处理 UI, 再者在 Android4.0 之后, 网络请求不能放在主线程, 只能在子线程请求, 在子线程请求到的数据必须返回到主线程才可以用于更新 UI, 为此我们必须使用 Handler 工具将数据切换到主线程更新 UI.
很多情况下开发者是直接采用下面的方式使用 Handler.
- private Handler handler=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- //TODO
- }
- };
这种直接采用内部类的方式处理 Handler 中的 Message 是最直接也是最简单的做法, 但是这样会导致一个问题, 非静态内部类会持有外部类的引用, 更多内部类引起的内存泄漏在下面会继续讨论, 如果 Handler 中发送了一个延时消息, 这样会导致 Activity 无法被释放掉进而引起内存泄漏.
网上有许多讨论 Handler 引起内存泄漏的示例, 被大家普遍接受的就是使用如下方式:
- private static class UIHandler extends Handler {
- private final WeakReference<Activity> weakReference;
- public UIHandler(Activity activity) {
- weakReference = new WeakReference<>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- if (weakReference.get() != null) {
- //TODO
- }
- }
- }
上述实现方式首先使用了一个静态内部类继承 Handler, 然后定义一个弱引用 WeakReference, 在构造方法中将 Activity 的 Context 存入弱引用中, 这样在 JVM 执行 GC 的时候就会直接回收掉弱引用中持有的 Activity. 在 handleMessage()方法中, 我们只需要对 Activity 进行判空处理一下即可. 如果想了解更多有关 Handler 导致内存泄漏, 可以参看这篇文章 Handler 造成 Activity 泄漏, 用弱引用真的有用么? https://blog.csdn.net/u013356254/article/details/52463636
有些开发者可能有些疑问了, Handler 作为 Android 系统刚开始时就使用的工具类, 而且很多系统自带的类中都有 Handler 的足迹, Google 不可能留给开发者这么一个大坑, 每次使用 Handler 的时候还必须时刻想 <着如果使用最简单直接的内部类方式就会导致内存泄漏 其实之所以 handler 会引起内存泄漏 =""就是因为 jvm 在进行回收 activity 的时候 ="" 会判断该 activity 还有没有在被使用 =""而这时候如果 handler 中还有 message 未处理完成 ="" 就会导致该 activity 不能及时被释放 =""那么如果在 activity 或者 fragment 生命周期的 ondestroy="" 方法中可以将 handler 没有执行完的消息清空 =""这样 activity 就不会被引用了 ="" 垃圾回收器就可以在执行 gc 的时候回收 activity 了 =""当然也就不会再发生内存泄漏的事情了 ="">
- @Override
- protected void onDestroy() {
- super.onDestroy();
- handler.removeCallbacksAndMessages(null);
- }
非静态内部类引起内存泄漏
非静态内部类会引起内存泄漏, 当然了并不是说所有的都会引起内存泄漏, 这里只是指在非静态内部类中处理比较耗时的操作容易导致内存泄漏, 更多的可能涉及到异步操作. 下面几个是比较常使用的用于处理异步任务的类, Handler 上面已经介绍了就不再罗列出来了.
- new Thread(){
- @Override
- public void run() {
- //TODO
- }
- }.start();
- new Timer().schedule(new TimerTask() {
- @Override
- public void run() {
- //TODO
- }
- },1000);
- private class MyTask extends AsyncTask<String,Void,String>{
- @Override
- protected String doInBackground(String... strings) {
- return null;
- }
- @Override
- protected void onPostExecute(String s) {
- super.onPostExecute(s);
- }
- }
非静态内部类之所以容易引起内存泄漏是因为它持会隐式持有外部类的引用, 更多内容可以参看 Java"失效" 的 private 修饰符 http://www.sunnyang.com/711.html . 当我们在 Activity 或者 Fragment 中使用了上面几个比较耗时的类处理一些业务逻辑时, 一旦跳过了当前页面, 有可能异步任务还在执行中, 这样就会导致 Activity 或者 Fragment 无法被垃圾回收器及时回收从而导致内存泄漏.
多线程并发在 Android 开发中是一定会涉及的, 由于频繁的创建和销毁线程会大大的降低系统效率, 再者就是线程本身也会占用内存, 所以不建议直接使用 Thread 来频繁新建线程, 而是使用线程池, 线程池适当地调整池中工作线线程的数目, 防止消耗过多的内存, 也可以防止频繁的创建和销毁线程, 从而提高系统运行效率. 还有一点就是使用线程池可以方便的停止正在运行的线程, 当界面用户已经看不到的时候可以直接调用线程池停止方法及时关闭异步任务.
Timer 和 AsyncTask 使用方式类似, 这两个类都有提供用于取消任务的 cancel()方法, 当不再使用的时候我们可以直接调用 cancel()方法. 如果说在界面不可见的时候还想在后台继续执行任务, 那么这时候就建议直接新建一个单独的类或者使用静态内部类, 这样就可以不必隐式持有 Activity 或者 Fragment, 当然了具体的情况还是要根据需求确定.
webView 引起的内存泄漏
现在安卓 APP 大多都用到了 WebView+H5 混合开发, 在 Android 中的 WebView 存在很大的兼容性问题, 不仅仅是 Android 系统版本的不同对 WebView 产生很大的差异, 另外不同的厂商出货的 ROM 里面 WebView 也存在着很大的差异. 即使 WebView 不加载任何内容, 它本身也会占用很大的内存, 在此基础上启动多个 WebView 占用的内存不会有太多变化, WebView 的渲染类似一个单例模式 View, 但又不同于原生 View, 因为 WebView 在页面销毁之后内存仍然不会有明确减少. 如下是两张截图, 第一张是在 Activity 之上启动一个使用 WebView 的 Activity, 可以看出内存有明显的涨幅, 第二张是关闭已经开启的 WebView 的 Activity, 内存虽有部分减少, 但是跟没有加载 WebView 时相比微乎其微.
在网上也有许多讨论如何减少 WebView 导致的内存泄漏, 有些建议不要在 xml 中设置 WebView, 而应该使用 Java 代码动态构建一个 WebView, 在新建 WebView 的时候传入 getApplicationContext(), 然后通过一个布局文件 addView()将 WebView 添加进视图中.
- layout = findViewById(R.id.layout);
- webView = new WebView(getApplicationContext());
- layout.addView(webView);
但是这种方式也不是很完美解决 WebView 内存泄漏的问题, 如下是网上的有部分开发者遇到的问题: 如果在 WebView 中打开链接或者你打开的页面带有 flash, 获得你的 WebView 想弹出一个 dialog, 都会导致从 ApplicationContext 到 ActivityContext 的强制类型转换错误, 从而导致应用崩溃. 这是因为在加载 flash 的时候, 系统会首先把 WebView 作为父控件, 然后在该控件上绘制 flash, 它想找一个 Activity 的 Context 来绘制他, 但是你传入的是 ApplicationContext. 这种类型的问题我在开发过程中还没有遇到过, 这应该也是确实存在的问题之一.
上面是介绍的是在构建的时候可以采取的优化错误, 接下来在页面销毁的时候, 可以在 onDestroy()方法调用部分方法清空 WebView 的内容.
- @Override
- protected void onDestroy() {
- super.onDestroy();
- layout.removeView(webView);
- webView.removeAllViews();
- webView.destroy();
- webView = null;
- }
实际上上面的两幅截图对比, 本身就已经采用的上面的优化方式防止 WebView 内存泄漏, 可是通过截图也可以发现, 采取的措施几乎没有起到作用. 由于标准的 WebView 就存在内存泄露的问题, Google 上面有专门讨论这个问题, 更多内容可以参看 WebView causes memory leak - leaks the parent Activity https://code.google.com/p/android/issues/detail?id=5067 . 所以通常根治这个问题的办法是为 WebView 开启另外一个进程, 通过 AIDL 或者 Messenger 与主进程进行通信, WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁, 从而达到内存的完整释放.
现在常用的 QQ 和微信就是采用的开启一个独立进程来处理 WebView 中的相关业务逻辑的.
图片引起的内存泄漏
在 Android 移动端开发中图片想来都是一个吃内存大户, Google 在 Android 系统的每一次升级中也都试图减少由 Bitmap 导致的内存溢出. 如下是 Google 官方文档的说明:
On Android 2.3.3 (API level 10) and lower, the backing pixel data for a Bitmap is stored in native memory. It is separate from the Bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. From Android 3.0 (API level 11) through Android 7.1 (API level 25), the pixel data is stored on the Dalvik heap along with the associated Bitmap. In Android 8.0 (API level 26), and higher, the Bitmap pixel data is stored in the native heap.
在 Android2.3.3(API 10)及之前的版本中, Bitmap 对象与其像素数据是分开存储的, Bitmap 对象存储在 Dalvik heap 中, 而 Bitmap 对象的像素数据则存储在 Native Memory(本地内存)中, 并且生命周期不太可控, 可能需要用户自己调用 recycle()方法回收, 在回收之前必须清楚的确定 Bitmap 已不再使用了, 如果调用了 Bitmap 对象 recycle()之后再将 Bitmap 绘制出来, 就会出现 "Canvas: trying to use a recycled bitmap" 错误. 3.0-7.1 之间, Bitmap 的像素数据和 Bitmap 对象一起存储在 Dalvik heap 中, 所以我们不用手动调用 recycle()来释放 Bitmap 对象, 内存的释放都交给垃圾回收器来做, 更多可以参看 Google 官方给的一个图片处理 Demo https://github.com/googlesamples/android-DisplayingBitmaps .
在 8.0 之后的像素内存又重新回到 native 上去分配, 不需要用户主动回收, 8.0 之后图像资源的管理更加优秀, 极大降低了 OOM. 在 Android2.3.3 以及以前的版本中 Bitmap 的像素信息也是存储的 Native Memory 中, 这样虽然可以增加了对手机本身内存的利用率, 可是 JVM 的垃圾收集器并不能回收 Native 分配的内存, 需要开发人员手动调用 recycle()方法回收图片内存, 而且容易出问题. 但是在 Android 8.0 引入的一种辅助自动回收 native 内存的一种机制 NativeAllocationRegistry, 它可以辅助回收 Java 对象所申请的 native 内存.
已经读取到内存中 Bitmap, 如何获取它们在内存中占用的内存大小呢.
- public static int getBitmapSize(Bitmap bitmap) {
- if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT) { //API 19
- return bitmap.getAllocationByteCount();
- }
- if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
- return bitmap.getByteCount();
- }
- // 在低版本中用一行的字节 x 高度
- return bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
- }
通过这个方法可以发现, 同样尺寸的图片在内存中占用的大小跟图片本身的大小没有关系. 将相同尺寸的图片放在同一个屏幕密度的手机上面, 即使一张图片大小是 1M, 另一张图片大小是 100K, 但是在内存中占用的大小仍然是相同. 如果一张图片尺寸是 720*1280, 大小是 61.37kb, 如果放在手机分辨率也是 720*1280 的 drawable-xhdpi, 那么该图片占用的内存大小是多小呢? 我们使用 BitmapFactory 的
decodeResource(Resources res, int id)
方法来加载图片, 实际上加载在内存中大小是 3600kb 大小, 读取的 Bitmap 的尺寸刚好和原图尺寸一致也是 720*1280, 但是内存却几乎是原图片占硬盘大小的 60 倍. 同样如果将图片放在 drawable-hdpi, 图片内存大小为 6401kb, 图片尺寸 960*1707, 原图只是现在在内存中图片宽高的 0.75 倍.
BitmapFactory 在加载一个图片时占用内存大小跟两个参数有关系 inDensity 和 inTargetDensity, 由于设备的屏幕密度是 320dpi, 所以 inTargetDensity 是 320, 如果将图片放在 drawable-xhdpi,inDensity 也是 320, 但是如果放在 drawable-hdpi, 此时的 inDensity 变为了 240, 由于将一个图片显示在某个屏幕密度的设备上就要根据屏幕密度进行缩放, 原来的宽高乘以所以系数, 这个缩放系数即为 inTargetDensity 除以 inDensity, 所以上的输出值也就说的通了.
除了与放置图片的的资源目录如 (drawable-hdpi,drawable-xhdpi) 有关系, 还跟 Bitmap 的像素格式有关系, 在加载图片时如果不做任何设置默认像素格式是 ARGB_8888, 这样每像素占用 4Byte, 而 RGB565 则是 2Byte, 除此之外还有 ARGB_4444 和 ALPHA_8, 虽然 ARGB_4444 占用内存只有 ARGB8888 的一半, 不过图片的失真比较严重已经被官方嫌弃. 而 ALPHA_8 只有透明度, 没有颜色值, 对于在图片上设置遮盖的效果的是有很有用. 因此如果设置的图片不设置透明度, RGB_565 是个不错选择, 既要设置透明度, 对图片质量要求又高的化, 那就用 ARGB_8888.
一般在开发中在处理图片加载使用的是第三方图片加载框架, 只要可以熟练使用, 基本上我们不必担心图引起的内存泄漏问题. 但是如果在某些场景需要自己处理 Bitmap, 记住使用 BitmapFactory 的 Option, 大图片一定要使用 inJustDecodeBounds 进行缩放, 因为设置 inJustDecodeBounds 的属性为 true 的时候, 我们解码的时候不分配内存但是却可以知道图片的宽高. 另外在设置 inSampleSize 时, 建议设置的图片的宽高以不超过原图 2 倍宽高为宜.
系统管理类引起的内存泄漏
这里所指的一些类是指通过 getSystemService()方法获取的类, 一般情况下建议使用上文所说的 getApplicationContext()获取, 因为这些类是系统服务类, 但是常用的 LayoutInflater 除外. 因为这些常用的系统服务类都是单利模式的, 如果在整个应用的生命周期之内都必须使用的话, 使用 getApplicationContext()也不必担心内存溢出. 但是某些类是在某个页面或者某种特殊场景下才会用到, 一旦使用过有可能系统并不会主动释放资源, 很有可能导致内存泄漏. 比如 InputMethodManager 在某种类型的手机上回出现内存泄漏, ClipboardManager 以及 SensorManager 在使用之后可能忘记解绑了, 都有会导致内存泄漏.
网上有关 InputMethodManager 引起内存泄漏的讨论也挺多的, 有些开发者认为可以不必关心这点, 因为 InputMethodManager 对象并不是完全归前一个 Activity 持有, 只是暂时性的指向了它, InputMethodManager 的对象是被整个 APP 循环的使用. 另外, InputMethodManager 是通过单例实现的, 不会造成内存的叠加, 如果使用了 leakCancy 一直检测出来可以直接屏蔽掉. 但是尽管如此, InputMethodManager 确实有内存泄漏的情况, 如下给出了解决内存泄漏的代码, 代码参考自 InputMethodManager 内存泄露现象及解决 https://blog.csdn.net/sodino/article/details/32188809 .
- public static void fixInputMethodManagerLeak(Context context) {
- if (context == null) {
- return;
- }
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm == null) {
- return;
- }
- String[] fieldValues = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
- Object objGet = null;
- Field oomField = null;
- for (String fieldValue : fieldValues) {
- try {
- oomField = InputMethodManager.class.getDeclaredField(fieldValue);
- if (oomField == null) {
- continue;
- }
- if (oomField.isAccessible() == false) {
- oomField.setAccessible(true);
- }
- objGet = oomField.get(imm);
- View viewGet = (View) objGet;
- if (viewGet.getContext() == context) {
- oomField.set(imm, null);
- }
- } catch (Throwable e) {
- continue;
- }
- }
- }
如果某些特定页面使用了 ClipboardManager 以及 SensorManager 等系统服务类, 一定要在页面销毁的时候注释解绑服务, 防止造成不必要的内存泄漏.
- @Override
- protected void onDestroy() {
- super.onDestroy();
- clipboardManager.removePrimaryClipChangedListener(clipChangedListener);
- sensorManager.unregisterListener(eventListener);
- //...
- }
动画引起的内存泄漏
动画的使用虽然可以提高用户体验, 可是使用不当也非常容易造成内存溢出. 这里主要介绍两种情况, 一种是帧动画引起内存溢出, 某些特效需要多张图片, 并且单张图片尺寸有比较大, 使用系统提供的方法很容易就会导致内存溢出. 帧动画将图片全部读出来后按顺序设置给 ImageView, 利用视觉暂留效果实现了动画. 一次拿出这么多图片, 而系统都是以 Bitmap 位图形式读取的. 而动画的播放是按顺序来的, 大量 Bitmap 就排好队等待播放然后释放. 在网上也给出了许多解决方式, 出发点基本是开发人员自己写一个实现图片顺序循环加载的逻辑达到帧动画同样的效果. 但是个人认为这里应该从设计方面考虑, 动效设计必须考虑不同平台的流畅性可用性, 不能影响到产品性能.
另外一种就是页面在用户不可见时没有及时停止动画导致内存泄漏. 由于动画从开始到结束时需要一定的时间的, 但是有可能用户还没等到动画执行结束就已经跳过该页面, 这时候应该及时停止动画. 如果动画是在 View 中定义的, 在 View 不可见时注意停止动画, 如果是在 Activity 中, 注意在 onDestroy()或者 onStop()方法中停止动画.
- @Override
- protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (visibility != VISIBLE) {
- animation.cancel();
- }
- }
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- animation.cancel();
- }
其它
资源未关闭造成的内存泄漏. 资源性对象比如 Cursor,Stream,MediaRecorder,File 文件等往往都用了一些缓冲. 这些资源在进行读写操作时通常都使用了缓冲, 如果及时不关闭, 这些缓冲对象就会一直被占用而得不到释放, 以致发生内存泄露. 因此在不需要使用它们的时候就及时关闭, 以便缓冲能及时得到释放, 从而避免内存泄露. 另外需要注意的一点就是, 如果数据库频繁开启关闭连接, 这样也很影响性能, 而且容易导致 StackOverFlowError.
集合中的元素在使用过之后要及时清理. 如果一个对象放入到 ArrayList,HashMap 等集合中, 这个集合就会持有该对象的引用. 当我们不再需要这个对象时, 也并没有将它从集合中移除, 这样只要集合还在使用(而此对象已经无用了), 这个对象就造成了内存泄露.
Protocol buffers 是由 Google 为序列化结构数据而设计的, 一种语言无关, 平台无关, 具有良好的扩展性. 类似 XML, 却比 XML 更加轻量, 快速, 简单. 如果你需要为你的数据实现序列化与协议化, 建议使用 nano protobufs. 关于更多细节, 请参考 protobuf readme 的 "Nano version" 章节.
如果应用需要在后台使用 service, 除非它被触发并执行一个任务, 否则其他时候 Service 都应该是停止状态. 当你启动一个 Service, 系统会倾向为了保留这个 Service 而一直保留 Service 所在的进程. 这使得进程的运行代价很高, 因为系统没有办法把 Service 所占用的 RAM 空间腾出来让给其他组件, 另外 Service 还不能被 Paged out. 这减少了系统能够存放到 LRU 缓存当中的进程数量, 它会影响应用之间的切换效率, 甚至会导致系统内存使用不稳定, 从而无法继续保持住所有目前正在运行的 service. 建议使用 IntentService, 它会在处理完交代给它的任务之后尽快结束自己.
除此之外, 在使用 BroadcastReceiver 的时候要注意注销掉广播, 应用内广播建议使用 LocalBroadcastManager.ListView 和 GridView 一定要注意复用 convertView 并结合 ViewHolder. 谨慎使用第三方库以及注解等等, 本篇文章就介绍到这里, 后续会再另起一篇博文介绍出现内存溢出 OOM 后如何使用工具检查内存泄漏.
来源: http://www.tuicool.com/articles/zqme6fm