Android内存管理机制与内存泄露的几种形式

码小农阅读笔记

华文澜
Android @ winlandiano

--

首先在这里吐槽我们的机动车驾驶证申请机制!低效不合理!浪费了我一整天加一个晚上的好心情!关键是还没有收货,到底没通过科二!!!

真郁闷!虽然这是好几天前的事情了依旧耿耿于怀……

今天拜读了子祥的“Android内存管理及内存泄露”的分享文,部分内容是以前就掌握的,部分内容是新学习的。

对我启迪比较大的有如下部分:

1. 以前简单的知道内存泄露的问题,但是仗着Java提供了一个虽然不完美但勉强能用的内存回收机制就可以仗剑行天了。但是这里提到一个问题:static 静态变量。一个以前没有想象到的问题:一个Activity里面引用了一个静态变量,就会让gc无法判定Activity在onDestroy里进行回收!太恐怖了!尤其是如果Activity里面有流媒体文件的缓存,如果在onStop的时候没来得及回收, 岂不是很恐怖!

拓展:

这里提供了一个静态变量的回收方法:引用之

1.1 对于确定不要的静态变量,置为null通知gc回收

1.2 在CSDN查到了一个变态的方法:

重写onDestroy,添加

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

直接干掉这个线程,好吧太暴力了,十四禁。。。

1.3 上网查了些资料,静态变量还有一个好用的地方,对于需要适应竖向屏幕和横向屏幕的APP来说,Android会将当前Activity状态保存,再销毁,在新的layout下create一个(应该是跳过了onCreate)。但是对一些大的缓存文件重新加载的速度只能靠静态变量来提速。例如:

private static Drawable sBackground; 

@Override
protected void onCreate(Bundle state) {
super.onCreate(state);

TextView label = new TextView(this);
label.setText(“Leaks are bad”);

if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);

setContentView(label);
}
public void onDestroy(Bundle state){ super.onDestroy(state); sBackground = null;}

但是就算这样也不能完全解决问题!因为你不能完全保证sBackground没有依附到其他地方。最好的方法是不适用Context_Activity而采用Context.getApplictaionContext获得Application对象代替Activity。这种Context拥有和应用程序一样长的生命周期,并且不依赖Activity的生命周期。如果你打算保存一个长时间的对象,并且其需要一个Context,记得使用Application对象。- http://www.2cto.com/kf/201108/101010.html

2. 另一些亮点在评论里:

图1屏幕截图引用

ceW���

那就我在这里补充吧,参考自红黑联盟Android内存管理机制详解http://www.2cto.com/kf/201212/175786.html

从内存角度分析,安卓的Activity分为如下四类:

2.1前台进程(foreground)

这点比较容易理解

2.2可见进程(visible)

可见进程是一些不在前台,但用户依然可见的进程,举个例来说:widget、输入法等

2.3桌面进程(home app)

即launcher,保证在多任务切换之后,可以快速返回到home界面而不需重新加载launcher。(注:也就是加载在桌面上的图标,桌面进程保证home键时快速的返回home。这样推测的话吐槽我用的MIUI,所有图标都强制放在桌面,这样的话无疑浪费了桌面进程的资源)

2.4次要服务(secondary server)

目前正在运行的一些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务),举例来说:谷歌企业套件,Gmail内部存储,联系人内部存储等。这部分服务虽然属于次要服务,但很一些系统功能依然息息相关,我们时常需要用到它们,所以也太希望他们被终止

2.5后台进程(hidden)

较易于理解。后台进程的管理策略有多种:有较为积极的方式,一旦程序到达后台立即终止,这种方式会提高程序的运行速度,但无法加速程序的再次启动;也有较消极的方式,尽可能多的保留后台程序,虽然可能会影响到单个程序的运行速度,但在再次启动已启动的程序时,速度会有所提升。这里就需要用户根据自己的使用习惯找到一个平衡点

2.6内容供应节点(content provider)

没有实体的应用,如邮件供应节点、日历等

2.7空进程(empty)

内部没有数据运行的Activity,存在的价值仅为Activity再次启动加速作用。有限回收。

在kernel/drivers/staging/android/lowmemorykiller.c可以查到内存回收的代码LowMemoryKiller代码这里就不粘了比较长。大致可以概括为如下部分:判断当前是否需要运行lowmemorykiller的, return一个rem变量;找到当前优先级大且占用内存最多的进程,返回select变量;杀死selected的进程,广播一个SIGKILL消息

为什么一些大应用运行时系统会慢?不是有内存回收机制吗?答案是,当内存较低时,应用会频繁的诱发系统启动内存回收机制,形成雪崩,内存回收机制是很慢的。

图2 评论

关于软引用的拓展

引用分为四种:

一.强引用

如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

二.软引用

较强引用较弱,内存空间够不会回收,内存空间不够,就会回收

三.弱引用

更弱,内存空间够不够,内存回收线程扫描到就会回收。但有趣的是,由于内存回收线程优先级较低,弱引用不会很快被回收。

四.虚引用

虚引用不会改变Activity的生命周期,一个Activity如果只虚引用那在内存回收线程眼里和没引用是一样的。虚引用的作用通常是跟踪对象被垃圾回收器回收的活动。

对比:

强弱关系一眼明了。虚引用有一点很特别,虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前, 把这个虚引用加入到与之 关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,达到虚引用的作用。

好吧就写这么多了,一晚上都奉献了……

--

--