内存泄漏(Leak)比较轻,内存溢出OOM很严重。频繁内存泄漏,会最终导致内存溢出,APP崩溃。
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
ActivityManager.getMemoryClass()可以查询当前APP的Heap Size阈值,单位是MB。
在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
Android 2.x系统,当dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM,也就是说在2.x系统中,考虑native heap对每个进程的内存限制。
参考在MDCC 2015中国移动开发者大会上胡凯前辈的讲述,整理总结。
1、使用轻量的数据结构
使用ArrayMap/SparseArray来代替HashMap。ArrayMap/SparseArray是专门为移动设备设计的高效的数据结构。
HashMap实现原理
HashMap内部使用一个默认容量为16的数组来存储数据,采用拉链法解决hash冲突(数组+链表),如下图:
Entry存储的内容有key、value、hash值、next指针,通过计算hash(key)%len找到Entry在数组中的位置。
SparseArray
ArrayMap
2、不要使用Enum
3、大胖子Bitmap的处理
4、不要使用String进行字符串拼接
严格的讲,String拼接只能归结到内存抖动中,因为产生的String副本能够被GC,不会造成内存泄露。
频繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。
5、非静态内部类内存泄露
在Activity中创建非静态内部类,非静态内部类会持有Activity的隐式引用,若内部类生命周期长于Activity,会导致Activity实例无法被回收。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)。
解决方案:如果一定要使用内部类,就改用static内部类,在内部类中通过WeakReference的方式引用外界资源。
正确的代码示例:
static class ImageDownloadTask extends AsyncTask {
private String url;
private WeakReference photoAdapter;
public ImageDownloadTask(PhotoAdapter photoAdapter) {
this.photoAdapter = new WeakReference(photoAdapter);
}
@Override
protected Bitmap doInBackground(String... params) {
//在后台开始下载图片
url = params[0];
Bitmap bitmap = photoAdapter.get().loadBitmap(url);
if (bitmap != null) {
//把下载好的图片放入LruCache中
String key = MD5Tools.decodeString(url);
photoAdapter.get().put(key, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下载好的图片显示出来
ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
photoAdapter.get().mDownloadTaskList.remove(this);//把下载好的任务移除
}
}
}
6、匿名内部类内存泄漏
跟非静态内部类一样,匿名内部类也会持有外部类的隐式引用,比较常见的情况有,耗时Handler,耗时Thread,都会造成内存泄漏,解决方式也是static+WeakReference,下面给出正确写法。
Handler的正确写法:
private static class MyHandler extends Handler {
private final WeakReference context;
private MyHandler(Context context) {
this.context = new WeakReference(context);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// 发送一个10分钟后执行的一个消息
mHandler.postDelayed(sRunnable, 600000);
}
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
// TODO 耗时任务
}
}
}
new MyThread().start();
7、Context持有导致内存泄漏
8、记得注销监听器
9、资源文件需要选择合适的文件夹进行存放
10、谨慎使用static对象
11、谨慎使用单例中不合理的持有
12、一定要记得关闭无用连接
注意:谨慎使用lager heap
这里介绍LeakCanary,一款非常好用的内存泄露检测工具,安装在手机上,能够通过Log的方式告诉你是哪块代码发生了内存泄露。
使用方法,在Application中install LeakCanary(默认只能检测Activity内容的内存泄露):
public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
public class MyApplication extends Application {
private static RefWatcher sRefWatcher;
@Override
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return sRefWatcher;
}
}
MyApplication.getRefWatcher().watch(sLeaky);
public class MyFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
MyApplication.getRefWatcher().watch(this);
}
}
---------------------------------------------------------------------------------------------------------------------------
android应用层是由java开发的,android的davlik虚拟机与jvm也类似,只不过它是基于寄存器的。
在java中,通过new为对象分配内存,所有对象在java堆内分配空间;而内存的释放是由垃圾收集器(GC)来回收的。 Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点(GC roots)开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存溢出是指当对象的内存占用已经超出分配内存的空间大小,这时未经处理的异常就会抛出。比如常见的内存溢出情况有:bitmap过大;引用没释放;资源对象没关闭
如图,这是常见的bitma对象的溢出,显示像素过高或图片尺寸远远大于显示空间的尺寸时,通常都要将其缩放,减小占用内存。
1、内存泄露导致
由于我们程序的失误,长期保持某些资源(如Context)的引用,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成内存泄露。
Android 中常见就是Activity被引用在调用finish之后却没有释放,第二次打开activity又重新创建.内存泄露不断的发生,则会导致内存的溢出。
Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程.
2、占用内存较多的对象
保存了多个耗用内存过大的对象(如Bitmap)或加载单个超大的图片,造成内存超出限制。
有些对象只有有限的生命周期。当它们的任务完成之后,它们将被垃圾回收。如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏。随着泄漏的累积,app将消耗完内存。
比如,在Activity.onDestroy()被调用之后,view树以及相关的bitmap都应该被垃圾回收。如果一个正在运行的后台线程继续持有这个Activity的引用,那么相关的内存将不会被回收。
memory leak会最终会导致out of memory!
如图,这是使用MAT工具查找内存泄漏的结果,例子是 handle 延时发送 message 而在关闭 activity 后 context 被销毁所引发的泄漏,这是作为目的性的测试所以问题比较容易找到。在实际开发中内存泄漏不易察觉并难以找到,当泄漏累积到一定程度是会引发 OOM 的。
内存泄漏的原因
比如当你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
1.资源对象没关闭
如Cursor,File等资源。他们会在finalize中关闭,但这样效率太低。容易造成内存泄漏
SQLiteCurost,当数据量大的时候容易泄漏
2.使用Adapter时,没有使用系统缓存的converView
3.没有即时调用recycle()释放不再使用的bitmap
4.应该使用application的context来替代activity相关的context
不要让生命周期长于Activity的对象持有到Activity的引用
5.广播注册没取消造成内存泄露
6.Handler应该申明为静态对象, 并在其内部类中保存一个对外部类的弱引用。