> Java引用:强、软、弱、虚 - Yuyy
Yuyy
Yuyy
Java引用:强、软、弱、虚

Java引用分为强引用、软引用、弱引用、虚引用

一、强引用

public class M {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("m.finalize");
        super.finalize();
    }
}

public static void main(String[] args) throws IOException {
        M m = new M();
        System.out.println(m);
        m=null;
        System.gc();
        System.out.println(m);
        System.in.read(); //为了阻塞main线程,让gc工作线程运行(试了下,不用这个也能gc,但后面还有代码的话,就不会立即gc了。说明调用System.gc();只是通知jvm执行gc,至于什么时候执行gc,还是得看jvm)
    }
  • 输出
com.yuyy.java.training.base.gc.bean.M@2ff4acd0
null
m.finalize

二、软引用

public static void main(String[] args) {

        java.lang.ref.SoftReference<byte[]> m=new java.lang.ref.SoftReference<>(new byte[1024*1024*10]);//m和sr是强引用关系,但sr和byte[]是弱引用关系
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(1_000);//为gc的工作线程提供cpu运行时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(m.get());//堆空间够时,及时gc,软引用的对象也不会被回收
        byte[] bytes = new byte[1024 * 1024 * 11];//撑爆堆
        System.out.println(m.get());//堆空间不够了,执行gc,回收软引用的对象
    }
  • Jvm参数:-Xmx20m
  • 输出
[B@2ff4acd0
[B@2ff4acd0
null
  • 适用场景:缓存(现在有很多缓存框架,相比软引用,有着更多的策略)

三、弱引用

  1. 调用System.gc();
public static void main(String[] args) {
        java.lang.ref.WeakReference<M> weakRef=new java.lang.ref.WeakReference<M>(new M());
        System.out.println(weakRef.get());
        System.gc();
        System.out.println(weakRef.get());
    }
  • 输出
com.yuyy.java.training.base.gc.bean.M@2ff4acd0
null
m.finalize
  • 注意:我们刚调用System.gc();完,gc并未执行(因为finalize方法没执行),软引用的对象已经获取不到了,但还未被回收
  1. 利用VisualVm手动gc(使用的是idea插件:VisualVM launcher),可以看到弱引用关联的对象被回收
public void test1() {
        java.lang.ref.WeakReference<byte[]> weakRef=new java.lang.ref.WeakReference<byte[]>(new byte[1024*1024*50]);
        System.out.println(weakRef.get());
        try {
            Thread.sleep(20_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(weakRef.get());
    }

http://bed.yuyy.info/image-20201216232651221.png

  • 输出

    [B@16b3fc9e
    null
  1. 弱引用关联的对象持有强引用
  • 执行gc时,会回收所有只被弱引用关联的对象,如果该对象持有强引用,是无法回收的
public void test(){
        M[] ms = new M[1000];
        for (int i = 0; i < 1000; i++) {
            ms[i]=new M();
        }
        java.lang.ref.WeakReference<M[]> weakRef=new java.lang.ref.WeakReference<M[]>(ms);
        System.out.println(weakRef.get());
        System.gc();
        System.out.println(weakRef.get());
    }
  • 输出
[Lcom.yuyy.java.training.base.gc.bean.M;@16b3fc9e
[Lcom.yuyy.java.training.base.gc.bean.M;@16b3fc9e
  • 执行gc后,weakRef关联的M[]还在

  • 我们看看弱引用的实战,threadlocal的内部类的内部类Entry,就是继承的弱引用。更多关于ThreadLocal的知识,请看我的这篇文章

    http://bed.yuyy.info/image-20201219111219225.png

四、虚引用

  • gc时,就会回收,与此同时,会将被回收对象的引用添加到引用队列。

  • 可对该引用队列进行监控,如果有引用进来了,说明有对象被回收了

  • 类似钩子函数,起到通知的作用

  • 收到通知,可以做一些事。很多框架(例如Netty)使用了NIO,其中有个功能叫做零拷贝:数据经过网卡,到内存里的数据缓冲区,jvm对其操作时,可直接操作堆外内存。就不用将数据拷贝到堆内。相应的,对象被回收时,需要将堆外内存里的相关数据进行清除。这个场景,就可以用虚引用。

    • 分配堆外内存
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    • 在对象被回收时,清理相应的堆外内存,使用的就是PhantomReference的子类
    public class Cleaner extends PhantomReference {
        private static final ReferenceQueue dummyQueue = new ReferenceQueue();
    
    
  • 虚引用也称为幽灵引用或幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾收集器回收后收到一个系统通知。例如回收堆外内存

  • 为什么不用finalize方法来执行上诉需求?

    • 从Java源码Finalizer.class中得知:在源码中,执行finalize()方法是通过开启一个低优先级的线程来执行的,而finalize()方法在执行过程中的任何异常都会被catch,然后被忽略,因而无法保证finalize方法里的任务会被执行完。
    • 由于执行finalize()的是一个低优先级的线程,既然是一个新的线程,虽然优先级低了点,但也是和垃圾收集器并发执行的,所以垃圾收集器没必要等这个低优先级的线程执行完才继续执行。也就是说,有可能会出现对象被回收之后,那个低优先级的线程才执行finalize()方法。
  • 对于软引用和弱引用,当执行第一次垃圾回收时,就会将软引用或弱引用对象添加到其关联的引用队列中,然后其finalize函数才会被执行(如果没覆写则不会被执行);而对于虚引用,如果被引用对象没有覆写finalize方法,则是在第一垃圾回收将该类销毁之后,才会将虚拟引用对象添加到引用队列,如果被引用对象覆写了finalize方法,则是当执行完第二次垃圾回收之后,才会将虚引用对象添加到其关联的引用队列。

  • 当执行第一次垃圾回收时,发现该对象具有finalize方法且没被执行过,因而这个对象不会被回收,并调用该对象的finalize()函数。当执行第二次垃圾回收时,发现该类虽然覆写了finalize方法,但已经执行过了,就可以直接将该类回收。以上是覆写了finalize函数的类的回收过程。对于没有覆写finalize函数的类或者已经执行过一次finalize函数的类,在垃圾回收时更简单,直接被回收即可。

  • public class PhantomReference {
    
        private static final List<Object> LIST = new LinkedList<>();
    
        private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
    
        public static void main(String[] args) {
            java.lang.ref.PhantomReference<M> phantomReference = new java.lang.ref.PhantomReference<>(new M(), QUEUE);
            System.out.println(phantomReference.get());
            new Thread(() -> {
                while (true) {
                    LIST.add(new byte[1024 * 1024]);
                    try {
                        Thread.sleep(1_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(phantomReference.get());// 这行代码如果被注释,将不会打印下面的输出内容,目前没搞明白
                }
            }).start();
            new Thread(() -> {
                while (true) {
                    Reference<? extends M> ref = QUEUE.poll();
                    if (ref != null) {
                        System.out.println("虚引用关联的对象已被回收,可进行其他操作");
                    }
                }
            }).start();
        }
    }
    • 输出
    null
    null
    null
    m.finalize
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    虚引用关联的对象已被回收,可进行其他操作
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at com.yuyy.java.training.base.gc.PhantomReference.lambda$main$0(PhantomReference.java:35)
        at com.yuyy.java.training.base.gc.PhantomReference$$Lambda$1/1198108795.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    • 上面有一行注释:目前没搞懂。通过打印GC日志,easyGC分析,有无那行代码,GC的情况是一样的,对象应该是被回收了的,但虚引用的引用队列并未包含那个引用。监控引用队列的那个线程也是正常工作的,甚至在持续生产对象那个线程OOM时,监控线程也未受影响,一直正常运行着。

      • 打印GC的jvm参数
      -XX:+PrintGCDateStamps
      -XX:+PrintGCDetails
      -Xloggc:gc.log

    发表评论

    textsms
    account_circle
    email

    Yuyy

    Java引用:强、软、弱、虚
    Java引用分为强引用、软引用、弱引用、虚引用 一、强引用 public class M { @Override protected void finalize() throws Throwable { System.out.println("m.final…
    扫描二维码继续阅读
    2020-12-19
    友情链接
    近期文章