什么是内存泄漏和内存溢出?
内存泄漏:memory leak
程序申请内存后,被某个其他对象一直持有,无法释放已申请的内存空间。(内存只进不出)
内存溢出:out of memory
当内存一直被申请,却得不到释放,内存空间太小。当程序在申请内存时,没有足够的空间供其使用,就出现OOM。
注意:内存泄漏/溢出不能try catch处理。
产生的原因和解决方法
-
大图片和多图片的优化
图片如果太大,加载到内存时可能导致OOM。
解决:-
合理使用JPG和PNG
- JPG内存小,但解码复杂
- PNG内存大,但解码简单。如果PNG图片过多,会造成频繁GC,甚至OOM。
-
图片压缩:
- createScaledBitmap();现成的API,但是这个API的使用前提条件是图片需要先加载到内存中。
- 还有一个经常使用到的技巧是inJustDecodeBounds,可以事先获取到图片的大小。需要设置bitmapOptions.inJustDecodeBounds = true;
- 降低分辨率
- 指定解码方式:
- Bitmap.Config ARGB_8888 默认,一个像素点占32位,最占内存的
- Bitmap.Config ARGB_4444 一个像素点占16位
-
bitmap对象不使用时,要recycle()释放内存
-
尽量使用RecyclerView或者ListView替换ScrollView
-
聊一下图片的三级缓存
- 目的:减少不必要的流量消耗,增加加载速度
- 原理:
- 一级缓存–>内存,LruCache,采用最近最少原则,把最近使用的通过LinkedhashMap来强引用持有,把最少使用的对象在缓存值达到预设值之前就从内存中移除
- 二级缓存–>本地,DiskLruCache,
- 三级缓存–>网络
- 流程:先从内存加载,内存中有,就直接加载;内存中没有,就从本地加载,若本地中有,加载的同时缓存到内存;若本地也没有,从网络加载,同时缓存到内存和本地。
- Glide自带三级缓存
- skipMemoryCache(false),默认false,自动开启内存缓存。
- diskCacheStrategy(DiskCacheStrategy.RESULT),开启硬盘缓存
DiskCacheStrategy.NONE: 表示不缓存任何内容
DiskCacheStrategy.SOURCE: 表示只缓存原始图片
DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片
-
聊一下图片加载框架
- Glide:
可以处理大型的图片流,图片自动缩放的 - Picasso:
体积非常小,图片未缩放的 - Fresco:
体积非常大,图片存储在系统的匿名共享内存,而不是dalvik heap内存中,所以不会导致OOM,减少了频繁GC,性能更高。
- Glide:
-
-
强引用、软引用、弱引用、虚引用
-
强引用:
直接new出来的对象
String str = new String(“aaa”);
特点:对象任何时候都不会对系统回收,JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。 -
软引用:SoftReference
String str = new String(“aaa”);
SoftReference s = new SoftReference(str);
if(s.get() != null){ // 一定要判空
String softStr = s.get();
}
特点:内存空间足,不回收;内存空间不足,就会回收 -
弱引用:WeakReference
String str = new String(“aaa”);
WeakReferences = new WeakReference(str);
if(s.get() != null){ // 一定要判空
String weakStr = s.get();
}
特点:无论内存空间是否足够,只要发现,就会回收 -
虚引用:PhantomReference
特点:可以在任何时候被回收,无法通过get()方法来获取对象实例。仅仅只能在对象被回收时收到一个通知。
我们知道,java的Object类里有个finalize()方法,它的工作原理是:一旦该对象即将被回收,会调用其finalize()方法,并且在下一次真正回收动作发生时,才会真正的回收它。
但是问题在于,GC时间是随机的,finalize()方法被调用时间也是随机的,使用虚引用就可以解决这个问题。虚引用可以精细的跟踪内存使用情况。到底什么时候用软引用,什么时候用弱引用?
1,如果只是想避免OOM,则可以使用软引用;如果对应用的性能更在意,则可以使用弱引用。
2,根据对象是否经常被使用。如果对象经常被使用,则尽量用软引用,否则使用弱引用。 -
-
单例导致的
单例的静态特性使其生命周期和应用一样长,所以传入的context的生命周期至关重要,一定要传入Application的Context,而不是Activity的Context。 -
在非静态类里创建静态实例
该静态实例生命周期和应用一样长,导致了该静态实例一直持有该Activity的引用,该Activity销毁后内存资源不能正常回收。要么去掉static,要么将this换成Application的Context
-
用非静态内部类创建其静态实例
非静态内部类会默认持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。要么将该内部类设为静态内部类,因为静态的内部类不会持有外部类的一个隐式引用;要么将该内部类抽取出来,如果需要使用Context,需要使用ApplicationContext 。
-
异步任务导致的
若Activity已经销毁,但是异步任务还没结束,需要将AsyncTask、Thread等设为静态内部类,而且需要在Activity销毁时取消相应的异步任务。 -
Handler导致的
Handler是非静态匿名内部类,持有Activity的引用,虽然Handler实例不是静态的,但是消息队列是在Looper线程中不断轮询处理消息的,当Activity销毁,消息队列中还有未处理的消息,消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,就会导致Activity内存资源无法正常回收。将Handler设为静态内部类,传入的Context可以使用ApplicationContext,也可以将Context弱引用,而且需要在Activity销毁时移除消息队列中的消息,handler.removeCallbacksAndMessages。
-
动画导致的
属性动画中可以设置无限循环动画,该View持有该Activity对象的引用需要在Activity销毁时停止动画,objectAnimator.cancel()。
-
资源未关闭导致的
IO流未关闭、BroadcastReceiver未注销、Bitmap未释放等。 -
第三方库导致的
EventBus,RxJava等,在Activity销毁时需要解除订阅