小Hub领读:

深层次分析,有谁看懂了,点个赞看看?我一脸懵逼进来,一脸懵逼出去~


作者:木枣粽子

https://juejin.im/post/6854573215767855117

在 Java 中总共有 4 中核心的引用类型——强引用、软引用、弱引用、虚引用。一般情况下我们往往用到强引用比较多,很少会遇到场景用到其他三种引用,所以对其原理的掌握就更加是一纸空白。此次,恰遇机会就正好研究一下这四种引用的原理,以解己惑。

关于强引用,因为日常使用,大家基本都比较清楚,因此本文就不探究强引用这块。除了上述的四种引用之外,还有一种引用类型,叫做 FinalReference,本文也同样不作探究。本文主要探究软引用、弱引用和虚引用的原理以及区别。

源码分析

无论是 SoftReference、WeakReference,还是 PhantomReference,事实上都继承了 Reference 类。此处先直接贴出 Reference 的回收过程,

在整个 Reference 的回收过程中,JVM 层和 Java 层都参与了清理工作。

Java 层

由于最终的清理工作是由 Java 层完成的,因此我们先从 Java 层作为切入点。

Reference 数据结构

我们不妨先来看一看 Reference 的数据结构,

public abstract class Reference<T> {private T referent;volatile ReferenceQueue super T> queue;    Reference next;transient private Reference discovered;private static Reference pending = null;}

这是 Reference 的数据结构,其中:

  1. referent 为引用的对象

  2. queue 用来存储被清理的引用,此处 queue 是通过链式来存储的,而 next 则表示这条链的下一个节点

  3. discovered 和 pending 就比较有意思了,它在不同情况下有着不同的含义:

  • 在平时,discovered 表示 DiscoveredList

  • 在对象回收阶段时,pending 和 discovered 共同组成 PendingList,此时 discovered 相当于 next 的作用

Java 层回收代码

接下来我们看一下 Java 层的回收代码,这段代码同样也在 Reference.class 里面

static boolean tryHandlePending(boolean waitForNotify) {  Reference r;  Cleaner c;try {synchronized (lock) {if (pending != null) {        r = pending;        c = r instanceof Cleaner ? (Cleaner) r : null;        pending = r.discovered;        r.discovered = null;      } else {if (waitForNotify) {          lock.wait();        }return waitForNotify;      }    }  } catch (OutOfMemoryError x) {    Thread.yield();return true;  } catch (InterruptedException x) {return true;  }if (c != null) {    c.clean();return true;  }  ReferenceQueue super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);return true;}private static class ReferenceHandler extends Thread {public void run() {while (true) {      tryHandlePending(true);    }  }}

首先看看 tryHandlePending 方法,可以发现整段逻辑还是比较简单的,如果 pending!=null,就清理 pending,然后指针移到下一个元素。再配上外层的 while(true),就实现了清理整个 PendingList 的功能。搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

JVM 层

从上面我们已经可以知道了只要引用对象被加入进了 PendingList,就会被清理掉,那这些引用对象又会在什么时候、什么情况下被加入到 PendingList 中呢?这同样也是软引用、弱引用和虚引用的核心区别。

JVM 层的核心处理代码在 referenceProcessor.cpp 中,核心方法为 process_discovered_references(),以 CMS GC 为例,这个方法会在 FinalMarking(重新标记) 阶段被调用,这段代码的核心逻辑如下:

ReferenceProcessorStats ReferenceProcessor::process\_discovered\_references(BoolObjectClosure\* is\_alive, OopClosure\* keep\_alive, VoidClosure\* complete\_gc, AbstractRefProcTaskExecutor\* task\_executor, ReferenceProcessorPhaseTimes\* phase\_times) {

double start\_time = os::elapsedTime();  disable\_discovery();  \_soft\_ref\_timestamp\_clock = java\_lang\_ref\_SoftReference::clock();  ReferenceProcessorStats stats(total\_count(\_discoveredSoftRefs),                                total\_count(\_discoveredWeakRefs),                                total\_count(\_discoveredFinalRefs),                                total\_count(\_discoveredPhantomRefs));

// 1. 初步处理软引用  {    RefProcTotalPhaseTimesTracker tt(RefPhase1, phase\_times, this);    process\_soft\_ref\_reconsider(is\_alive, keep\_alive, complete\_gc,                                task\_executor, phase\_times);  }

  update\_soft\_ref\_master\_clock();

// 2. 处理软引用、弱引用、FinalReference  {    RefProcTotalPhaseTimesTracker tt(RefPhase2, phase\_times, this);    process\_soft\_weak\_final\_refs(is\_alive, keep\_alive, complete\_gc, task\_executor, phase\_times);  }

// 3. FinalReference的另一端处理逻辑  {    RefProcTotalPhaseTimesTracker tt(RefPhase3, phase\_times, this);    process\_final\_keep\_alive(keep\_alive, complete\_gc, task\_executor, phase\_times);  }

// 4. 处理虚引用  {    RefProcTotalPhaseTimesTracker tt(RefPhase4, phase\_times, this);    process\_phantom\_refs(is\_alive, keep\_alive, complete\_gc, task\_executor, phase\_times);  }

if (task\_executor != NULL) {    task\_executor->set\_single\_threaded\_mode();  }

  phase\_times->set\_total\_time\_ms((os::elapsedTime() - start\_time) \* 1000);

return stats;}

排除掉我们本次并不关心的 FinalReference,我们可以大概看到整体处理是这样的:

  1. 初步处理软引用

  2. 处理软引用和弱引用

  3. 处理虚引用

1. process_soft_ref_reconsider

在这个方法里面核心主要调用下面一段逻辑,

size\_t ReferenceProcessor::process\_soft\_ref\_reconsider\_work(DiscoveredList&    refs\_list,                                                            ReferencePolicy\*   policy,                                                            BoolObjectClosure\* is\_alive,                                                            OopClosure\*        keep\_alive,                                                            VoidClosure\*       complete\_gc) {  DiscoveredListIterator iter(refs\_list, keep\_alive, is\_alive);while (iter.has\_next()) {    bool referent\_is\_dead = (iter.referent() != NULL) && !iter.is\_referent\_alive();if (referent\_is\_dead &&        !policy->should\_clear\_reference(iter.obj(), \_soft\_ref\_timestamp\_clock)) {      iter.remove();      iter.make\_referent\_alive();      iter.move\_to\_next();    } else {      iter.next();    }  }  complete\_gc->do\_void();return iter.removed();}

在这段代码中,大白话翻译一下就是: 将软引用列表中所有的处于死亡状态但不需要清理的对象从队列中移除掉,也就是说不参与清理

那么这里就有一个比较有意思的地方了,这里的是否需要清理是怎样一段逻辑呢?过去我们常听到内存满的时候才会清理软引用,那到底是不是这么一回事呢?

在这里,should_clear_reference 其实是使用了策略模式,也就是说这个方法在不同情况下是不一样的,目前而言有如下几种策略:

// AlwaysClearPolicyclass AlwaysClearPolicy : public ReferencePolicy {public:  virtual bool should\_clear\_reference(oop p, jlong timestamp\_clock) {return true;  }};

// LRUCurrentHeapPolicybool LRUCurrentHeapPolicy::should\_clear\_reference(oop p,                                                  jlong timestamp\_clock) {  jlong interval = timestamp\_clock - java\_lang\_ref\_SoftReference::timestamp(p);if(interval <= \_max\_interval) {return false;  }return true;}

// LRUMaxHeapPolicybool LRUMaxHeapPolicy::should\_clear\_reference(oop p,                                             jlong timestamp\_clock) {  jlong interval = timestamp\_clock - java\_lang\_ref\_SoftReference::timestamp(p);if(interval <= \_max\_interval) {return false;  }return true;}

// NeverClearPolicyclass NeverClearPolicy : public ReferencePolicy {public:  virtual bool should\_clear\_reference(oop p, jlong timestamp\_clock) {return false;  }};

首先 NeverClearPolicy 在 JVM 事实上并没有用到,我们此处忽略。AlwaysClearPolicy 此处也不进行讨论,因为平时 GC 时 (以 CMS GC 为例) 也不是使用的这个策略。那么接下来就是 LRUCurrentHeapPolicy 和 LRUMaxHeapPolicy 了,那这两种策略分别在什么情况下使用的呢?

这里就不贴代码了,直接说答案吧。当我们的编译模式是 server 的时候使用 LRUMaxHeapPolicy,编译模式是 client 的时候则使用 LRUCurrentHeapPolicy

但是这时如果我们仔细瞧一瞧,却会发现这两个策略的代码貌似一毛一样啊。那他们的差别到底在哪里呢?

其实这两个策略的_max_interval的值是不一样的,如下:

void LRUCurrentHeapPolicy::setup() {  \_max\_interval = (Universe::get\_heap\_free\_at\_last\_gc() / M) \* SoftRefLRUPolicyMSPerMB;}

void LRUMaxHeapPolicy::setup() {  size\_t max\_heap = MaxHeapSize;  max\_heap -= Universe::get\_heap\_used\_at\_last\_gc();  max\_heap /= M;  \_max\_interval = max\_heap \* SoftRefLRUPolicyMSPerMB;}

我们不去探究这两种方式的优与劣。从上面代码中我们能得出以下结论:

  • 软引用的回收机制在不同情况下是有所不同的

  • 软引用大概会在内存不足的时候才会回收

  • 软引用的回收时机是一个推算的时间节点,根据历史 GC 数据推算得来的,而不是真正意义上的和内存容量挂钩

2. process_soft_weak_final_refs

这个方法中的核心逻辑如下:

process\_soft\_weak\_final\_refs\_work(\_discoveredSoftRefs\[i\], is\_alive, keep\_alive, true);process\_soft\_weak\_final\_refs\_work(\_discoveredWeakRefs\[i\], is\_alive, keep\_alive, true);process\_soft\_weak\_final\_refs\_work(\_discoveredFinalRefs\[i\], is\_alive, keep\_alive, false);

即分别针对软引用、弱引用以及 FinalReference 调用了process_soft_weak_final_refs_work()这个方法,我们来看看这个方法,

size\_t ReferenceProcessor::process\_soft\_weak\_final\_refs\_work(DiscoveredList&    refs\_list,                                                             BoolObjectClosure\* is\_alive,                                                             OopClosure\*        keep\_alive,                                                             bool do\_enqueue\_and\_clear) {  DiscoveredListIterator iter(refs\_list, keep\_alive, is\_alive);while (iter.has\_next()) {if (iter.referent() == NULL) {      iter.remove();      iter.move\_to\_next();    } else if (iter.is\_referent\_alive()) {      iter.remove();      iter.make\_referent\_alive();      iter.move\_to\_next();    } else {if (do\_enqueue\_and\_clear) { // 软引用和弱引用的情况下都为true        iter.clear\_referent();        iter.enqueue();      }      iter.next();    }  }if (do\_enqueue\_and\_clear) {    iter.complete\_enqueue();    refs\_list.clear();  }return iter.removed();}

这段逻辑同样比较简单,简单来说就是:

  1. 如果引用对象为空,或着引用对象仍是活跃对象,则移出队列

  2. 如果引用对象不是活跃对象,就添加到 PendingList 中

也就是说弱引用到了 GC 时就会清理掉所有的不活跃对象,但是软引用由于有之前的策略初筛,不活跃对象不一定会被被清理。

3. process_phantom_refs

此方法核心调用逻辑如下:

size\_t ReferenceProcessor::process\_phantom\_refs\_work(DiscoveredList&    refs\_list,                                          BoolObjectClosure\* is\_alive,                                          OopClosure\*        keep\_alive,                                          VoidClosure\*       complete\_gc) {  DiscoveredListIterator iter(refs\_list, keep\_alive, is\_alive);while (iter.has\_next()) {    oop const referent = iter.referent();if (referent == NULL || iter.is\_referent\_alive()) {      iter.make\_referent\_alive();      iter.remove();      iter.move\_to\_next();    } else {      iter.clear\_referent();      iter.enqueue();      iter.next();    }  }  iter.complete\_enqueue();  complete\_gc->do\_void();  refs\_list.clear();return iter.removed();}

和弱引用对比,貌似唯一的区别就是虚引用 referent == NULL 的时候,会执行 make_referent_alive 操作,但是似乎好像也没啥大的区别。说起弱引用和虚引用的真正区别,其实是在 Java 层代码处,虚引用的 get 方法永远返回的都是 null,也就是说虚引用就真正的相当于没有引用 (不考虑使用反射获取引用对象这种奇葩情况)。

总结

从上文的分析中,我们可以得出软引用、弱引用以及虚引用的区别:

  1. 软引用会在 GC 的时候可能会被清理,但是频率会比较低

  2. 弱引用在 GC 的时候必定会被清理

  3. 虚引用引用对象无法直接使用,主要应用场景是配合 ReferenceQueue 跟踪垃圾回收,在 GC 的时候也必然会被清理

另外, 由于 SoftReference、WeakReference、PhantomReference 以及 FinalReference 是与 JVM 硬相关的,因此我们随意实现自己的 Reference 是没有意义的。


(完)

MarkerHub文章索引:

https://github.com/MarkerHub/JavaIndex

【推荐阅读】

企业服务内部接口校验方案

为什么MySQL不推荐使用uuid或者雪花id作为主键?

Docker 实战总结(非常全面)这是我读过写得最好的【秒杀系统架构】分析与实战!

你的登录接口真的安全吗?

权限管理系统之集成Shiro实现登录、url和页面按钮的访问控制

Vue项目使用拦截器和JWT验证 完整案例

好文章!点个在看!

的引用_懵!啥是Java软引用、弱引用、虚引用?相关推荐

  1. java中四种引用类型(对象的强、软、弱和虚引用)

    2019独角兽企业重金招聘Python工程师标准>>> 对象的强.软.弱和虚引用在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有 ...

  2. Java:对象的强、软、弱和虚引用

    见:http://zhangjunhd.blog.51cto.com/113473/53092 maven/Java/web/bootstrap/dataTable/app开发QQ群:56686262 ...

  3. 软引用和弱引用的区别_强、软、弱、虚引用的区别和使用

    原文阅读: 强.软.弱.虚引用的区别和使用​mp.weixin.qq.com Java提供了四种级别的应用类型:强引用.软引用.弱引用及虚引用.那么这四种引用类型有什么区别呢? 首先我们通过一张图来看 ...

  4. 对象的强、软、弱和虚引用

    本文介绍对象的强.软.弱和虚引用的概念.应用及其在UML中的表示. 1.对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有 ...

  5. java-基础-强、软、弱、虚引用

    Java的四种引用,强弱软虚,用到的场景. JDK1.2之前只有强引用,其他几种引用都是在JDK1.2之后引入的. 强引用(Strong Reference) 最常用的引用类型,如Object obj ...

  6. jvm系列 (四) ---强、软、弱、虚引用

    java引用 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 我的博客目录 为什么将引用分为不同的强度 因为我们需要实现这样一种情 ...

  7. JVM 学习(2)—简单理解Java 四大引用(强、软、弱、虚)

    一.Java 引用概述 Java 中出现四种引用是为了更加灵活地管理对象的生命周期,以便在不同场景下灵活地处理对象的回收问题.不同类型的引用在垃圾回收时的处理方式不同,可以用来实现不同的垃圾回收策略. ...

  8. Java对象引用四个级别(强、软、弱、虚)

    转载自 Java对象引用四个级别(强.软.弱.虚) 最近,高级Java技术栈微信群中,有一些猿友在讨论JVM中对象的周期问题,有谈到引用的级别,现在为大家做个总结吧,虽然大多数公司并没有意识或者用到这 ...

  9. Java中的强软弱虚引用《对Java的分析总结三》

    <对Java的分析总结>-Java中的强软弱虚引用 强引用 StrongReference 软引用 SoftReference 弱引用 WeakReference 虚引用 PlantomR ...

最新文章

  1. 把ct图像像素值转化为_数字图像概论与基础
  2. java非静态块,在java中使用非静态块有什么用?
  3. Learn from mistake, i.e. 和 e.g. 是不同的
  4. Android实训日志:基于外部存储卡的音乐播放器V02
  5. OpenDNS,独特的免费DNS
  6. 【SPSS】第十周-面板数据的线性回归
  7. 获取url中的参数方法,避免#的干扰,删除url指定参数(vue hash模式 有#删除指定参数问题)
  8. SQL SERVER 内存分配及常见内存问题(1)——简介
  9. Unity3D 使用 GL 绘制线条
  10. 利用 Python 插件 xlwings 读写 Excel
  11. http网络传输协议
  12. 深入解析Windows操作系统(笔记2)
  13. 通用印刷体文字识别_腾讯云通用文字识别GeneralOCR-图片文字识别 印刷体文字识别...
  14. 画图软件gliffy
  15. YOLOv2相比于yolov1的改进
  16. 字体识别在线工具-整理
  17. docker装LibreELEC_如何在LibreELEC上安装Entware?
  18. 国际化之货币符号显示
  19. 【一名合格前端工程师的自检清单】--自检答案
  20. HTML5 水平线标签 hr

热门文章

  1. 使用OpenExif修改jpeg图片信息
  2. LeetCode题 - 1 两数之和
  3. .NET开发 程序员必备工具 -- Regulator:生成正则表达式工具
  4. 在ASP.NET 3.5中使用新的ListView控件(5)
  5. pytorch图像数据增强N大技巧
  6. ML《决策树(一)ID3》
  7. 计算 期望与方差(mean and Variance)在 Tensorflow 与 Numpy 对比
  8. 行为识别(HAR)调研
  9. 用三角函数正交坐标系计算三角级数(傅里叶级数)
  10. android studio抛出,Android Studio中新的项目不能运行,抛出错误(Android Studio new pr