欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

6、强引用,软引用,弱引用和虚引用

程序员文章站 2022-04-06 15:06:13
...

上一篇: GC Roots的介绍  https://blog.csdn.net/chenjianhuideyueding/article/details/110788112

 

java中的引用可以分成四类,分别为:强引用,软引用,弱引用和虚引用

 

6.1、强引用

对于强引用,是我们最常见,比如直接创建一个对象:Obeject obj = new Object();那么obj就是一个强引用。在当前栈帧有效的作用域内,是永远不会被回收的。

 

6.2、软引用

 

软引用是指被SoftReference类实现的引用。它的特征是当系统有足够的内存的时候,它能够存活;当系统内存不足,垃圾回收的动作到来时,它会被回收释放内存。

代码:

package cn.yishijie.jol;

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class SoftTest {

    public static void main(String[] args) {
        SoftReference<String> softStr = new SoftReference<>(new String("jeff.chan"));

        System.out.println("before gc -> " + softStr.get());

        System.gc();

        System.out.println("after gc -> " + softStr.get());
    }
}

执行的结果:加上虚拟机参数: -XX:+PrintGCDetails

before gc -> jeff.chan
[GC (System.gc()) [PSYoungGen: 5350K->1047K(38400K)] 5350K->1055K(125952K), 0.0007776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 1047K->0K(38400K)] [ParOldGen: 8K->937K(87552K)] 1055K->937K(125952K), [Metaspace: 2988K->2988K(1056768K)], 0.0035376 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
after gc -> jeff.chan

发现没有被回收。这是因为内存足够。

package cn.yishijie.jol;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class SoftTest {

    public static void main(String[] args) {
        SoftReference<byte[]> softStr = new SoftReference<>(new byte[1024*1024]);

        System.out.println("before gc -> " + softStr.get());
        List<byte[]> list = new ArrayList<>();
        int i = 0;
        while (true){
            list.add(new byte[1024*1024]);
            System.out.println(++i+"allow memory -> " + softStr.get());
        }
    }
}

加入jvm的参数: -XX:+PrintGCDetails -Xmx10m -Xms10m 执行上述的代码

before gc -> [[email protected]
[1]allow memory -> [[email protected]
[2]allow memory -> [[email protected]
[3]allow memory -> [[email protected]
[4]allow memory -> [[email protected]
[5]allow memory -> [[email protected]
[GC (Allocation Failure) [PSYoungGen: 1667K->487K(2560K)] 8203K->7169K(9728K), 0.0006804 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[6]allow memory -> [[email protected]
[GC (Allocation Failure) --[PSYoungGen: 1551K->1551K(2560K)] 8233K->8249K(9728K), 0.0004440 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1551K->1497K(2560K)] [ParOldGen: 6698K->6650K(7168K)] 8249K->8147K(9728K), [Metaspace: 3249K->3249K(1056768K)], 0.0037707 secs] [Times: user=0.08 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 1497K->1497K(2560K)] 8147K->8147K(9728K), 0.0005219 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1497K->0K(2560K)] [ParOldGen: 6650K->7094K(7168K)] 8147K->7094K(9728K), [Metaspace: 3249K->3249K(1056768K)], 0.0039966 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[7]allow memory -> null
[Full GC (Ergonomics) [PSYoungGen: 1064K->1024K(2560K)] [ParOldGen: 7094K->7062K(7168K)] 8159K->8086K(9728K), [Metaspace: 3250K->3250K(1056768K)], 0.0054518 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1024K->1024K(2560K)] [ParOldGen: 7062K->7062K(7168K)] 8086K->8086K(9728K), [Metaspace: 3250K->3250K(1056768K)], 0.0025587 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at cn.yishijie.jol.SoftTest.main(SoftTest.java:16)

可以发现第七次的时候; [7]allow memory -> null,已经把上面的软引用的对象给回收掉了。紧接着后续就抛出OOM异常了,因为内存不够了。所以软引用是在内存告急时,才会被回收释放。

使用场景:在Hibernate中就有用到这个软引用,就是当作缓存,当内存足够的时候,数据存在缓存中,可以直接提交查询的性能,当内存不够时,又可以释放内存,防止OOM的发生。举个例子:比如以前我做过一个城市列表的需求,第一次的时候,通过查询数据库放入到软引用的内存中,然后后续需要用到这个城市列表的时候,直接从缓存中拿,而不用穿透到数据库中去,提供了性能。但是当内存告急的时候,那么就会释放这部分内存。缓解内存的压力。内存中没有这些城市的数据,不会引起服务异常,只不过是,下一次需要数据的时候,就需要穿透数据库拿数据而已。

 

6.3、弱引用

弱引用是指被WeakReference实现的引用。它只能存活到下一次垃圾回收发生之前。如果进行垃圾回收,那么一定会被回收。

package cn.yishijie.jol;

import java.lang.ref.WeakReference;
public class WeakTest {

    public static void main(String[] args) {
        WeakReference<String> weakStr = new WeakReference<>(new String("jeff.chan"));

        System.out.println("before gc -> "+weakStr.get());

        System.gc();

        System.out.println("after gc -> " + weakStr.get());
    }
}

返回结果:加上虚拟机参数: -XX:+PrintGCDetails

before gc -> jeff.chan
[GC (System.gc()) [PSYoungGen: 5350K->1015K(38400K)] 5350K->1023K(125952K), 0.0165296 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
[Full GC (System.gc()) [PSYoungGen: 1015K->0K(38400K)] [ParOldGen: 8K->944K(87552K)] 1023K->944K(125952K), [Metaspace: 3102K->3102K(1056768K)], 0.0037297 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
after gc -> null

1、ThreadLocal里面就是使用了弱引用减少内存泄漏的发生。但是我觉得这个使用场景,并不怎么好。我提出了以下的疑问?

因为ThreadLocal我看到很多人使用把它当作一个全局的静态变量,一般来说类被加载进来就会有静态变量。而静态变量要被回收的话,条件是很苛刻,基本是不被回收的,那么就是说,下一次垃圾回收到来之前,根本就无法回收掉被当作弱引用创建出来的ThreadLocal,因为它有一个强引用引用着。按照这种场景的话,弱引用感觉什么用都没有。
​
就算有使用场景,ThreadLocal的强引用会被回收,那么对于存活的线程来说,只不过是ThreaLocalMap里面key引用着ThreadLocal这个实例,况且这个对象并不怎么占内存。而且也是推荐使用完ThreadLocal后,要手动去remove掉。那么感觉弱引用也没啥用。
​
ThreadLocal就算弱引用被回收了。那么这个ThreadLocal不使用的话,对应的value,也是被存活的线程引用的,也并不会回收掉。也会造成内存泄漏,最终也是要手动去remove掉,或者是再次调用set或者get的方法。
​
难道就为了弱引用被垃圾回收后(看使用场景,经常是通过全局的静态变量,回收的概率又不太高),当再使用set或者get的方法能够主动清除value这个功能,就引入了这个设计?

2、当作缓存使用,在下一次gc之前可以拿到,gc之后就从数据库中拿数据。但是感觉SoftReference用作缓存更适合些。

 

6.4、虚引用

虚引用时指被PhantomReference类实现的引用,无法通过虚引用来获取到一个对象实例。它被用来跟踪对象引用被加入到队列的时刻。所以它的使用是需要和队列一起使用的。其实上述的软引用和弱引用也是可以搭配队列使用的。但是虚引用必须搭配队列使用。

package cn.yishijie.jol;

import cn.yishijie.config.Person;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceTest {
    public static void main(String[] args) throws Exception{
        ReferenceQueue<Person> queue = new ReferenceQueue<>();
        PhantomReference<Person> reference = new PhantomReference<>(new Person(23,"jeff.chan"),queue);
        System.out.println("before gc->"+reference.get());
        System.out.println("before gc->"+queue.poll());
        System.gc();
        System.out.println("after gc->"+reference.get());
        System.out.println("after gc->"+queue.poll());
    }
}

返回结果:加上虚拟机参数:  -XX:+PrintGCDetails

before gc->null
before gc->null
[GC (System.gc()) [PSYoungGen: 5350K->1031K(38400K)] 5350K->1039K(125952K), 0.0013433 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 1031K->0K(38400K)] [ParOldGen: 8K->979K(87552K)] 1039K->979K(125952K), [Metaspace: 3249K->3249K(1056768K)], 0.0036022 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
after gc->null
after gc->[email protected]

可以发现,gc之后,讲虚引用对象存入到队列中,那么就可以对这个对象做一些操作,很多参考资料都说来释放资源啥的,不过我在想不是可以finally里释放资源啥的吗,所以使用场景上,还是不太理解的。

 

相关标签: jvm