【Java虚拟机】弱引用引发的一些思考

弱引用引发的想法

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());
    }

上述代码,有兴趣的小伙伴可以自己思考或者动手实践看看输出结果。

结论

该问题虽然实践中可能不会产生什么大问题,但是该想法可以作为日常面试考察,涉及虚拟机引用、字符串常量池、自动装箱拆箱机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值