美文网首页Java 核心技术
详细谈谈JAVA中对象的四种引用

详细谈谈JAVA中对象的四种引用

作者: 先生zeng | 来源:发表于2019-08-27 16:32 被阅读0次

    在JDK 1.2以前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。

    ​ 在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

    强引用

    强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

    StringBuffer str = new StringBuffer ("Hello world") ;
    
    假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例被分配在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer实例的强引用

    此时,如果再运行一 个赋值语句:StringBuffer strl=str;

    那么,str所指向的对象也将被strl 所指向,同时在局部变量表上会分配空间存放strl 变量

    强引用具备以下特点:

    • 强引用可以直接访问目标对象。
    • 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向对象。
    • 强引用可能导致内存泄漏。

    软引用

    软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。一个对象只持有软引用,那么当堆空间不足时,就会被回收。



    在main方法中,第 1 行,建立 了User类的实例,这里的 u 变量为强引用。代码第2行,通过强引用u, 建立软引用。代码第3行,去除强引用。代码第5行从软引用中重新获得强引用对象。第6行进行第一 次垃圾回收,第 8 行,在垃圾同收之后,在此获得软引用中的对象。在第10行,分配一 块较大的内存,让系统认为内存资源紧张,在 11 行进行一 次 GC实际上,这个是多余的,因为在分配大数据时,系统会自动进行GC, 这里只是为了更清楚地说明问题),第12行再次从软引用中获取数据。



    可知,GC不会引起软引用对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用不会引起内存溢出。

    每一 个软引用都可以附带一 个引用队列,当对象的可达性状态发生改变时 (由可达变为不可达),软引用对象就会进入引用队列。通过这个引用队列,可以跟踪对象的回收情况。

    弱引用(发现即回收)

    ​弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。

    在系统 G C 时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并不一 定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一 个弱引用对象被垃圾回收器回收,便会加入到一 个注册的引用队列中 。

    总结:

    软引用、弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。
    而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
    

    虚引用(对象回收跟踪)

    虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

    (它的作用在于跟踪垃圾回收过程,在对象被收集器回收时收到一个系统通知。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。 所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收了)。

    虚引用实例

    可以看一下代码:

    public class PhantomReferenceTest {
    
        private static final List<Object> TEST_DATA=new LinkedList<>();
    
        private static final ReferenceQueue<TestClass> QUEUE=new ReferenceQueue<>();
    
        public static void main(String[] args) {
            TestClass obj  = new TestClass("test");
            PhantomReference<TestClass> phantomReference = new PhantomReference<>(obj, QUEUE);
    
        // 该线程不断读取这个虚引用,并不断往列表里插入数据,以促使系统早点进行GC
        new Thread(() -> {
            while (true){
                TEST_DATA.add(new byte[1024*100]);
                try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                System.out.println(phantomReference.get());
            }
        }).start();
    
            // 这个线程不断读取引用队列,当弱引用指向的对象被回收时,该引用就会被加入到引用队列中
            new Thread(() -> {
                while (true) {
                    Reference<? extends TestClass> poll = QUEUE.poll();
                    if (poll != null) {
                        System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                        System.out.println("--- 回收对象 ---- " + poll.get());
                    }
                }
            }).start();
    
            obj = null;
            try {
                Thread.currentThread().join();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
        static class TestClass{
    
            private String name;
    
            public TestClass(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "TestClass{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
    }
    

    测试结果:

    com.zxy.test.jvmtest.PhantomReferenceTest
    [GC (Allocation Failure)  1024K->648K(3584K), 0.0017586 secs]
    [GC (Allocation Failure)  1672K->900K(3584K), 0.0014332 secs]
    [GC (Allocation Failure)  1924K->1076K(3584K), 0.0018352 secs]
    [GC (Allocation Failure)  2100K->1240K(3584K), 0.0067994 secs]
    null
    null
    null
    null
    null
    null
    null
    [GC (Allocation Failure)  2235K->2004K(3584K), 0.0014507 secs]
    [Full GC (Ergonomics)  2004K->1613K(3584K), 0.0142815 secs]
    --- 虚引用对象被jvm回收了 ---- java.lang.ref.PhantomReference@7141c426
    --- 回收对象 ---- null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    null
    [Full GC (Ergonomics)  2630K->2612K(3584K), 0.0147467 secs]
    null
    null
    null
    null
    [Full GC (Ergonomics)  3033K->3010K(3584K), 0.0126232 secs]
    [Full GC (Allocation Failure)  3010K->3010K(3584K), 0.0038559 secs]
    Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at com.zxy.test.jvmtest.PhantomReferenceTest.lambda$main$0(PhantomReferenceTest.java:30)
        at com.zxy.test.jvmtest.PhantomReferenceTest$$Lambda$1/1637070917.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
    

    待续更新。。。。

    相关文章

      网友评论

        本文标题:详细谈谈JAVA中对象的四种引用

        本文链接:https://www.haomeiwen.com/subject/vpgeectx.html