弱引用引发的想法
Java虚拟机中,存在四种引用:强引用、软引用、弱引用、虚引用。
弱引用
弱引用是这样的一种引用,仅存在弱引用的对象,只能存活到下一次垃圾回收之前。
String的弱引用
对于上述描述,需要重点强调仅存在弱引用,对于虚拟机中隐含的一些默认引用,我们需要注意。比如有下面的代码:
// 添加虚拟机参数,可以显示gc信息 -verbose:gc
// 其实指向的这个字符串存在于字符串常量池里面,因此无法回收
WeakReference<String> stringWeakReference = new WeakReference<String>("我依旧存在");
System.out.println("stringWeakReference before gc:" + stringWeakReference.get());
Runtime.getRuntime().gc();
Thread.sleep(2000);
System.out.println("stringWeakReference after gc:" + stringWeakReference.get());
假若2000毫秒足够发生一次GC,那么,对于上述情况,两次的输出是:
stringWeakRef before gc:我依旧存在
[GC (System.gc()) 3328K->816K(125952K), 0.0044850 secs]
[Full GC (System.gc()) 816K->602K(125952K), 0.0205189 secs]
stringWeakRef after gc:我依旧存在
可以看到,即使发生了GC之后,该弱引用引用的对象依旧没有被回收,这是因为
WeakReference<String> stringWeakReference = new WeakReference<String>("我依旧存在");
这段代码中,stringWeakReference所引用的对象其实是常量池中的一个字符串对象【“我依旧存在”】,除了弱引用之外,其实还有常量池中的引用,因此无法回收,也就产生了上述输出的情况。为了可以顺利回收该对象,我们可以借助该字符串常量重新创建一个字符串,使软引用指向新建的对象,再进行测试,代码如下:
// 若要可以回收,可以使用以下方式新new数据
WeakReference<String> newStringWeakReference = new WeakReference<String>(new String("这个活不久了"));
System.out.println("newStringWeakReference before gc:" + newStringWeakReference.get());
Runtime.getRuntime().gc();
Thread.sleep(2000);
System.out.println("newStringWeakReference after gc:" + newStringWeakReference.get());
同样假设2000毫秒足够发生一次GC,那么上述代码的输出情况如下:
newStringWeakReference before gc:这个活不久了
[GC (System.gc()) 2002K->767K(125952K), 0.0015831 secs]
[Full GC (System.gc()) 767K->598K(125952K), 0.0252165 secs]
newStringWeakReference after gc:null
可以看到,对于新new的字符串,由于堆上仅存在一个弱引用,在进行一次GC后,该String对象被回收,导致第二次输出为null。
基本类型的弱引用
其实,根据上面的情况,很容易联想到的是Integer等一些包装类内嵌的缓存常量池,因此可以有以下代码:
Integer integer = 4;
WeakReference<Integer> integerWeakReference = new WeakReference<>(integer);
// 修改Integer的引用为null
integer = null;
System.out.println("[integerWeakReference.get()]在垃圾回收前:" + integerWeakReference.get());
System.gc();
Thread.sleep(2000);
System.out.println("[integerWeakReference.get()]在垃圾回收后:" + integerWeakReference.get());
会有如下的输出结果:
[integerWeakReference.get()]在垃圾回收前:4
[GC (System.gc()) 3329K->839K(125952K), 0.0039346 secs]
[Full GC (System.gc()) 839K->671K(125952K), 0.0245653 secs]
[integerWeakReference.get()]在垃圾回收后:4
有自动装箱的经验小伙伴应该很容易理解原因,在自动装箱的时候,调用的是Integer.valueOf()
方法,该方法实现如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
它会先在预先设定的缓存池里面去查询,如果有的话,就返回缓存里面的引用。对于前面的例子,因为这个缓存池的范围为[-128,127],自动装箱的时候,引用了缓存池里的对象,即使发生了GC,该缓存池里的对象也不只存在integerWeakReference
一个弱引用。对于上述猜想,可以增加验证的例子如下:
Integer integer = 128;
WeakReference<Integer> integerWeakReference = new WeakReference<>(integer);
// 修改Integer的引用为null
integer = null;
System.out.println("[integerWeakReference.get()]在垃圾回收前:" + integerWeakReference.get());
System.gc();
Thread.sleep(2000);
System.out.println("[integerWeakReference.get()]在垃圾回收后:" + integerWeakReference.get());
上述例子会有如下输出结果:
[integerWeakReference.get()]在垃圾回收前:128
[GC (System.gc()) 3329K->807K(125952K), 0.0039630 secs]
[Full GC (System.gc()) 807K->671K(125952K), 0.0292003 secs]
[integerWeakReference.get()]在垃圾回收后:null
借助上面所描述的,可能更常见的是基本类型的自动装箱,如以下代码:
public static void integerWeakReference() throws InterruptedException {
int integer15 = 15;
WeakReference<Integer> integer15WeakReference = new WeakReference<>(integer15);
System.out.println("[integer15WeakReference.get()]垃圾回收前:" + integer15WeakReference.get());
System.gc();
Thread.sleep(2000);
System.out.println("[integer15WeakReference.get()]垃圾回收后:" + integer15WeakReference.get());
int integer179 = 179;
WeakReference<Integer> integer179WeakReference = new WeakReference<>(integer179);
System.out.println("[integer179WeakReference.get()]垃圾回收前:" + integer179WeakReference.get());
System.gc();
Thread.sleep(2000);
System.out.println("[integer179WeakReference.get()]垃圾回收后:" + integer179WeakReference.get());
}
上述代码,有兴趣的小伙伴可以自己思考或者动手实践看看输出结果。
结论
该问题虽然实践中可能不会产生什么大问题,但是该想法可以作为日常面试考察,涉及虚拟机引用、字符串常量池、自动装箱拆箱机制