如果你参加面试,面试官经常会问到你的一个问题可能是:你在开发过程中,有过排除内存泄漏的经验吗?对于一个合格的Android/C/Java开发老手,这个问题想必已经深入你的心;若是一名新手或者一直对内存泄漏这个东西模模糊糊的工程师,你的答案可能让面试官并不满意,这里将从低到上对内存泄漏的原因、排查方法和一些经验为你做一次完整的解剖。

同时处理内存泄漏的问题是将软件做到极致的一个必须的步骤,尤其是那种被用户高强度使用的软件。

一个简单的C和Android的例子

一个最简单的C的内存泄漏的例子:

char *ptr1 = (char *)malloc(10);char *ptr2 = (char *)malloc(10);

ptr2=ptr1;

free(ptr1)

这里最后发生了10个字节的内存泄漏,那么道理发生了什么?

首先各自分配了两块10个字节的内存,分别用叫ptr1和ptr2的指针指向这两块内存(就像是java中的引用),然后呢让ptr2也指向一开始ptr1指向的那块内存(这时候ptr1和ptr2都指向了ptr1一开始指向的那个10个字节的内存),最后用free将ptr1指向的那块内存给释放了——>结果就是一开始ptr2指向的那块内存发生了泄漏(没人用了却又回收不掉)

可能你会说Java有内存垃圾回收机制,不要考虑谁分配和释放的访问,那么下面这个例子就会让你明白你错了:

public classPendingOrderManager {private staticPendingOrderManager instance;privateContext mContext;publicPendingOrderManager(Context context) {this.mContext =context;

}public staticPendingOrderManager getInstance(Context context) {if (instance == null) {

instance= newPendingOrderManager(context);

}returninstance;

}

public void func(){

...

}

...

}

然后让你的某个Activity去使用这个PendingOrderManager单例,并且某个时候退出这个Activity:

//belong to some Activity

PendingOrderManager.getInstance(this).func();

...

finish()

这个时候内存泄漏已经发生:你退出了你的这个Activity本以为java的垃圾回收会将它释放,但实际上Activity一直被PendingOrderManager持有着。Acitivity这个Context被长生命周期(单例一旦被创建就是整个app的生命周期)持有导致了这个Context发生了内存泄漏。

这个例子和上面的例子是相通的,上面的C的例子因为忘记了手动执行free一个10字节内存导致内存泄漏。而下面这个例子是垃圾回收机制“故意忘记”了回收Context的内存而导致了内存泄漏。下面两节将对这个里面到底发生了什么进行说明。

静态、堆和栈

编译原理说软件内存分配的时候一般会放在三种位置:静态存储区域、堆和栈,他们的位置、功能、速度都各不相同,区别如下:

静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量

栈:就是CPU的寄存器(并不是内存),特点是容量很小但是速度最快,函数或者方法的的方法体内声明的变量或者指向对象的引用、局部变量即分配在这里,生命周期到该函数或者方法体尾部即止

堆:就是动态内存分配去(就是实体的内存RAM),C中malloc和fee,java中的new和垃圾回收直接操作的就是这里的区域,类的成员变量分配在这里

从上面即可看出静态存储区域是编译时已经分配好的,栈是CPU自动控制的,那么我们所讨论的内存泄漏的问题实际上就是分配在堆里面的内存出现了问题,一般问题在于两点:

快速不断的进行new操作。比如Android的自定义View的时候你在onDraw里面new出对象,就会导致这个自定义View的绘制特别卡,这是因为onDraw是快速重复执行的方法,在这个方法里面每次都new出对象会导致连续不断的new出新的对象,也导致gc也在不断的执行从而不断的回收堆内存。由于堆位于内存条上,这样子就导致了内存的不断的分配和回收消耗了CPU,同时导致了内存出现“空洞”(因为堆内存不是连续的)

忘记释放。如果你忘记了手动释放应该释放的内存,或者gc误判导致没有释放本应该释放的内存,那么久导致了内存泄漏。由于Android给一个app可在堆上(可以在AndroidManifest设置一个largeHeap="true"增大可分配量)分配的内存量是有限的,如果内存泄漏不断的发生,总有一天会消耗完毕,从而导致OOM

Java有了垃圾回收(GC)为什么任然后内存泄漏

在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。但是误判是经常发生的,有些内存实际上已经没有用处了,但是GC并不知道。这里简单介绍下GC的机制:

上面一节说过栈上的局部变量可以引用对上的分配的内存,所以GC发生的时候,一般是遍历一下静态存储区、栈从而列出所有堆上被引用的内存(对象)集合,这些内存都是有个引用计数,那么除此之外,其他的内存就是没有被引用的(或者说引用计数归零),这些内存就是要被释放的,随后GC开始清理这些内存(对象)

那么这里第一节的两个例子就很好理解了,那个单例模式由于生命周期太长(可以把他看作一个虚拟的栈中的局部变量)并且一直引用了Context(即Activity),所以GC的时候发现这个Activity的引用计数还是大于1,所以回收内存的时候把他跳过,但实际上我们已经不需要这块内存了。这样就导致了内存泄漏。

Android使用弱引用和完美退出app的方法

从上面来看,内存泄漏因为对象被别人引用了而导致,java为了避免这种问题(假如你的单例模式必须要传入个Context),特地提供了几个特殊引用类型,其中一个叫做弱引用WeakReference,当它引用一个对象的时候,即使该WeakReference的生命周期更长,但是只要发生GC,它就立即释放所被引用的内存而不会继续持有。

这里有一个常用的例子:

通常我们会在自定义的Application中来记住app中创建的Activity,从而中途在某个Activity中需要完全退出app时可以完全的销毁所有已经打开的Activity,这里我们可以对自定义Application改造,让其只有一个对Activity的弱引用的HashMap,大致的代码如下:

public class CustomApplication extendsApplication {private HashMap> activityList = new HashMap>();private staticCustomApplication instance;public staticCustomApplication getInstance() {returninstance;

}public voidaddActivity(Activity activity) {if (null !=activity) {

L.d("********* add Activity " +activity.getClass().getName());

activityList.put(activity.getClass().getName(),new WeakReference<>(activity));

}

}public voidremoveActivity(Activity activity) {if (null !=activity) {

L.d("********* remove Activity " +activity.getClass().getName());

activityList.remove(activity.getClass().getName());

}

}public voidexit() {for(String key : activityList.keySet()) {

WeakReference activity =activityList.get(key);if (activity != null && activity.get() != null) {

L.d("********* Exit " +activity.get().getClass().getSimpleName());

activity.get().finish();

}

}

System.exit(0);

android.os.Process.killProcess(android.os.Process.myPid());

}

}

我们在自定义的Activity的基类BaseActivity中的onCreate执行:

CustomApplication.getInstance().addActivity(this);

在BaseActivity的onDestroy中执行:

CustomApplication.getInstance().removeActivity(this);

这样子自定义Application就能不泄露的持有所有打开的Activity的引用,同时当你需要中途退出app的时候,只需要执行:

//完美退出程序方法

CustomApplication.getInstance().exit();

哪些情况会导致内存泄漏

到此你应该对内存泄漏的本质已经有所了解了,这里列举出一些会导致内存泄漏的地方,可以作为排查内存泄漏的一个checklist

某个集合类(List)被一个static变量引用,同时这个集合类没有删除自己内部的元素

单例模式持有外部本应该被释放的对象(第一节中那个例子)

Android特殊组件或者类忘记释放,比如:BraodcastReceiver忘记解注册、Cursor忘记销毁、Socket忘记close、TypedArray忘记recycle、callback忘记remove。如果你自己定义了一个类,最好不要直接将一个Activity类型作为他的属性,如果必须要用,要么处理好释放的问题,要么使用弱引用

Handler。只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。如上所述,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。

Thread。如果Thread的run方法一直在循环的执行不停,而该Thread又持有了外部变量,那么这个外部变量即发生内存泄漏。

网络请求或者其他异步线程。之前Volley会有这样的一个问题,在Volley的response来到之前如果Activity已经退出了而且response里面含有Activity的成员变量,会导致该Activity发生内存泄漏,该问题一直没有找到合适的解决办法。不过看来Volley官网已经注意到这个问题了,目前最新的版本已经fix this leak

使用leakcanary

之前Android开发通常使用MAT内存分析工具来排查heap的问题,之类的文章比较多,大家可以自己找。这里推荐一个叫做leakcanary的工具,他可以集成在你的代码里面。这个东西大家可以参考:

android c 内存泄露,内存泄漏弄个明白相关推荐

  1. 堆,栈,内存泄露,内存溢出介绍

    简单的可以理解为: heap(堆):是由malloc之类函数分配的空间所在地.地址是由低向高增长的. stack(栈):是自动分配变量,以及函数调用的时候所使用的一些空间.地址是由高向低减少的. 一. ...

  2. 内存泄露 内存溢出 内存碎片

    ******************************************************************************** 内存泄漏也称作"存储渗漏&q ...

  3. win10系统内存泄露内存居高不下但是实际又没什么程序占内存的解决方案

    现象: 电脑就开了微信开机10给小时左右就占用72%的内存了 我可是32G的内存. 重启后又一天的时间慢慢的增长就又达到了80%左右(什么都没运行).有点像内存泄漏的问题,但是进程里面也看不出来那个进 ...

  4. Android开发过程中内存泄露检测

    转自 http://blog.csdn.net/shimiso/article/details/44677041 一.内存泄露 内存泄漏会因为减少可用内存的数量从而降低计算机的性能.最终,在最糟糕的情 ...

  5. Android Studio 选项菜单和动画结合_Android性能测试③-发现和定位内存泄露amp;卡顿...

    Android性能测试③-发现和定位内存泄露&卡顿 Android用户也许会经常碰到以下的问题: 1)应用后台开着,手机很快没电了--应用耗电大: 2)首次/非首次启动应用,进入应用特别慢-- ...

  6. Android leak内存,GitHub - jin870132/memoryleakdemo: 安卓内存泄露几种常见形式及解决方案...

    安卓内存泄露几种常见形式及解决方案 一.前言 1.内存溢出与内存泄露 内存溢出(oom),是指程序在申请内存时,没有足够的内存空间供其使用,出现oom:比如申请了一个integer,但给它存了long ...

  7. 【内存泄露】Memory Leaks 内存优化

    什么是内存泄露 内存管理一直是Java 所鼓吹的强大优点.开发者只需要简单地创建对象,而Java的垃圾收集器将会自动管理内存空间的分配和释放. 但在很多情况下,事情并不那么简单,在 Java程序中总是 ...

  8. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  9. 如何使用Eclipse内存分析工具定位内存泄露

    本文以我司生产环境Java应用内存泄露为案例进行分析,讲解如何使用Eclipse的MAT分析定位问题 一. 背景 11月10号晚上8点收到报警邮件,一看是OOM 打开公司监控系统查看应用各项指标发现J ...

  10. Java内存泄露和内存溢出、JVM命令行工具、.JDK可视化工具、Java Class文件

    1.Java内存泄露和内存溢出对比 1.1 Java 内存泄露 内存泄露是指一个不再被程序使用的对象或变量还在内存中占用空间. 1.1.1判断内存空间是否符合垃圾回收的标准 在Java语言中,判断一个 ...

最新文章

  1. Google App Engine Java功能和命名空间API
  2. 在中断程序里修改全局变量的童鞋注意啦~(C中的volatile作用 转载~)
  3. 解决:Caused by: java.lang.UnsupportedOperationException: null
  4. 「NOIP 2013」 货车运输
  5. java中关于线程的状态属性_深入理解Java多线程与并发框(第①篇)——线程的状态...
  6. The LAO将于4月2日启动Neptune DAO,旨在为其他区块链项目提供流动性
  7. 某 iOS 零点击 0day 漏洞已存在8年之久且正遭利用?苹果称正在调查并将推出补丁...
  8. DockOne微信分享(一四一):如何开发部署Kubernetes Native应用
  9. Spring Boot + PageHelper 实现分页,总结得很全了!
  10. 利用集合类排序JAVA_Java使用Collections工具类对List集合进行排序
  11. oracle hint firstrow,stored outlines迁移成SQL执行计划基线
  12. easyExcel导出文件为空解决
  13. mac强制关机后悲剧了
  14. “云脉文档管理”微信小程序提供高效的办公体验
  15. 正则:\b 匹配出一个单词的小天使
  16. cron 每隔3天_crontab实现每隔多少天执行一次脚本的两种方法
  17. vue双向绑定是如何实现的
  18. Opencv(C++)笔记--模板匹配cv::matchTemplate()和最值计算cv::minMaxLoc()
  19. IDEA全局查找关键字的方法
  20. 五种 Ajax 反模式:避免常见的 Ajax 代码陷阱!

热门文章

  1. php中echo(),print(),print_r()的区别
  2. Spring自学教程-AOP学习(五)
  3. 关于asp.net页面缓存
  4. Linux 下串口编程入门教程
  5. 459B Pashmak and Flowers
  6. 配备 Apple T2 安全芯片的 Mac 电脑怎样用U盘装系统
  7. 如何解决PS软件工作区不能吸附到工具栏?
  8. Effective_STL 学习笔记(四十五) 注意 count、find、binary_search、lower_bound、upper_bound 和 equal_range 的区别...
  9. 29.2. Ubuntu
  10. unix系统简明教程 命令