对于开发老手,这个问题想必已经深入你的心;若是一名新手或者一直对内存泄漏这个东西模模糊糊的工程师,你的答案可能让面试官并不满意,这里将从底到上对内存泄漏的原因、排查方法和一些经验为你做一次完整的解剖。
处理内存泄漏的问题是将软件做到极致的一个必须的步骤,尤其是那种将被用户高强度使用的软件。
案例:
public class PendingOrderManager {
private static PendingOrderManager instance;
private Context mContext;
public PendingOrderManager(Context context) {
this.mContext = context;
}
public static PendingOrderManager getInstance(Context context) {
if (instance == null) {
instance = new PendingOrderManager(context);
}
return instance;
}
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的内存而导致了内存泄漏。下面两节将对这个里面到底发生了什么进行说明。
编译原理说软件内存分配的时候一般会放在三种位置:静态存储区域、堆和栈,他们的位置、功能、速度都各不相同,区别如下:
在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。但是误判是经常发生的,有些内存实际上已经没有用处了,但是GC并不知道。这里简单介绍下GC的机制:
上面一节说过栈上的局部变量可以引用堆上的分配的内存,所以GC发生的时候,一般是遍历一下静态存储区、栈从而列出所有堆上被他们引用的内存(对象)集合,这些内存都是有个引用计数,那么除此之外,其他的内存就是没有被引用的(或者说引用计数归零),这些内存就是要被释放的,随后GC开始清理这些内存(对象)
那么这里第一节的两个例子就很好理解了,那个单例模式由于生命周期太长(可以把他看作一个虚拟的栈中的局部变量)并且一直引用了Context(即Activity),所以GC的时候发现这个Activity的引用计数还是大于1,所以回收内存的时候把他跳过,但实际上我们已经不需要这块内存了。这样就导致了内存泄漏。
从上面来看,内存泄漏因为对象被别人引用了而导致,java为了避免这种问题(假如你的单例模式必须要传入个Context),特地提供了几个特殊引用类型,其中一个叫做弱引用WeakReference,当它引用一个对象的时候,即使该WeakReference的生命周期更长,但是只要发生GC,它就立即释放所被引用的内存而不会继续持有。
这里有一个常用的例子:
通常我们会在自定义的Application中来记住app中创建的Activity,从而中途在某个Activity中需要完全退出app时可以完全的销毁所有已经打开的Activity,这里我们可以对自定义Application改造,让其只有一个对Activity的弱引用的HashMap,大致的代码如下:
public class CustomApplication extends Application {
private HashMap<String, WeakReference<Activity>> activityList = new HashMap<String, WeakReference<Activity>>();
private static CustomApplication instance;
public static CustomApplication getInstance() {
return instance;
}
public void addActivity(Activity activity) {
if (null != activity) {
L.d("********* add Activity " + activity.getClass().getName());
activityList.put(activity.getClass().getName(), new WeakReference<>(activity));
}
}
public void removeActivity(Activity activity) {
if (null != activity) {
L.d("********* remove Activity " + activity.getClass().getName());
activityList.remove(activity.getClass().getName());
}
}
public void exit() {
for (String key : activityList.keySet()) {
WeakReference<Activity> 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);
到此你应该对内存泄漏的本质已经有所了解了,这里列举出一些会导致内存泄漏的地方,可以作为排查内存泄漏的一个checklist
之前Android开发通常使用MAT内存分析工具来排查heap的问题,之类的文章比较多,大家可以自己找。这里推荐一个叫做leakcanary的工具,他可以集成在你的代码里面。这个东西大家可以参考: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0509/2854.html
文章改编于: 内存泄漏弄个明白 - soaringEveryday - 博客园 http://www.cnblogs.com/soaringEveryday/p/5035366.html
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有