JVM原理与实现——Reference

= 1685

本文出自:【InTheWorld的博客】 (欢迎留言、交流)

duke_beer1. Reference的基本介绍

reference的中文含义是“引用”。由于本文所基于的HotSpot虚拟机主要使用C++开发,因此我担心有人会把C++的引用和这里的reference混为一谈。所以,我会尽量使用reference(首字母小写)来表述”引用“这个概念。通常我们写下如下的语句: Object obj; 其实就是定义了一个reference。我可以很直白的说出一个结论——在32位机器上HotSpot的reference就是一个32bit的指针。如下图所示,reference指向一个堆空间的实际对象。这种常用的reference,我们给它起个名字——StrongReference。之所以这样叫,是为了体现它和其他Reference(即将登场)的联系与区别。

image

在java.lang.ref包下,有这样几个类:SoftReference、WeakReference以及PhantomReference。它们之间的继承关系图如下所示:

reference_class

以WeakReference为例,我们来看看Reference的用法。有如下的代码片段:

   SoftReference sr = new SoftReference (new Employee ());
    Employee em = sr.get();

第一行就定义了一个关于Employee的软引用,换言之sr和new Employee()产生的匿名对象之间形成了软引用关系。第二行代码展示了软引用的使用方式,也很简单使用get()方法。而软应用的要点在于这个get()方法可能返回null,换言之软引用所指向的东西是可以被gc清理掉的。下面这张图展示了软引用的内存布局:

soft_reference

其中,SoftReference实例也是一个堆内存中的对象,它被sr这个普通的StrongReference所引用,同时它还通过软引用指向图中的Emplyee对象。其实在内存布局方面,WeakReference、PhantomReference与SoftReference是非常相似的,它们主要的区别在于gc的时机和get()方法返回值不同。比如,软引用会在内存紧张的时候被释放,而弱引用会在gc的时候立即被释放。

引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 通过 get() 方法 视内存情况回收 不可能
弱引用 通过 get() 方法 永远回收 不可能
虚引用 无法取得 不回收 可能

上面这张表展示了不同种类的Reference的特性对比。此外还有一个知识点值得注意,就是Reference.get()方法拿到的是一个强引用,所以不会出现get到一个非null值却在之后被gc掉的情况(在强引用作用域内)。另外,Reference中指向the referent的内部引用域其实是会被多个读、多个写的。Reference.get()是读的情况,它没有加锁,gc对引用域会存在写操作,但gc的操作都是在锁内完成的,这个锁就是全局的gc锁。因此,Reference是不存在同步错误的。

2. Reference语义的实现

前面用不少篇幅来介绍了Reference的基本工作方式,下面我们来研究下HotSpot VM是如何实现Reference的基本语义的。话不多说,先看看java.lang.ref包里面的代码。、

public abstract class Reference<T> {
    //Reference指向的对象
    private T referent;         /* Treated specially by GC */
    //Reference所指向的队列
    volatile ReferenceQueue<? super T> queue;

    @SuppressWarnings("rawtypes")
    Reference next;

    transient private Reference<T> discovered;  /* used by VM */

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
}

Reference的queue的作用是为了监视引用的状态,不算是一种常用的模式,第一个构造函数更为常用。这java.lang.ref的包里面,并看不出Reference和gc是如何协同作用的,而且也看不到任何native方法。然而”Treated specially by GC ”这句注释还是暴露了不少信息的。不同类型的Reference语义其实其实是由GC直接实现的。听起来有点悬。还是根据源代码来分析吧!下面根据g1垃圾收集器分析Reference的语义实现:

    openjdk\hotspot\src\share\vm\gc_implementation\g1\g1CollectedHeap.cpp

g1ColloctedHeap.cpp是g1垃圾收集器的重要实现部分。在g1ColloctedHeap类中有下面这个方法,简化的代码如下:

void G1CollectedHeap::process_discovered_references(uint no_of_gc_workers) {
  double ref_proc_start = os::elapsedTime();
  //获取ReferenceProcessor
  ReferenceProcessor* rp = _ref_processor_stw;
  ReferenceProcessorStats stats;
  if (!rp->processing_is_mt()) {
    // 串行Reference处理...
    stats = rp->process_discovered_references(&is_alive,
                                              &keep_alive,
                                              &drain_queue,
                                              NULL,
                                              _gc_timer_stw);
  } else {
    // 并行Reference处理
    G1STWRefProcTaskExecutor par_task_executor(this, workers(), _task_queues, no_of_gc_workers);
    stats = rp->process_discovered_references(&is_alive,
                                              &keep_alive,
                                              &drain_queue,
                                              &par_task_executor,
                                              _gc_timer_stw);
  }
}

这个函数会调用ReferenceProcessor的process_discovered_references()方法。ReferenceProcessor,顾名思义就是处理Reference的。到ReferenceProcessor的代码里面一看究竟吧!process_discovered_references()方法看起来非常直接了当,可以非常明显看出它完成了几种Reference的处理。

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(/* */) {
  // Soft references
  size_t soft_count = 0;
  {
    soft_count =
      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }
  // Weak references
  size_t weak_count = 0;
  /*  */
  // Phantom references
  size_t phantom_count = 0;
  /*  */
  return ReferenceProcessorStats(soft_count, weak_count, final_count, phantom_count);
}

由于几种Reference的处理其实比较类似,我就只贴了SoftReference的代码。process_discoverd_reflist()方法实现了真正的Reference处理工作。这个方法会调用三个阶段的代码,其中第三个阶段会去gc非Strong Reference的referent引用。这个第三阶段的函数就是process_phase3()。它的大致代码如下:

void
ReferenceProcessor::process_phase3(DiscoveredList&    refs_list,
                                   bool               clear_referent,
                                   BoolObjectClosure* is_alive,
                                   OopClosure*        keep_alive,
                                   VoidClosure*       complete_gc) {
  ResourceMark rm;
  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
  while (iter.has_next()) {
    iter.update_discovered();
    if (clear_referent) {
      // NULL out referent pointer
      iter.clear_referent();
    } else {
      // keep the referent around
      iter.make_referent_alive();
    }
    assert(iter.obj()->is_oop(UseConcMarkSweepGC), "Adding a bad reference");
    iter.next();
  }
  // Remember to update the next pointer of the last ref.
  iter.update_discovered();
  // Close the reachable set
  complete_gc->do_void();
}

不知道为什么,我很喜欢看到循环。大概是觉得它活干的比较多吧!process_phase3()的这个while循环,就完成了对Reference的处理。iter.clear_referenct()就是把Reference的referent域赋值为null,然后referent所指向的堆上对象就要面临被gc的命运了。

写到这里,可以看出gc对Reference的大致处理逻辑。然而,仍然有很多Reference的内容没有分析到,比如几种Reference的差异化处理,没有分析。WeakReference和SoftRefence的处理逻辑差异,主要体现在Reference发现的阶段,简单来讲SoftReference要在内存吃紧的时候才会被加到DiscoverList里面。而PhantomReference的作用是跟踪对象被垃圾回收器回收的活动,它需要和ReferenceQueue配合使用,绝大多数开发者都不需要使用它。还有就是ReferenceQueue的内容,这里也没有涉及。如果有同学感兴趣,可以按着上面的代码去跟跟。

~~~The End~~~

 

参考资料:

[1]. http://www.javaworld.com/article/2073891/java-se/java-se-trash-talk-part-2.html

[2]. https://www.ibm.com/developerworks/cn/java/j-lo-langref/

发表评论