让人心疼的Java虚引用!

本文转载自微信公众号「小姐姐味道」,作者小姐姐养的狗 。转载本文请联系小姐姐味道公众号。

创新互联专注为客户提供全方位的互联网综合服务,包含不限于成都网站建设、网站建设、衡南网络推广、重庆小程序开发、衡南网络营销、衡南企业策划、衡南品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供衡南建站搭建服务,24小时服务热线:13518219792,官方网址:www.cdcxhl.com

在Java的世界里,对象的存在层次,也有三六九等,充满了阶层之间的嘲弄。强软弱虚各种引用,对于熟悉Java的同学一定不会感到陌生,它们随着等级的降低,越来越没存在感。平常使用的对象,大多数就是强引用的;而软引用和弱引用,则经常在一些堆内缓存框架中用到。

那虚引用呢?传说中的幽灵引用,是不是就如同它的名字一样,一无是处呢?

三种引用

首先,我们来回顾一下其他三种引用的类型和用途。

Strong references

当内存空间不足,系统撑不住了,JVM 就会抛出 OutOfMemoryError 错误。即使程序会异常终止,这种对象也不会被回收。这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉。

这种引用,你每天的编码都在用。例如:new 一个普通的对象。

 
 
 
 
  1. Object obj = new Object()

这种方式可能是有问题的。假如你的系统被大量用户(User)访问,你需要记录这个 User 访问的时间。可惜的是,User 对象里并没有这个字段,所以我们决定将这些信息额外开辟一个空间进行存放。

Soft references

软引用用于维护一些可有可无的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

可以看到,这种特性非常适合用在缓存技术上。比如网页缓存、图片缓存等。

Guava 的 CacheBuilder,就提供了软引用和弱引用的设置方式。在这种场景中,软引用比强引用安全的多。

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

Weak references

弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。

当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。弱引用拥有更短的生命周期,在 Java 中,用 java.lang.ref.WeakReference 类来表示。

怪异的虚引用

以上几个引用级别都很好理解,但是虚引用是个例外。虚引用可以使用下面的代码定义:

 
 
 
 
  1. Object  object = new Object();
  2. ReferenceQueue queue = new ReferenceQueue();
  3. // 虚引用,必须与一个引用队列关联
  4. PhantomReference pr = new PhantomReference(object, queue);

但是当你想取出其中的值时(get),得到的却总是null。

 
 
 
 
  1. //JDK源码   
  2. /**
  3.      * Returns this reference object's referent.  Because the referent of a
  4.      * phantom reference is always inaccessible, this method always returns
  5.      * {@code null}.
  6.      *
  7.      * @return {@code null}
  8.      */
  9.     public T get() {
  10.         return null;
  11.     }

虚引用主要用来跟踪对象被垃圾回收的活动。

当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之前,把这个虚引用加入到与之关联的引用队列中。

程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

桃花源深处

在hotspot的jvm中,有一个叫做cleaner的类,其实就是虚引用典型的应用。可以看到Cleaner是直接简单粗暴的继承了PhantomReference,所以它本质上就是一个虚引用,只不过多了一些便捷的操作。

那么这个类是在什么地方用到的呢?大家手上应该都有jdk的源代码,追踪一下,发现最后竟然是DirectByteBuffer用到了它。

直接内存,一直是一个看起来非常高大上的名词,基本上和高性能挂钩,但也容易产生内存泄漏。由于直接内存,是属于堆外内存的,所以垃圾回收的时候,就不能靠JVM的那一套垃圾回收算法进行清理。

事实上,由于DirectByteBuffer可能会被使用较长时间,熬过了年轻代的各种回收,就会进入老年代。这时候就比较麻烦了,这些引用对象,要在下一轮Old GC或者Full GC才能触发,如果你的老年代空间较大,触发回收的操作就需要等很久很久。问题是,在这段时间内,虽然这些堆外内存不再使用了,但它仍然占用着较大的物理空间,最后造成严重的浪费甚至崩溃。

对堆外内存不是很熟悉的同学,可以看我以前的一张图。或者直接看这篇文章。通过-XX:MaxDirectMemorySize可以限制直接内存的使用上限。

《一图解千愁,jvm内存从来没有这么简单过!》

那么这些堆外内存是如何进行回收的呢?这就是Cleaner的作用。Cleaner通过next和prev构造了一个典型的链表,但它本身是没有任何逻辑的,因为它的清理逻辑都在thunk方法中。

 
 
 
 
  1. cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
  2. public void clean() {
  3.         if (remove(this)) {
  4.             try {
  5.                 this.thunk.run();

也就是Deallocator = De allocator。其中,传入的base,就是靠unsafe类申请的堆外内存地址引用(仅仅是个地址),有了引用和容量,其实我们就能够在回收的时候定位到真正的堆外内存块。就像Deallocator做的一样。

 
 
 
 
  1. public void run() {
  2.   if (address == 0) {
  3.     // Paranoia
  4.     return;
  5.   }
  6.   unsafe.freeMemory(address);
  7.   address = 0;
  8.   Bits.unreserveMemory(size, capacity);
  9. }

机制上没什么问题,关键要看它们是怎么联系起来的。这种问题,当然是要靠其他线程完成,这里就是ReferenceHandler。很熟悉的名字,你每次使用jstack命令导出堆栈,都会看到它。

 
 
 
 
  1. Thread handler = new ReferenceHandler(tg, "Reference Handler");
  2. /* If there were a special system-only priority greater than
  3. * MAX_PRIORITY, it would be used here
  4. */
  5. handler.setPriority(Thread.MAX_PRIORITY);
  6. handler.setDaemon(true);
  7. handler.start();

真正去工作的方法,是tryHandlePending,然后在这里,调用Cleaner的clean方法,进而调用真正的清理方法,释放堆外内存。它会从虚引用注册的队列里,取出新的对象,然后判断是不是Cleaner类型,如果是,就进行一次清理。

End

这就是虚引用。它存在的唯一目的,就是在回收的时候,能够被感知到,以便进行更深层次的清理。在commons-io包的FileCleaningTracker类中,同样有继承了虚引用的Tracker类,用来跟踪后续文件的一些清理工作。这个没存在感的小小虚引用,默默的承担起最后一道防线,是系统正常运行的有效保证。

不要小看它,它无处不在。因为你的每一个JVM进程,都跑着一个叫做Reference Handler的线程呢。

作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

名称栏目:让人心疼的Java虚引用!
网站路径:http://www.shufengxianlan.com/qtweb/news0/287900.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联