热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java中的四种引用类型

了解内存布局和垃圾回收,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链。从GCRoots开始遍历,判

了解内存布局和垃圾回收,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链。从GC Roots 开始 遍历,判断引用是否可达。引用的可达性是判断能够被垃圾回收的基本条件。在某些场景下,即使引用可达,也希望能够根据语义的 强弱引用进行有选择的回收,以保证系统的正常运行。根据引用类型语义的强弱来决定垃圾回收的阶段,我们可以把引用分为强引用、软引用、弱引用和虚引用。后三类引用,本质上可以让开发工程师 通过代码方式来决定对象的垃圾回收时机。


  • 强引用,即Strong Reference ,最为常见,如 Object object = new Object();这样的 变量声明和定义就会产生对该对象的强引用。只要对象有强引用执行啊,并且GC Roots 可达,那么java内存回收时,即使濒临内存耗尽也不会回收该对象。
  • 软引用,即Soft Reference, 因用力弱于“强引用”,是用在非必须对象的场景。在即 将OOM之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间。主要用来缓存服务器中间结算结果即不需要实时保存的用户行为等。
  • 弱引用,即 Weak Reference ,引用强度较前两种更弱,也是用来描述非必须对象的。如果弱引用指定的对象只存在弱引用这一条路,则在下一次YGC时会被回收由于YGC 不确定,弱引用何时被回收也具有不确定性。弱引用主要用于指向某个易消失的对象。在强引用断开后,此引用不会劫持对象。
  • 虚引用,即Phantom Reference ,是极弱的一种引用关系,定义完成后吗,就无法通过该引用获取指向的对象。为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知。

对象的引用类型如下图:

Java 中的四种引用类型


举个例子,在房产交易市场中,某个卖家有一套房子,成功出售给某个买家后引用置为null,

这里有4个买家使用4种不同的引用关系指向这套房子。


  • 买家是强引用: 如果把seller引用赋值给它,则永久有效,系统不会因为seller= null 就触发对这套房子的回收,这时房屋交易市场最常见的交付方式。
  • 买家buyer2是软引用,只要不产生OOM,buyer2.get() 就可以获取房子对象,就像房子是租来的一样。
  • 买家buyer3是弱引用,一旦过户后,seller置为null, buyer3的房子持有时间估计只有几秒钟,卖家只是给买家做了一张假的房产证,买家高兴了几秒钟后没发现房子已经不是自己的了。
  • buyer4 是虚引用,定义完成后无法访问到房子对象,卖家只是虚构的房源,是空手套白狼的诈骗术。

强引用是最常用的,而虚引用在业务中几乎很难用到,下面我们举例介绍一下软引用和弱引用

先来说一下软引用的回收机制。首先设置JVM参数: -Xms20m -Xmx20m , 即只有20MB的堆空间内容。在下面代码中不断地往集合中添加House对象,而每个House有2000个Door的成员变量,狭小的堆空间加上大对象的产生,就为了尽快触达内存耗尽的临界状态。

强引用


public class SoftReferenceHouse {public static void main(String[] args) {List houses &#61; new ArrayList<>();int i &#61;0;while (true){try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}houses.add(new House());System.out.println("i&#61;&#61;"&#43;(&#43;&#43;i));}}
}class House{private static final Integer DOOR_NUMBER &#61; 2000;public Door[] doors &#61; new Door[DOOR_NUMBER];class Door{}
}

Java 中的四种引用类型


上面代码会一直执行&#xff0c;直到报OOM&#xff0c;因为强引用不会释放。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.yaspeed2.House.(SoftReferenceHouse.java:26)at com.yaspeed2.SoftReferenceHouse.main(SoftReferenceHouse.java:17)

软引用

public class SoftReferenceHouse {public static void main(String[] args) {List> houses &#61; new ArrayList>();int i &#61;0;while (true){SoftReference buyer2 &#61; new SoftReference(new House());houses.add(buyer2);System.out.println("i&#61;&#61;"&#43;(&#43;&#43;i));}}
}class House{private static final Integer DOOR_NUMBER &#61; 4000;public Door[] doors &#61; new Door[DOOR_NUMBER];class Door{}
}

正常运行一段时间&#xff0c;内存到达耗尽的临近状态&#xff0c;House$Door 超过10MB左右&#xff0c;内存占比达到百分之七八十。

Java 中的四种引用类型


软引用的特性在数秒之后产生价值&#xff0c;House对象从千数量级迅速降到百数量级&#xff0c;内存容量迅速被释放出来。保证了程序的正常执行。

软引用SoftReference 的父类 Reference的属性&#xff1a; private T referent, 它指向new House()对象&#xff0c;而SoftReference 的get(),也是调用了super.get() 来访问父类这个私有属性。大量的House 在内存即将耗尽前&#xff0c;成功地一次次被清理掉。

对象buyer2虽然是引用类型&#xff0c;但其本身碍事占用一定内存空间的&#xff0c;它是被集合 ArrayList 强引用劫持的&#xff0c;在不断循环执行 houses.add() 后&#xff0c;终究会产生 OOM。

软引用、弱引用、虚引用均存在带有队列的构造方法

public SoftReference(T referent, ReferenceQueue q) {super(referent, q);this.timestamp &#61; clock;}

可以在队列中检查哪个软引用的对象被回收了&#xff0c;从而把失去House 的软引用对象清理掉。

软引用一般用于在同一服务器内缓存中间结果。如果命中缓存&#xff0c;则提取缓存结果&#xff0c;否则重新计算或获取。但是&#xff0c;软引用肯定不是用来缓存高频数据结构的&#xff0c;万一服务器重启或者软引用触发大规模回收&#xff0c;所有的访问将直接指向数据库&#xff0c;导致数据库压力时大时小&#xff0c;甚至崩溃。

弱引用
代码如下:

public class WeakReferenceWhenIdle {public static void main(String[] args) {House seller &#61; new House();WeakReference buyer3 &#61; new WeakReference<>(seller);seller &#61; null;long start &#61; System.nanoTime();long count &#61; 0;while (true){if(buyer3.get() &#61;&#61; null){long duration &#61; (System.nanoTime()- start)/(1000*1000);System.out.println("house is null and exited time &#61; "&#43;duration &#43;"ms");break;}else{System.out.println("still there.count&#61;"&#43;count&#43;&#43;);}}}
}

执行结果如下:

house is null and exited time &#61; 964ms
HeapPSYoungGen total 75776K, used 2733K [0x000000076bf80000, 0x0000000771400000, 0x00000007c0000000)eden space 65024K, 2% used [0x000000076bf80000,0x000000076c161470,0x000000076ff00000)from space 10752K, 7% used [0x000000076ff00000,0x000000076ffca020,0x0000000770980000)to space 10752K, 0% used [0x0000000770980000,0x0000000770980000,0x0000000771400000)ParOldGen total 173568K, used 8K [0x00000006c3e00000, 0x00000006ce780000, 0x000000076bf80000)object space 173568K, 0% used [0x00000006c3e00000,0x00000006c3e02000,0x00000006ce780000)Metaspace used 3541K, capacity 4502K, committed 4864K, reserved 1056768Kclass space used 388K, capacity 390K, committed 512K, reserved 1048576K

这个示例代码在YGC下&#xff0c;可以轻松回收 WeakReference指向的new House() 对象&#xff0c;WeakReference 典型的应用是WeakHashMap中。

在刚才的房源案例中&#xff0c;卖家的房子对应一些列房源资料&#xff0c;如果卖家的房源已经售出&#xff0c;则中介也不需要一直保存相关信息&#xff0c;自动回收存储空间即可&#xff0c;如下代码&#xff1a;

public class WeakHashMapTest {public static void main(String[] args) {House seller1 &#61; new House("1号卖家房源.");SellerInfo sellerInfo1 &#61; new SellerInfo();House seller2 &#61; new House("2号卖家房源");SellerInfo sellerInfo2 &#61; new SellerInfo();WeakHashMap weakHashMap &#61; new WeakHashMap<>();//如果换成 HashMap &#xff0c;则Key是对House对象的强引用weakHashMap.put(seller1,sellerInfo1);weakHashMap.put(seller2,sellerInfo2);System.out.println("weakHashMap before null,size&#61;"&#43;weakHashMap.size());seller1 &#61; null;System.gc();System.runFinalization();//如果换成 HashMap &#xff0c;size 依然等于2System.out.println("weakHashMap after null, size &#61; "&#43;weakHashMap.size());System.out.println(weakHashMap);}
}class SellerInfo{}

执行结果如下:

weakHashMap before null,size&#61;2
weakHashMap after null, size &#61; 1
{House{name&#61;&#39;2号卖家房源&#39;}&#61;com.yaspeed2.SellerInfo&#64;14ae5a5}

如果换成HashMap ,key就是强引用指向House对象&#xff0c;即使seller1&#61;null &#xff0c;也并不影响HashMap这个key被置为null,如果是HashMap ,则最后的size依然等于2&#xff0c;而WeakHashMap就是1&#xff0c;回收seller1的引用。因而WeakHashMap 适用于 缓存不敏感的临时信息的场景。例如&#xff0c;用户登录系统后的浏览路径在关闭浏览器后可以自动清空。


推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • JVM:33 如何查看JVM的Full GC日志
    1.示例代码packagecom.webcode;publicclassDemo4{publicstaticvoidmain(String[]args){byte[]arr ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了Java中Hashtable的clear()方法,该方法用于清除和移除指定Hashtable中的所有键。通过示例程序演示了clear()方法的使用。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 从相邻元素对还原数组的解题思路和代码
    本文介绍了从相邻元素对还原数组的解题思路和代码。思路是使用HashMap存放邻接关系,并找出起始点,然后依次取。代码使用了HashMap来存放起始点所在的adjacentPairs中的位置,并对重复的起始点进行处理。 ... [详细]
  • java中的try catch_Java中的trycatchfinally异常处理
    Java中的try-catch-finally异常处理一、异常处理异常(Exception):是在运行发生的不正常情况。原始异常处理:if(条件){处理办法1处理办法 ... [详细]
author-avatar
dmcm0006
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有