JVM-强引用 软引用 弱引用 虚引用 底层原理

514 阅读9分钟

问题

看文章前,先尝试回答一下这个几个问题:

  • 软引用在内存不足时,会被回收,那什么叫内存不足?
  • 弱引用只要发生GC时,就会被回收,那底层原理是怎么实现的?
  • 虚引用形同虚设,任何时候都会被回收,那具体是什么时候会被回收?
  • 拓展:WeakHashMap和ThreadLocal应用(后续文章介绍,敬请关注)
  • 软引用,弱引用的适用场景(后续文章介绍,敬请关注)

假设上述这些问题,在面试时被问到,你怎么回答。

如果你能回答,可以对比一下我的思路;如果你不太清楚,可以接着往下看:

什么是强引用 软引用 弱引用 虚引用

首先,回答问题前,我们简单回顾一下知识:

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

  • 软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

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

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

它们四者的引用强度:强引用>软引用>弱引用>虚引用

上面是书本的知识。看完之后,每个字你都懂,连成一句话你也懂,但具体是如何实现你可能有点模糊。

那就对了。

我们接着深入到源码-->cpp源码

源码分析

本文基于JDK8源码进行分析:jdk/hotspot/src/share/vm/memory/referenceProcessor.cpp

process_discovered_references()

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
   //...省略

  // 软引用
  size_t soft_count = 0;
  {
    GCTraceTime tt("SoftReference", trace_time, false, gc_timer);
    soft_count =
      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  update_soft_ref_master_clock();

  // 弱引用
  size_t weak_count = 0;
  {
    GCTraceTime tt("WeakReference", trace_time, false, gc_timer);
    weak_count =
      process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }


  // 虚引用
  size_t phantom_count = 0;
  {
    GCTraceTime tt("PhantomReference", trace_time, false, gc_timer);
    phantom_count =
      process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

process_discovered_references() 方法具体逻辑:
按顺序处理

  • 1、软引用
  • 2、弱引用
  • 3、虚引用

三个不同的引用,都调用了同一个方法:process_discovered_reflist()

  • 入参:入参值不一样
  • 出参:引用的数量
  • 具体逻辑:我们接着往下看:

友情提示:代码分析有点长,最好先收藏,后点赞,方便你以后查阅理解。

process_discovered_reflist()

ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)
{
  //bool        _processing_is_mt;        // true during phases when
  //软引用:mt_processing true 弱引用: mt_processing true 虚引用:mt_processing true
  bool mt_processing = task_executor != NULL && _processing_is_mt;
  
  
  bool must_balance = _discovery_is_mt;

  if ((mt_processing && ParallelRefProcBalancingEnabled) ||
      must_balance) {
    balance_queues(refs_lists);
  }
  
  //统计refs_lists的总数量
  size_t total_list_count = total_count(refs_lists);

  if (PrintReferenceGC && PrintGCDetails) {
    gclog_or_tty->print(", %u refs", total_list_count);
  }
  //
  // Phase 1 (soft refs only):
  // . Traverse the list and remove any SoftReferences whose
  //   referents are not alive, but that should be kept alive for
  //   policy reasons. Keep alive the transitive closure of all
  //   such referents.
  
  // 阶段1:只处理软引用,因为policy入参,只有软引用不为NULL
  // 功能:把不存活的软引用从refs_lists中移除
  if (policy != NULL) {
    if (mt_processing) {
      RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);
      task_executor->execute(phase1);
    } else {
      for (uint i = 0; i < _max_num_q; i++) {
        //遍历全部引用
        process_phase1(refs_lists[i], policy,
                       is_alive, keep_alive, complete_gc);
      }
    }
  } else { // policy == NULL
    assert(refs_lists != _discoveredSoftRefs,
           "Policy must be specified for soft references.");
  }

  // Phase 2:
  // . Traverse the list and remove any refs whose referents are alive.
  // 阶段2
  // 功能:从refs_lists移除存活的引用
  if (mt_processing) {
    RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);
    task_executor->execute(phase2);
  } else {
    for (uint i = 0; i < _max_num_q; i++) {
      //遍历
      process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
    }
  }

  // Phase 3:
  // . Traverse the list and process referents as appropriate.
  // 阶段3
  // 功能:根据clear_referent的值决定是否将不存活对象回收。
  if (mt_processing) {
    RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
    task_executor->execute(phase3);
  } else {
    for (uint i = 0; i < _max_num_q; i++) {
      //遍历
      process_phase3(refs_lists[i], clear_referent,
                     is_alive, keep_alive, complete_gc);
    }
  }

  return total_list_count;
}

process_discovered_reflist()方法总结一下:

  • 阶段1:只处理软引用;
    • 功能:把不存活的软引用,但不需要清理的对象从refs_lists中移除
  • 阶段2:
    • 功能:从存活的对象从refs_lists移除
  • 阶段3:
    • 功能:根据clear_referent的值决定是否将不存活对象回收

所以可以说,软引用会经过阶段1,2,3;弱引用会经过阶段2,3;虚引用会经过2,3

阶段1:process_phase1

void
ReferenceProcessor::process_phase1(DiscoveredList&    refs_list,
                                   ReferencePolicy*   policy,
                                   BoolObjectClosure* is_alive,
                                   OopClosure*        keep_alive,
                                   VoidClosure*       complete_gc) {
  assert(policy != NULL, "Must have a non-NULL policy");
  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
  // Decide which softly reachable refs should be kept alive.
  // 遍历refs_list
  while (iter.has_next()) {
    iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));
    // 判断所引用的对象是否存活 存活:false 不存货:true
    bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();
    // 如果已经不存活,则根据ReferencePolicy策略(下面补充它是啥)去判断是否应该回收,should_clear_reference返回false,则从refs_list中移除,也就是不回收
    if (referent_is_dead &&
        !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {
        //回收对象
      if (TraceReferenceGC) {
        gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s"  ") by policy",
                               iter.obj(), iter.obj()->klass()->internal_name());
      }
      // Remove Reference object from list
      // 从refs_list移除
      iter.remove();
      // Make the Reference object active again
      iter.make_active();
      // keep the referent around
      iter.make_referent_alive();
      iter.move_to_next();
    } else {
      //对象存活,跳过,遍历下一个
      iter.next();
    }
  }
  // Close the reachable set
  complete_gc->do_void();
  NOT_PRODUCT(
    if (PrintGCDetails && TraceReferenceGC) {
      gclog_or_tty->print_cr(" Dropped %d dead Refs out of %d "
        "discovered Refs by policy, from list " INTPTR_FORMAT,
        iter.removed(), iter.processed(), (address)refs_list.head());
    }
  )
}

上述阶段1的代码逻辑:

  • 用人话说就是:将已经处于死亡状态但是不需要清理的对象从refs_list中清除掉,说白了,就是不参与垃圾回收。(补充:refs_list里面的对象,是要被垃圾回收的,别混淆啊)
  • 补充:ReferencePolicy.hpp

补充:ReferencePolicy.hpp


class ReferencePolicy : public CHeapObj<mtGC> {
 public:
  virtual bool should_clear_reference(oop p, jlong timestamp_clock) {
    ShouldNotReachHere();
    return true;
  }

  // Capture state (of-the-VM) information needed to evaluate the policy
  virtual void setup() { /* do nothing */ }
};

class NeverClearPolicy : public ReferencePolicy {
 public:
  virtual bool should_clear_reference(oop p, jlong timestamp_clock) {
    //永远返回false
    return false;
  }
};

class AlwaysClearPolicy : public ReferencePolicy {
 public:
  virtual bool should_clear_reference(oop p, jlong timestamp_clock) {
    //永远返回true
    return true;
  }
};

class LRUCurrentHeapPolicy : public ReferencePolicy {
 private:
  jlong _max_interval;

 public:
  LRUCurrentHeapPolicy();

  // Capture state (of-the-VM) information needed to evaluate the policy
  void setup();
  //这个类似于Java的抽象方法,具体实现在ReferencePolicy.cpp中
  virtual bool should_clear_reference(oop p, jlong timestamp_clock);
};

class LRUMaxHeapPolicy : public ReferencePolicy {
 private:
  jlong _max_interval;

 public:
  LRUMaxHeapPolicy();

  // Capture state (of-the-VM) information needed to evaluate the policy
  void setup();
  //这个类似于Java的抽象方法,具体实现在ReferencePolicy.cpp中
  virtual bool should_clear_reference(oop p, jlong timestamp_clock);
};

总结一下:referencePolicy.hpp有四个子类:

  • NeverClearPolicy
  • AlwaysClearPolicy
  • LRUCurrentHeapPolicy
  • LRUMaxHeapPolicy

对于should_clear_reference()方法很简单

  • NeverClearPolicy 永远返回false
  • AlwaysClearPolicy 永远返回true
  • LRUCurrentHeapPolicy 具体实现在ReferencePolicy.cpp中
  • LRUMaxHeapPolicy 具体实现在ReferencePolicy.cpp中

友情提示:如果你觉得分析得还不错,可以点“在看”,感谢老板

补充:ReferencePolicy.cpp

LRUCurrentHeapPolicy::LRUCurrentHeapPolicy() {
  setup();
}

void LRUCurrentHeapPolicy::setup() {
  //_max_interval:上一次GC后堆的可用空间
  _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}


bool LRUCurrentHeapPolicy::should_clear_reference(oop p,
                                                  jlong timestamp_clock) {
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  assert(interval >= 0, "Sanity check");

  // The interval will be zero if the ref was accessed since the last scavenge/gc.
  // 如果这个引用在上次GC中被访问interval为0
  if(interval <= _max_interval) {
    return false;
  }

  return true;
}

void LRUMaxHeapPolicy::setup() {
  size_t max_heap = MaxHeapSize;
  //max_heap: 堆的总空间-上次GC时堆的已使用空间
  max_heap -= Universe::get_heap_used_at_last_gc();
  max_heap /= M;
  
  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}


bool LRUMaxHeapPolicy::should_clear_reference(oop p,
                                             jlong timestamp_clock) {
  //java_lang_ref_SoftReference 是Java源码中的timestamp字段
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  assert(interval >= 0, "Sanity check");

  //返回false,代表软引用,会被GC回收
  if(interval <= _max_interval) {
    return false;
  }

  return true;
}

总结一下: LRUCurrentHeapPolicy的_max_interval是:上一次GC后堆的可用空间
LRUMaxHeapPolicy的_max_interval是:堆的总空间-上次GC时堆的已使用空间

那么问题来说:什么时候用LRUCurrentHeapPolicy?什么时候用LRUMaxHeapPolicy?

这里直接说结论:编译模式是server的时候使用LRUMaxHeapPolicy,编译模式是client的时候则使用LRUCurrentHeapPolicy。(我们目前一般都用server模式)

看到这里,我们可以解答这个问题了:

软引用在内存不足时,会被回收,那什么叫内存不足?

  • 是堆,不是新生代,也不是老年代。而且堆,也要根据不同的策略,有不同的计算方式。而且要补充一下,软引用的回收时机是推算出来的,根据什么推算出来?根据历史GC数据推算的。软引用的回收,不是单纯和内存挂钩的,是和内存,存活时间,policy策略都有关系的。

2种策略的interval的计算方式都一样,如下

//这个是C++代码
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
//这个是Java代码
public class SoftReference<T> extends Reference<T> {
  	// 时间戳时钟,由GC更新。
    static private long clock;
  	// 每次调用get方法所更新的时间戳
    private long timestamp;
    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }
    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
}

clock 就是上面C++代码timestamp_clock。

你看一下get()方法,这里的clock给timestamp赋值

而java_lang_ref_SoftReference::timestamp(p)就是指Java代码中的timestamp

换句话说,如果上次GC有访问这个get()方法,也就是调用这个,那么interval就是0

如果没调用,那么interval就是差值

用人话,总结一下上面:软引用被回收,除了和policy和是否存活外,还和存活时间有关系。

友情提示:如果你觉得分析得还不错,记得点关注,后续会有更多JVM源码分析文章

阶段2:process_phase2()

void
ReferenceProcessor::pp2_work(DiscoveredList&    refs_list,
                             BoolObjectClosure* is_alive,
                             OopClosure*        keep_alive) {
  assert(discovery_is_atomic(), "Error");
  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
  //遍历元素
  while (iter.has_next()) {
    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));
    DEBUG_ONLY(oop next = java_lang_ref_Reference::next(iter.obj());)
    assert(next == NULL, "Should not discover inactive Reference");
    //引用如果存活,则从refs_list中移除;如果引用不存活,就遍历下一个元素
    if (iter.is_referent_alive()) {
      if (TraceReferenceGC) {
        gclog_or_tty->print_cr("Dropping strongly reachable reference (" INTPTR_FORMAT ": %s)",
                               iter.obj(), iter.obj()->klass()->internal_name());
      }
      // The referent is reachable after all.
      // Remove Reference object from list.
      iter.remove();
      // Update the referent pointer as necessary: Note that this
      // should not entail any recursive marking because the
      // referent must already have been traversed.
      iter.make_referent_alive();
      iter.move_to_next();
    } else {
      iter.next();
    }
  }
  NOT_PRODUCT(
    if (PrintGCDetails && TraceReferenceGC && (iter.processed() > 0)) {
      gclog_or_tty->print_cr(" Dropped %d active Refs out of %d "
        "Refs in discovered list " INTPTR_FORMAT,
        iter.removed(), iter.processed(), (address)refs_list.head());
    }
  )
}

总结一下阶段2的功能:

就是如果引用指向的对象还存活,则从list中移除,否则保留。

阶段3: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);
  //遍历元素,这个时候list的元素,是经过阶段1和阶段2剩下的元素。
  while (iter.has_next()) {
    iter.update_discovered();
    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));
    if (clear_referent) { //软引用和弱引用都是true
      // 清除引用,之后对象会被GC回收
      iter.clear_referent(); 
    } else {
      // 标记引用的对象为存活,该对象在这次GC不会被回收
      iter.make_referent_alive();
    }
    if (TraceReferenceGC) {
      gclog_or_tty->print_cr("Adding %sreference (" INTPTR_FORMAT ": %s) as pending",
                             clear_referent ? "cleared " : "",
                             iter.obj(), iter.obj()->klass()->internal_name());
    }
    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();
}

总结一下阶段3的代码逻辑:

主要是根据clear_referent标记位,如果是true,那就清除引用,之后对象会被GC回收;如果是false,标记引用的对象为存活,该对象在这次GC不会被回收。

看到这里解答一下这个问题:

弱引用只要发生GC时,就会被回收,那底层原理是怎么实现的?

  • 在阶段3中,根据clear_referent标记位来进行是否回收操作。弱引用在process_discovered_reflist方法中传入的clear_referent为true。

最后一个问题:

虚引用形同虚设,任何时候都会被回收,那具体是什么时候会被回收?

  • 虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),这个时候是不会被回收的;如果你想被回收,需要调用clear方法解除PhantomReference和其引用对象的引用关系。

下一篇文章,说一下强引用 软引用 弱引用 虚引用的适用场景。敬请关注。

引用文章