强引用、软引用、弱引用和虚引用
四种应用区别在于体现对象不同的可达性状态,进而影响到GC。
一、可达性状态-----JVM的可达性分析算法
JVM中对象的存活,取决于GC root节点向下搜索,能否经过该节点。搜索的路径被称为引用链,该链上的节点都是存活的,反之则将会被JVM判定为可回收对象。
GC root节点有哪些?
- 虚拟机栈(栈桢中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)的引用的对象
二、强引用
强引用所指向的对象,在发生OOM时仍不会被垃圾回收。只有在超过引用作用域(生命周期)或被显式地置位null,才会被标记为可回收对象(但不一定会被回收,具体取决于JVM的分代回收策略)。
三、软引用-----SoftReference
软引用是在发生OOM前,JVM才会试图回收的引用对象,通常被用来引用内存敏感的缓存对象,保证使用缓存的同时不会因此耗尽内存。
我们可以将软引用联合引用队列使用,使用poll方法来判断该队列中是否存在未被回收的应用。
- 利用软引用处理图片占用内存大的情况
public static void imgSoftReference(int img, ImageView imageView) {
if (imageView != null) {
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(),img);
Drawable drawable = new BitmapDrawable(bitmap);
SoftReference<Drawable> drawableSoftReference = new SoftReference<Drawable>(drawable);
if(drawableSoftReference != null) {
view.setBackground(drawableSoftReference.get());
}else{
view.setImageResources(img);
}
}
}
通过软引用的get()方法,取得drawable对象实例的强引用,发现对象被未回收。在GC在内存充足的情况下,不会回收软引用对象,为ImageView设置图片;发生gc时,drawables.get()不再返回Drawable对象,而是返回null,此时View设置默认图,避免crash。
四、弱引用-----WeakReference
弱引用不会使对象豁免GC,会生存到下次Gc前。弱引用仅为我们提供一种访问弱引用状态对象的途径。通过弱引用可以构建一种没有特定约束的关系,同样是一种实现缓存的方式-----如果试图获取时该对象依然存在,就使用它,否则重新实例化。
- 使用弱引用和ReferenceQueue监控并反向操作的案例:
//描述一种强key关系的处理,当value值被回收之后,我们可以通过反向引用将key从map中移除的做法,即通过在weakReference中加入其所引用的key值,以获取key信息,再反向移除map信息
class WeakR extends WeakReference<byte[]> {
private Object key;
WeakR(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) {
super(referent, q);
this.key = key;
}
}
//使用普通的hashMap,将weakR作为value进行存储
final Map<Object, WeakR> hashMap = new HashMap<>();
for(int i = 0;i < 10000;i++) {
byte[] bytesKey = new byte[_1M];
byte[] bytesValue = new byte[_1M];
hashMap.put(bytesKey, new WeakR(bytesKey, bytesValue, referenceQueue));
}
//监控队列并对获取的WeakR对象进行额外的处理:
//获取反向引用的key值(此时value已经不存在了),因为kv映射已没有意义,将其从map中移除,避免出现无用的kv映射。
int cnt = 0;
WeakR k;
while((k = (WeakR) referenceQueue.remove()) != null) {
System.out.println((cnt++) + "回收了:" + k);
//触发反向hash remove
hashMap.remove(k.key);
//额外对key对象作其它处理,比如关闭流,通知操作等
}
- 对handler内存泄漏的处理
直接new一个handler对象为什么会造成内存泄漏?
这是由于android的特殊机制造成的:当一个android主线程被创建的时候,同时会有一个Looper对象被创建,改Looper对象会实现一个MessageQueue(消息队列)。每当我们通过handler将一个msg放入消息队列时,这个msg就会持有一个handler对象的引用。因此当Activity被结束后,这个msg在被取出来之前会继续存活,由于msg持有handler的引用,而handler在Activity中创建,会持有Activity的引用。如果Activity被结束了,msg无法被处理,从而导致永久持有handler对象,handler永久持有Activity对象,Activity对象不能够被gc回收,因而出现内存泄漏。
弱引用解决办法
private MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler{
WeakReference<FirstActivity> weakReference;
MyHandler(FirstActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
}
}
}
在java中所有非静态的对象都会持有当前类的强引用,而静态对象则只会持有当前类的弱引用。声明为静态后,handler将会持有一个Activity的弱引用,而弱引用会很容易被gc回收,这样就能解决Activity结束后,gc却无法回收的情况。
五、虚引用------PhantonReference
虚引用对应的实体类为PhantonReference。虚引用不论所引用的对象是不是null,不论内存空间是否充足,都会被垃圾回收器回收。
引用队列-----ReferenceQueue
在使用各种引用对象时,可以选择关联引用队列,然后就可以从队列中获取引用。特别是虚引用,其get方法只返回null,所有要使用虚引用,一般都要指定引用队列。
like:
public static void testReferenceQueue(){
ReferenceQueue referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(object,referenceQueue);
object = null;
System.gc();
try {
//remove是一个阻塞方法,可以指定其超时时间
Reference <Object> ref = referenceQueue.remove(1000L);
if (ref != null) {
//do something
}
} catch (InterruptedException e) {
//handle
}
}
在创建这种引用并关联对象时,使用引用队列可以将引用与改队列关联。当垃圾回收器回收一个对象时,如果发现其拥有虚引用,就会在GC前将其加入到与之关联的引用队列中,可以通过判断引用队列是否已加入了虚引用,确定被引用对象是否要被GC。即我们可以通过跟踪对象被垃圾回收器回收的活动,收到一条系统通知,从而做出额外处理。
//通过往map中放入10000个对象,每个对象大小为1M字节数组。使用引用队列监控被放入的key的回收情况
Object value = new Object();
Map<Object, Object> map = new HashMap<>();
for(int i = 0;i < 10000;i++) {
byte[] bytes = new byte[_1M];
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
map.put(weakReference, value);
}
System.out.println("map.size->" + map.size());
//使用weakReference对象,即当值不再被引用时,相应的数据被回收。另外使用一个线程不断地从队列中获取被gc的数据
Thread thread = new Thread(() -> {
try {
int cnt = 0;
WeakReference<byte[]> k;
while((k = (WeakReference) referenceQueue.remove()) != null) {
System.out.println((cnt++) + "回收了:" + k);
}
} catch(InterruptedException e) {
//结束循环
}
});
thread.setDaemon(true);
thread.start();