Java幽灵引用的作用

垃圾收集过程中,对象的可触及状态改变的时候,可以把引用对象和引用队列关联起来【这里说的关联,是说垃圾收集器会把要回收的对象添加到引用队列ReferenceQueue】,这样在可触及性发生变化的时候得到“通知”。

当垃圾收集器对加入队列的对象改变可触及性的时候,就可以收到异步通知了。

看下面的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

package static_;

 

import java.lang.ref.PhantomReference;

import java.lang.ref.Reference;

import java.lang.ref.ReferenceQueue;

import java.lang.reflect.Field;

 

public class Test {

    public static boolean isRun = true;

 

    @SuppressWarnings("static-access")

    public static void main(String[] args) throws Exception {

        String abc = new String("abc");

        System.out.println(abc.getClass() + "@" + abc.hashCode());

        final ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();

        new Thread() {

            public void run() {

                while (isRun) {

                    Object obj = referenceQueue.poll();

                    if (obj != null) {

                        try {

                            Field rereferent = Reference.class

                                    .getDeclaredField("referent");

                            rereferent.setAccessible(true);

                            Object result = rereferent.get(obj);

                            System.out.println("gc will collect:"

                                    + result.getClass() + "@"

                                    + result.hashCode() + "\t"

                                    + (String) result);

                        } catch (Exception e) {

                            e.printStackTrace();

                        }

                    }

                }

            }

        }.start();

        PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,

                referenceQueue);

        abc = null;

        Thread.currentThread().sleep(3000);

        System.gc();

        Thread.currentThread().sleep(3000);

        isRun = false;

    }

}

我们用一个线程检测referenceQueue里面是不是有内容,如果有内容,打印出来queue里面的内容。

从这个例子中,我们可以看出来,虚引用的作用是,我们可以声明虚引用来引用我们感兴趣的对象,在gc要回收的时候,gc收集器会把这个对象添加到referenceQueue,这样我们如果检测到referenceQueue中有我们感兴趣的对象的时候,说明gc将要回收这个对象了。此时我们可以在gc回收之前做一些其他事情,比如记录些日志什么的。

———————————————-分割—————————————————-

感谢蓝大牛分享下面的例子。

在java中,finalize函数本来是设计用来在对象被回收的时候来做一些操作的(类似C++的析构函数)。但是对象被GC什么时候回收的时间,却是不固定的,这样finalize函数很尴尬。虚引用可以用来解决这个问题。

在创建虚引用的时候必须传入一个引用队列。在一个对象的finalize函数被调用之后,这个对象的幽灵引用会被加入到引用队列中。通过检查队列的内容就知道对象是不是要准备被回收了。

幽灵引用的使用并不多见,主要是实现细粒度的内存控制。比如下面代码实现一个缓存。程序在确认原来的对象要被回收之后,才申请内存创建新的缓存。

在上面的代码中,每次申请新的缓存的时候,都要确保之前的字节数组被成功回收。引用队列的remove方法会阻塞直到虚引用被加入到引用队列中。【只有对象在内存中被移除之后才会进入引用队列中】【?这里有点不太确定。后续补发】

不过注意,这种方式可能会导致gc次数过多,程序吞吐量下降。

 

Java 中的 Reference

 在 jdk 1.2 及其以后,引入了强引用、软引用、弱引用、虚引用这四个概念。网上很多关于这四个概念的解释,但大多是概念性的泛泛而谈,今天我结合着代码分析了一下,首先我们先来看定义与大概解释(引用类型在包 java.lang.ref 里)。

  1、强引用(StrongReference)

    强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型。举个例子来说:
    Object obj = new Object();
    这里的obj引用便是一个强引用,不会被GC回收。

  2、软引用(SoftReference)

    软引用在JVM报告内存不足的时候才会被GC回收,否则不会回收,正是由于这种特性软引用在caching和pooling中用处广泛。软引用的用法:

1

2

3

4

Object obj = new Object();

SoftReference<Object> softRef = new SoftReference(obj);

// 使用 softRef.get() 获取软引用所引用的对象

Object objg = softRef.get();

  3、弱引用(WeakReference)

    当GC一但发现了弱引用对象,将会释放WeakReference所引用的对象。弱引用使用方法与软引用类似,但回收策略不同。

  4、虚引用(PhantomReference)

    当GC一但发现了虚引用对象,将会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference所指向的对象并没有被GC回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。虚引用的用法:

1

2

3

4

5

6

7

8

9

Object obj = new Object();

ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue);

// 调用phanRef.get()不管在什么情况下会一直返回null

Object objg = phanRef.get();

// 如果obj被置为null,当GC发现了虚引用,GC会将phanRef插入进我们之前创建时传入的refQueue队列

// 注意,此时phanRef所引用的obj对象,并没有被GC回收,在我们显式地调用refQueue.poll返回phanRef之后

// 当GC第二次发现虚引用,而此时JVM将phanRef插入到refQueue会插入失败,此时GC才会对obj进行回收

Reference<? extends Object> phanRefP = refQueue.poll();

看了简单的定义之后,我们结合着代码来测试一下,强引用就不用说了,软引用的描述也很清楚,关键是 “弱引用” 与 “虚引用”。

弱引用

1

2

3

4

5

6

7

8

9

10

11

public static void main(String[] args) throws InterruptedException {

    Object obj = new Object();

    ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

    WeakReference<Object> weakRef = new WeakReference<Object>(obj, refQueue);

    System.out.println(weakRef.get());

    System.out.println(refQueue.poll());

    obj = null;

    System.gc();

    System.out.println(weakRef.get());

    System.out.println(refQueue.poll());

}

由于System.gc()是告诉JVM这是一个执行GC的好时机,但具体执不执行由JVM决定,因此当JVM决定执行GC,得到的结果便是(事实上这段代码一般都会执行GC):

  java.lang.Object@de6ced
  null
  null
  java.lang.ref.WeakReference@1fb8ee3

从执行结果得知,通过调用weakRef.get()我们得到了obj对象,由于没有执行GC,因此refQueue.poll()返回的null,当我们把obj = null;此时没有引用指向堆中的obj对象了,这里JVM执行了一次GC,我们通过weakRef.get()发现返回了null,而refQueue.poll()返回了WeakReference对象,因此JVM在对obj进行了回收之后,才将weakRef插入到refQueue队列中。

虚引用

1

2

3

4

5

6

7

8

9

10

11

public static void main(String[] args) throws InterruptedException {

    Object obj = new Object();

    ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

    PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue);

    System.out.println(phanRef.get());

    System.out.println(refQueue.poll());

    obj = null;

    System.gc();

    System.out.println(phanRef.get());

    System.out.println(refQueue.poll());

}

同样,当JVM执行了GC,得到的结果便是:

  null
  null
  null
  java.lang.ref.PhantomReference@1fb8ee3

从执行结果得知,我们先前说的没有错,phanRef.get()不管在什么情况下,都会返回null,而当JVM执行GC发现虚引用之后,JVM并没有回收obj,而是将PhantomReference对象插入到对应的虚引用队列refQueue中,当调用refQueue.poll()返回PhantomReference对象时,poll方法会先把PhantomReference的持有队列queue(ReferenceQueue<? super T>)置为NULL,NULL对象继承自ReferenceQueue,将enqueue(Reference paramReference)方法覆盖为return false,而此时obj再次被GC发现时,JVM再将PhantomReference插入到NULL队列中便会插入失败返回false,此时GC便会回收obj。事实上通过这段代码我们也的却看不出来obj是否被回收,但通过 PhantomReference 的javadoc注释中有一句是这样写的:

Once the garbage collector decides that an object obj is phantom-reachable, it is being enqueued on the corresponding queue, but its referent is not cleared. That is, the reference queue of the phantom reference must explicitly be processed by some application code.

翻译一下(这句话很简单,我相信很多人应该也看得懂):

一旦GC决定一个“obj”是虚可达的,它(指PhantomReference)将会被入队到对应的队列,但是它的指代并没有被清除。也就是说,虚引用的引用队列一定要明确地被一些应用程序代码所处理。

弱引用与虚引用的用处

  软引用很明显可以用来制作caching和pooling,而弱引用与虚引用呢?其实用处也很大,首先我们来看看弱引用,举个例子:

1

2

3

4

5

6

7

class Registry {

    private Set registeredObjects = new HashSet();

 

    public void register(Object object) {

        registeredObjects.add( object );

    }

}

所有我添加进 registeredObjects 中的object永远不会被GC回收,因为这里有个强引用保存在registeredObjects里,另一方面如果我把代码改为如下:

1

2

3

4

5

6

7

class Registry {

     private Set registeredObjects = new HashSet();

 

     public void register(Object object) {

         registeredObjects.add( new WeakReference(object) );

     }

 }

  现在如果GC想要回收registeredObjects中的object,便能够实现了,同样在使用HashMap如果想实现如上的效果,一种更好的实现是使用WeakHashMap。

而虚引用呢?我们先来看看javadoc的部分说明:

Phantom references are useful for implementing cleanup operations that are necessary before an object gets garbage-collected. They are sometimes more flexible than the finalize() method.

翻译一下:

虚引用在实现一个对象被回收之前必须做清理操作是很有用的。有时候,他们比finalize()方法更灵活。

很明显的,虚引用可以用来做对象被回收之前的清理工作。

转载于:https://my.oschina.net/u/2365905/blog/1538636

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值