内存泄露是指不再使用的对象持续占有内存空间或者无用对象的内存没有及时释放,从而造成内存空间的浪费。
Android中常见的内存泄露有以下几种:
1.单例造成的内存泄露
开发中我们经常会使用到单例模式,而由于单例的静态特性,它的生命周期跟应用的生命周期一样长。如果在创建单例时需要传入Context做为构造参数,这种情况就要注意了。
public class Singleton {
private Context mContext;
private volatile static Singleton sInstance;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (sInstance == null) {
synchronized (Singleton.class) {
if(sInstance == null) {
sInstance = new Singleton(context);
}
}
}
return sInstance;
}
}
上述代码,如果创建单例时传入的Context对象是一个Activity,那么,当这个Activity要退出时,由于该Context的引用仍然被单例持有,而单例的生命周期和应用的生命周期一样长,这就导致了Activity退出时内存无法被回收,最终造成内存泄露。
面对这种情况我们有什么解决办法吗?
1.创建单例时尽量使用getApplicationContext()或者getApplication()代替
2.修改单例的构造方法
private Singleton(Context context) {
this.mContext = context.getApplicationContext();
}
3.在需要的时候再传入Context
2.非静态内部类创建静态实例造成的内存溢出
public class MainActivity extends AppCompatActivity {
private static Demo mDemo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDemo = new Demo();
}
class Demo {
}
}
非静态内部类隐式持有外部类的引用,而全局的stacic数据处于静态的存储区,静态存储区的内存在程序编译的时候就已经分配好了,并且在整个运行期间一直存在。被静态实例隐式持有了外部类的引用,导致activity结束后内存无法被回收,造成内存泄露。
解决方法:
用静态内部类代替非静态内部类。静态内部类不会隐式持有外部类的引用。
3.异步线程导致的内存泄露(常见的Handler、Handler,AsyncTask,Thread,Timer等)
非静态内部类和匿名类会持有外部类的隐式引用,并且非静态内部类/匿名类是用来执行一些异步的操作(Handler,AsyncTask,Thread,Timer等),外部类就有可能发生泄漏。
而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收。但是使用静态内部类/匿名类后,你会发现无法操作外部类的属性或方法,如果想在静态内部类/匿名类中使用外部类的属性或方法时,可以显式的持有一个外部类的弱引用。
另外,创建一个静态Handler内部类,然后对 Handler 持有的对象使用弱引用,这样在回收时也可以回收 Handler 持有的对象,但是这样做虽然避免了 Activity 泄漏,不过 Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
4.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
说到内存泄露的问题,继续总结一下面试中常碰到的内存相关的提问。
Java的四种引用方式
强引用:指创建一个对象并把这个对象赋值给一个引用变量。
Object object =new Object();
String str ="string";
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OOM也不会回收这种对象。除非代码段执行完毕,超出引用变量的范围。如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来,JVM就会在合适的时间回收该对象。
2.软引用:SoftReference,当内存空间不足时,会被垃圾回收。
3.弱引用:WeakReference,当JVM进行垃圾回收时,不管内存是否充足,都会回收被弱引用关联的对象。
4.虚引用:主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
监听系统的GC过程
系统每进行一次GC操作时,都会在LogCat中打印一条日志,我们只要去分析这条日志就可以了,日志的格式如下:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>,<Pause_time>
GC_Reason表示触发这次GC操作的原因,一般情况下一共有以下几种触发GC操作的原因:
GC_CONCURRENT: 当我们应用程序的堆内存快要满的时候,系统会自动触发GC操作来释放内存。
GC_FOR_MALLOC: 当我们的应用程序需要分配更多内存,可是现有内存已经不足的时候,系统会进行GC操作来释放内存。
GC_HPROF_DUMP_HEAP: 使用dump heap当生成HPROF文件的时候,系统会进行GC操作。
GC_EXPLICIT: 主动通知系统去进行GC操作,比如调用System.gc()方法来通知系统(调用后不一定立即执行)。
Amount_freed,表示系统通过这次GC操作释放了多少内存。
Heap_stats中会显示当前内存的空闲比例以及使用情况(活动对象所占内存 / 当前程序总内存)。
Pause_time表示这次GC操作导致应用程序暂停的时间。
在Android 2.3之前GC操作是不能并发进行的,也就是系统正在进行GC,那么应用程序就只能阻塞住等待GC结束。虽说这个阻塞的过程并不会很长,也就是几百毫秒,但是用户在使用我们的程序时还是有可能会感觉到略微的卡顿。而自2.3之后,GC操作改成了并发的方式进行,就是说GC的过程中不会影响到应用程序的正常运行,但是在GC操作的开始和结束的时候会短暂阻塞一段时间,