前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java强引用、软引用、弱引用、虚引用以及FinalReference

java强引用、软引用、弱引用、虚引用以及FinalReference

作者头像
早安嵩骏
发布2020-08-11 16:19:37
1.2K0
发布2020-08-11 16:19:37
举报
文章被收录于专栏:程序猿人程序猿人

基于JDK1.8 rt.jar是java中的基础类库,在它的 java.lang.ref包下,有着一堆关于引用的类。软引用、弱引用以及虚引用的定义就在其中。另外还有一个FinalReference。

代码语言:javascript
复制
* ReferenceQueue//引用队列
* Reference(abstract)//抽象类
    * SoftReference//软引用
    * WeakReference//弱引用
    * PhantomReference//虚引用
    * FinalReference
        *Finalizer
*FinalizerHistogram

强引用

jdk1.8的包中并没有这个类,是普通类的引用。在Hotspot的垃圾回收过程中,采用可达性分析算法判定对象是否存活,可以到达GC Roots的对象是永远不会被当做垃圾回收的;

软引用(SoftReference)

软引用常常被用于实现一些高速且敏感的缓存,如Guava中的LocalCache中就是用了SoftReference来做缓存 它的特点是:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存

弱引用(WeakReference)

弱引用同样经常被用做缓存的实现,在JDK中比较常见的是维护一种非强制性的映射关系的实现,动态代理的缓存就是基于它实现的。 它的特点是:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。可见它相对于软引用来说,拥有更为短暂的生命周期。

虚引用(PhantomReference)

如果一个对象只被一个PhantomReference所引用,那等于没有任何引用。它随时都可以 回收。虚引用经常被用作 跟踪对象被垃圾回收的活动。

以上三种引用类型,均可以和ReferenceQueue引用队列一起使用,例如:

代码语言:javascript
复制
ReferenceQueue referenceQueue = new ReferenceQueue();//引用队列
Object obj = new Object();//强引用
WeakReference weakReference = new WeakReference(obj,referenceQueue);//初始化弱引用,PhantomReference只有这一个构造函数

一个对象,通过被“弱引用对象”引用,而改变了它的生命周期。可以方便它更快的被垃圾回收。试想在一个map构造的缓存系统中,缓存值是一直被map强引用着的,map值一直被使用,所有的缓存值也会一直得不到释放,这是很危险的,随时都会带来OOM的危险,这时,Reference就派上用场了:通过一个软引用或者虚引用隔离开map对象和缓存值的直接引用,变成map->reference->缓存值,一旦缓存值没有强引用,就可以很快被回收了。

具体看下如下实验(一种非强制性映射关系的简单实现):

代码语言:javascript
复制
public class Oil {

    private String name;

    public Oil(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Oil "+name+" finalize.");
    }

    @Override
    public String toString() {
        return "Oil "+name+" print!";
    }
}
代码语言:javascript
复制
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class ReferenceQueueTest {

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Oil> oilReferenceQueue = new ReferenceQueue<>();
        Map map = new HashMap();
        Oil oil = new Oil("花生油");
        Oil oil1 = new Oil("葵花籽油");
        WeakReference<Oil> oilWeakReference = new WeakReference<>(oil, oilReferenceQueue);
        WeakReference<Oil> oilWeakReference1 = new WeakReference<>(oil1);
        map.put(oilReferenceQueue,oilWeakReference1);
        while (true) {
            System.out.println(oilWeakReference);
            System.out.println(oilWeakReference1);
            System.out.println(oilWeakReference.get());
            System.out.println(oilWeakReference1.get());
            Reference<? extends Oil> reference = null;
            while ((reference = oilReferenceQueue.poll()) != null) {
                System.out.println("ReferenceQueue内:" + reference);
                map.remove(reference);
                return;
            }
            oil = null;
            System.out.println("=========gc===========");
            System.gc();//调用GC
            TimeUnit.SECONDS.sleep(10);
        }
    }
}

打印结果:

代码语言:javascript
复制
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" **-XX:+PrintGCDetails** "...
java.lang.ref.WeakReference@2503dbd3
java.lang.ref.WeakReference@4b67cf4d
Oil 花生油 print!
Oil 葵花籽油 print!
=========gc===========
[GC (System.gc()) [PSYoungGen: 3334K->744K(38400K)] 3334K->752K(125952K), 0.0008666 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->647K(87552K)] 752K->647K(125952K), [Metaspace: 3228K->3228K(1056768K)], 0.0049257 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Oil 花生油 finalize.
java.lang.ref.WeakReference@2503dbd3
java.lang.ref.WeakReference@4b67cf4d
null
Oil 葵花籽油 print!
ReferenceQueue内:java.lang.ref.WeakReference@2503dbd3
Heap
 PSYoungGen      total 38400K, used 3661K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
  eden space 33280K, 11% used [0x00000000d5e00000,0x00000000d6193500,0x00000000d7e80000)
  from space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
  to   space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
 ParOldGen       total 87552K, used 647K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
  object space 87552K, 0% used [0x0000000081a00000,0x0000000081aa1e00,0x0000000086f80000)
 Metaspace       used 3770K, capacity 4540K, committed 4864K, reserved 1056768K
  class space    used 416K, capacity 428K, committed 512K, reserved 1048576K

Process finished with exit code 0

那么,ReferenceQueue是做什么用呢?

如果缓存值被回收了,那么引用对象(也就是WeakReference或者SoftReference的对象)也就没用了,失去被应用对象的它们,会被放入ReferenceQueue中,方便更快回收。JDK中常见的回收代码如下:

代码语言:javascript
复制
   private void expungeStaleEntries() {
        CacheKey<K> cacheKey;
        while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {//从队列拉出没用的引用类型对象
            cacheKey.expungeFrom(map, reverseMap);//将它们从map中剔除,方便被垃圾回收
        }
    }

FinalReference

FinalReference和Finalizer以及常见的finnalize()方法应该放在一起说。

  1. FinalReference灰常简单(就一个传统的引用类都有的构造),我们看下他的子类:Finalizer。 这个类是和FinalReference一样,都是Package-private,只能在包内使用。 说明它是被jvm控制的。
  2. 那finnalize()是个怎么回事呢? java不同于C和C++,它的垃圾回收自然有垃圾回收器负责。 但也有比较特殊的情况,比方说你建立了一个连接,垃圾回收器只能释放掉那些经由new分配的内存区域,而对于建立的连接,则无所应对。 为了应对这种情况,java允许在类中定义一个名为finnalize()的方法。 它的工作原理是: 一旦垃圾回收器准备释放该对象占据的存储空间,会首先调用其finalize()方法,并且在下一次垃圾回收动作发生的时候,才会真正回收该对象占用的内存。 但请注意,finalize()并算不上java的“析构”。
  3. 它们的联系?
  • 虚拟机在类加载的时候会将实现了finalize()方法的类成为“f类”。
  • JVM在新建F类的对象时,调用Finalizer.register方法。 register方法,会新建一个指向该对象的强引用,这样它就不会被垃圾回收了。
  • 在Finalizer类中,有一个static代码块:
代码语言:javascript
复制
static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
private static class FinalizerThread extends Thread {
    private volatile boolean running;
    FinalizerThread(ThreadGroup g) {
        super(g, "Finalizer");
    }
    public void run() {
        // in case of recursive call to run()
        if (running)
            return;

        // Finalizer thread starts before System.initializeSystemClass
        // is called.  Wait until JavaLangAccess is available
        while (!VM.isBooted()) {
            // delay until VM completes initialization
            try {
                VM.awaitBooted();
            } catch (InterruptedException x) {
                // ignore and continue
            }
        }
        final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
        running = true;
        for (;;) {
            try {
                Finalizer f = (Finalizer)queue.remove();//从引用队列取出Finalizer类
                f.runFinalizer(jla);// 从Finalizer的引用中剥离对象,并且调用该对象的finalize()方法
            } catch (InterruptedException x) {
                // ignore and continue
            }
        }
    }
}

它实现了一个守护线程,如注释所示,它将整个处理finalize的处理过程串联起来了。但是由于优先级并不是最高,所以当CPU资源不充足的时候,它的执行就会收到影响,也就不能保证你的连接一定被释放了。在《java编程思想》中,作者曾经写到:如果希望进行除释放存储空间之外的清理工作,还是得明确调用某个恰当的Java方法。这就等同与使用析构函数了,只是没有它方便。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 强引用
  • 软引用(SoftReference)
  • 弱引用(WeakReference)
  • 虚引用(PhantomReference)
    • 具体看下如下实验(一种非强制性映射关系的简单实现):
      • 那么,ReferenceQueue是做什么用呢?
      • FinalReference
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档