[转载]Java四种引用类型

掌握Java中的引用类型,有助于我们理解对象的生命周期,以及如何改变或者介入对象生命周期中的各个阶段。

每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。

//创建一个引用,引用可以独立存在,并不一定需要与一个对象关联
String s;

通过将这个叫“引用”的标识符指向某个对象,之后便可以通过这个引用来实现操作对象了。

String str = new String("abc");
System.out.println(str.toString());

在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。
在不同垃圾回收算法中,对引用的判断方式有所不同:

  • 引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了)。
  • 可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。

JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。

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

强引用

Java中默认声明的就是强引用,比如:

Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null;  //手动置null

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了

软引用

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。

下面以一个例子来验证下:

    private static void testSoftReference() {
        MyObject instance = new MyObject();
        SoftReference<Object> reference = new SoftReference<>(instance);
        System.out.println("[Origin]reference = " + reference.get());
        instance = null; //移除强引用

        gcAndWaitFinalized();//尝试触发GC
        System.out.println("[After GC]reference = " + reference.get());

        //在这一行,我们尝试耗尽所有可用内存,直到抛出OutOfMemoryError为止。
        tryToAllocateAllAvailableMemory();
        System.out.println("[After OutOfMemory]reference = " + reference.get());
    }

    private static void gcAndWaitFinalized() {
        CountDownLatch finalizerLatch = new CountDownLatch(1);
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        new WeakReference<>(
                new Object() {
                    @Override
                    protected void finalize() {
                        finalizerLatch.countDown();
                    }
                },
                queue);

        System.gc();

        try {
            finalizerLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    public static void tryToAllocateAllAvailableMemory() {
        try {
            final List<Object[]> allocations = new ArrayList<>();
            int size;
            while ((size = (int) Runtime.getRuntime().freeMemory()) > 0) {
                Object[] part = new Object[Math.min(size, Integer.MAX_VALUE)];
                allocations.add(part);
            }
        } catch (OutOfMemoryError e) {
            System.out.println("OutOfMemoryError:" + e.getMessage());
        }
    }

    private static class MyObject {

        private byte[] data = new byte[1 * 1024 * 1024];

        @Override
        protected void finalize() throws Throwable {
            System.out.println("==Trigger MyObject finalize==" + this.toString());
        }
    }

打印结果:

[Origin]reference = com.reference.ReferenceTest$MyObject@1b6d3586
[After GC]reference = com.reference.ReferenceTest$MyObject@1b6d3586
==Trigger MyObject finalize==
OutOfMemoryError:Java heap space
[After OutOfMemory]reference = null

结果表明,在可用内存不足的时候,软引用指向的对象被回收掉了。这种特性可以被用来实现缓存技术,比如网页缓存,图片缓存等。但更好的做法是基于LRU算法来实现缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

    private static void testSoftReferenceWithQueue() {
        MyObject instance = new MyObject();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        SoftReference<Object> reference = new SoftReference<>(instance, queue);
        System.out.println("[Origin]reference = " + reference.get());
        System.out.println("[Origin]queue poll = " + queue.poll());
        instance = null; //移除强引用

        gcAndWaitFinalized();//尝试触发GC
        System.out.println("[After GC]reference = " + reference.get());
        System.out.println("[After GC]queue poll = " + queue.poll());

        //在这一行,我们尝试耗尽所有可用内存,直到抛出OutOfMemoryError为止。
        tryToAllocateAllAvailableMemory();
        System.out.println("[After OutOfMemory]reference = " + reference.get());
        System.out.println("[After OutOfMemory]queue poll = " + queue.poll());
    }

打印结果:

[Origin]reference = com.reference.ReferenceTest$MyObject@1b6d3586
[Origin]queue poll = null
[After GC]reference = com.reference.ReferenceTest$MyObject@1b6d3586
[After GC]queue poll = null
OutOfMemoryError:Java heap space
==Trigger MyObject finalize==
[After OutOfMemory]reference = null
[After OutOfMemory]queue poll = java.lang.ref.SoftReference@677327b6

弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。

在GC决定回收弱引用关联的对象前,你都可以通过弱引用来获取该对象。但是一旦GC标记该对象可回收(注意:不是在调用对象的finalize()方法并释放内存之后),你就只能从中获取到NULL。

与软引用一样,弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

下面以一个例子来验证下:

    private static void testWeakReferenceWithQueue() {
        MyObject instance = new MyObject();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        WeakReference<Object> reference = new WeakReference<>(instance, queue);
        System.out.println("[Origin]reference = " + reference.get());
        System.out.println("[Origin]queue poll = " + queue.poll());
        instance = null; //移除强引用

        gcAndWaitFinalized();//尝试触发GC
        System.out.println("[After GC]reference = " + reference.get());
        System.out.println("[After GC]queue poll = " + queue.poll());
    }

打印结果:

[Origin]reference = com.reference.ReferenceTest$MyObject@1b6d3586
[Origin]queue poll = null
==Trigger MyObject finalize==
[After GC]reference = null
[After GC]queue poll = java.lang.ref.WeakReference@677327b6

利用WeakReference和ReferenceQueue这个特性,Java为我们提供了WeakHashMap这个数据结构。类似于HashMap,它使用WeakReference用作Map的键。如果这个键成为垃圾,那它对应的值也会相应的被删除掉。

下面以一个例子来验证下:

    private static void testWeakMap() throws InterruptedException {
        WeakHashMap<String, Boolean> map = new WeakHashMap<>();
        String instance = new String("hello world");
        map.put(instance, true);
        System.out.println("[before] weak map size=" + map.size());
        instance = null;//移除强引用
        gcAndWaitFinalized();//触发GC,hold住主线程直到被回收的对象finalize()方法被执行
        System.out.println("[after] weak map size=" + map.size());
    }

打印结果:

[before] weak map size=1
[after] weak map size=0

虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,这点和弱引用保持一致。在 JDK1.2 之后,用 PhantomReference 类来表示。

与软应用和弱引用不同点:

  • 你永远无法从虚引用来获取对象,因为它的get()方法永远会返回NULL
  • 虚引用必须和引用队列一起使用,软引用和弱引用是可选的
  • 只有对象的内存即将被物理清理的时候,该虚引用才会进入引用队列。软引用和弱引用都是在GC开始标识不可达对象阶段就已经进入了引用队列
  • 与软引用和弱引用不同,当虚引用进入引用队列后,该虚引用指向的对象不会被自动垃圾回收掉。除非该虚引用本身变得不可达或者清除掉虚引用和对象之间的关系(PhantomReference#clear())。

利用虚引用的这些特性,我们可以有机会在对象从内存中被彻底清除的时候得到一个系统通知。常用来完成资源的清理工作,它比Finalize机制更灵活可靠。

让我们看一下代码:

VM options: -Xms50m -Xmx50m -verbose:gc

    private static void testPhantomReferenceWithQueue() {
        MyObject instance = new MyObject();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        PhantomReference<Object> reference = new PhantomReference<>(instance, queue);
        System.out.println("[Origin]queue poll = " + queue.poll());
        instance = null; //移除强引用
        gcAndWaitFinalized();//尝试触发GC - 因为MyObject重写了Finalize方法,本次无法GC,要等Finalizer线程异步执行后才能继续GC
        System.out.println("[After GC]queue poll = " + queue.poll());

        while(true) {
            Reference ref = queue.poll();
            if (ref == null) {
                System.gc();//尝试再次触发GC - 从Finalized阶段尝试进入Deallocated阶段,因为存在引用队列,需要先通知。
            } else {
                System.out.println("[After GC Again]queue poll = " + ref);
                ref.clear();//清除虚引用关系
                System.gc();//再次触发GC - 真正的释放内存
                break;
            }
        }
    }

打印结果:

[Origin]queue poll = null
[GC (System.gc())  2632K->1744K(49152K), 0.0015372 secs]
[Full GC (System.gc())  1744K->1621K(49152K), 0.0046187 secs]
[After GC]queue poll = null
[GC (System.gc())  2389K->1717K(49152K), 0.0004784 secs]
[Full GC (System.gc())  1717K->1621K(49152K), 0.0051003 secs]
==Trigger MyObject finalize==com.reference.ReferenceTest$MyObject@3a18f192
[GC (System.gc())  1877K->1717K(49152K), 0.0003184 secs]
[Full GC (System.gc())  1717K->1618K(49152K), 0.0063282 secs]
[GC (System.gc())  1618K->1650K(49152K), 0.0004734 secs]
[Full GC (System.gc())  1650K->1618K(49152K), 0.0022322 secs]
[GC (System.gc())  1874K->1682K(49152K), 0.0003491 secs]
[Full GC (System.gc())  1682K->1619K(49152K), 0.0046835 secs]
[After GC Again]queue poll = java.lang.ref.PhantomReference@74a14482
[GC (System.gc())  2387K->1683K(49152K), 0.0007709 secs]
[Full GC (System.gc())  1683K->595K(49152K), 0.0053436 secs]

JDK很多地方都用到了虚引用,比如 DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。

原文地址:
https://www.cnblogs.com/liyutian/p/9690974.html
http://antkorwin.com/concurrency/weakreference.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341