###前言
在java中存在GC机制,会回收掉那些它认为不再被使用的的对象,从而释放内存空间。
###在java中有4中引用类型
- 强引用(Strong References)
- 软引用(Soft References)
- 弱引用(Weak References)
虚引用(Phantom References)
1.强引用
强引用的对象不会被GC回收.
<例1>
class Test{Object a = new Object();
}
该对象被回收的情景:
1.a = null,变量a释放掉对象的引用
2.Test类的对象被销毁。
1.1扩展 静态的强引用
<例2>
class Test{
static Object a = new Object();
}
与<例1>不同就是在声明变量时添加了static关键字,从而把成员变量变成静态成员变量。
该对象被回收的情景:
- a = null,变量a释放掉对象的引用
- Test类被回收。
这里解释一下<例1>和<例2>的第二点
<例1> 变量a存在于Test类的对象中,例如:new Test()中就存在一个变量a,变量a引用着一个对象new Object();
<例2> 静态变量a存在于Test类中,只要Test.class被加载入内存,静态变量a就初始化完成,并生成、引用Object对象。
<例1>和<例2>的区别就在于,<例1>的变量a是由对象所持有的,<例2>的变量a是由类所持有的。
由于类的回收比对象的回收更加麻烦,所以静态成员变量的生命周期普遍比对象的变量的生命周期长,所以需要注意静态变量a的初始化和释放工作。
2.软引用
Android 2.3之前软引用只有在内存不足的时候才会被GC回收.
软引用在强引用释放了对象的引用后,会继续持有对象的引用,直到内存不足后才会释放。所在再做一些内存紧张时可以停止的工作时,可以使用软引用进行相关操作。
例如,Android的图片加载框架,通过软引用维护一个集合,保存一些最近常用的图片缓存(内存缓存)。在内存紧张的时候,这个集合的中的软引用所持有的对象被回收,达到释放内存的效果。
<例3>
Object a = new Object();
Log.e("ref","a所引用的对象的内存地址:"+a.toString());
Reference<Object> ref = new SoftReference<>(a);
a = null;
System.gc();
if(ref.get() != null){
Log.e("ref","ref所引用的对象的内存地址:"+ref.get().toString());
}else{
Log.e("ref","aRef.get() 为空");
}
可以看到日志打印的结果:
a所引用的对象的内存地址:java.lang.Object@19579822 aRef.get() 为空
从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
LruCache本质上是通过一个LinkedHashMap来维护内存缓存的数据,采用的是Lru(最近最少使用)算法。
LruCache<String,Bitmap> bitmapLruCache = new LruCache<String,Bitmap>(
(int) (Runtime.getRuntime().maxMemory()/8/1024)){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount()/1024;
}
};
注意: 因为初始化传入的maxSize = Runtime.getRuntime().maxMemory()/8,单位为字节(B)。maxSize/1024即转化单位为KB.
为了保持一致,sizeof里面的value.getByteCount()单位(B)也需要转化为KB.
所以,在Android2.3之后,软引用其实也很容易被回收,并不一定是在内存不足的情况下才会被回收。
3.弱引用
GC只要发现只具有弱引用的对象,都会回收该对象的内存。
在Android的日常编码中,我们很多时候会使用到handler进行子线程与UI线程的通信。可是直接使用会如上图那样让我们使用静态类的模式,即:
<例4>
static class MyHandler extends Handler{
Reference<Activity> mAtyRef;
public MyHandler(Activity activity) {
this.mAtyRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(mAtyRef.get() != null){
//...
}
}
}
主要原因就在于直接使用匿名内部类默认会挟持当前activity的上下文对象。而如果持有该handler的子线程在进行耗时操作,那么如果activity意外关闭了,子线程依然在执行并调用了handler发送消息给Ui线程。那么很可能会导致程序抛出异常。
由于
- 子线程执行的不确定性
- handler本身可以发送延时信息(eg:mHandler.postDelayed(nRunnable,1000);)
所以在编码中需要假设当前activity == null 的情况下,代码的正确执行。
<例4>中用到弱引用持有当前的activity对象,那么当activity被finish后,持有activity对象的强引用会在接下来的一定时间内被释放。那么当子线程使用handler与UI线程交互时,因为activity已经不存在强引用了,所以mAtyRef.get()为空,以下代码不再执行。
注意:activity在finish后,对象不是立即被回收。
在类中使用弱引用持有对象,主要是为了使本类不会成为被持有对象被回收的限制,同时可以在此对象存在是进行一些操作。
如:<例4>中MyHandler类不会阻碍activity的回收,同时在activity未被回收的时候可以进行一些操作。
测试代码:
<例5>
Object a = new Object();
Object b = new Object();
Log.e("ref","a所引用的对象的内存地址:"+a.toString());
Log.e("ref","b所引用的对象的内存地址:"+b.toString());
Reference<Object> aRef = new WeakReference<>(a);
Reference<Object> bRef = new WeakReference<>(b);
a = null; //释放了a的强引用
System.gc();//注意System.gc()不是真正的执行GC,只是让JVM知道可以执行了.所以下面加个延时效果会更好。
if(aRef.get() != null){
Log.e("ref","aRef所引用的对象的内存地址:"+aRef.get().toString());
}else {
Log.e("ref","aRef.get() 为空");
}
if(bRef.get() != null){
Log.e("ref","bRef所引用的对象的内存地址:"+bRef.get().toString());
}else {
Log.e("ref","bRef.get() 为空");
}
运行结果:
a所引用的对象的内存地址:java.lang.Object@1cb54ea3 b所引用的对象的内存地址:java.lang.Object@2c3a55a0 aRef.get() 为空 bRef所引用的对象的内存地址:java.lang.Object@2c3a55a0
4.虚引用
<例6>
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> ref = new PhantomReference<>(a,queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
参考文章:
java中四种引用类型