虚引用真的不影响对象的生命周期吗?99%的人都错了

news/2024/7/6 13:40:57 标签: python, jvm, 开发语言

Java的四大引用,大家都很熟悉吧:

  • 强应用:正常代码中的引用。一个对象能通过强应用访问到,那它就永远不会被回收
  • 软引用:比强引用弱一级的引用,内存不足时引用指向的对象会被回收
  • 弱引用:比软引用弱一级的引用,下一次GC时指向对象会被回收
  • 虚引用

最后一个虚应用是今天要讨论的。很多文章都是这么写的:

一个对象是否有虚引用存在,对其生存不会产生任何影响。

事实上,这个是错的。正确的表述是:

在Java 8以及之前的版本中,在虚引用回收后,虚引用指向的对象才会回收。在Java 9以及更新的版本中,虚引用不会对对象的生存产生任何影响。

一个示例

首先用Java 8,带上-Xmx10m -XX:+HeapDumpOnOutOfMemoryError参数运行如下代码:

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public final class Main {

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        PhantomReference<byte[]> ref = new PhantomReference<>(new byte[1024 * 1024 * 5], queue);

        System.out.println(queue.poll());
        System.out.println("第一次gc");
        System.gc();
        Thread.sleep(300L);
        System.out.println(queue.poll());
        System.out.println("第二次gc");
        System.gc();
        byte[] bytes1 = new byte[1024 * 1024 * 6];
        System.out.println("ending");
    }
}

也就是说,一个5M的数组,只被虚引用指向了,但是在OOM之前,它也不能被回收。

再看看heapdump:

从这张图可以看到,正是由于虚引用的存在,导致这个对象无法回收掉。

再去看看虚引用的文档,里面有这么一段:

An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.

翻译过来就是:

被虚引用指向的对象会一直存在,直到这些引用被清除或者这些引用不可达。

也就是说,只要有虚引用指向这个对象,那这个对象就会一直存在。

Java 11下的表现

更加奇怪的是,在Java 11下,用同样的参数运行这个程序,结果如下:

null
第一次gc
java.lang.ref.PhantomReference@5e91993f
第二次gc
ending

没有OOM了。

Java 9 引入的变更

翻了下变更记录,这个变化是在Java 9引入的:

修改的代码更是寥寥几行:

--- a/src/share/vm/gc/shared/referenceProcessor.cpp	Thu Dec 24 07:35:18 2015 -0800
+++ b/src/share/vm/gc/shared/referenceProcessor.cpp	Mon Dec 28 13:48:43 2015 -0500
@@ -243,7 +243,7 @@
   // Phantom references
   {
     GCTraceTime(Debug, gc, ref) tt("PhantomReference", gc_timer);
-    process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
+    process_discovered_reflist(_discoveredPhantomRefs, NULL, true,
                                is_alive, keep_alive, complete_gc, task_executor);
 
     // Process cleaners, but include them in phantom timing.  We expect

从代码来看,就是在处理虚引用的时候,将第三个参数clear_referent从false变为了true。

为了理清楚这个逻辑,我们来看看process_discovered_reflist的代码:

size_t
ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)
{
  // 省略了无关逻辑

  // 阶段三:
  // 切断剩余引用指向的对象
  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_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();
    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));
    // 这儿,如果clear_reference为true,就会清理指向的对象
    //  否则,就会将指向的对象标记为alive
    if (clear_referent) {
      // NULL out referent pointer
      iter.clear_referent();
    } else {
      // keep the referent around
      iter.make_referent_alive();
    }

可以看到,在Java 8之前的逻辑中,会调用make_referent_alive方法,导致虚引用指向的对象无法回收。

而在Java 9之后的逻辑中,会调用clear_referent,回收掉执行的对象。

于此同时,Java 9中,PhantomReference的文档说明也变了:

Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed.

在确定指向的对象会被回收后,虚引用会被放到队列( ReferenceQueue)中。

为什么Java 8不回收虚引用的对象呢

PhantomReference是为了追踪对象GC、回收对象关联的资源的。在Java 8的实现中,确保对象在真正GC前能被对应的ReferenceQueue处理,所以将对象标记为活跃,不回收对象。

显然,在这种情况下,会导致本可以回收的对象无法回收的问题,所以在Java 9中,确保PhantomReference指向的对象在回收后(而不是原来的回收前),会被对应的ReferenceQueue处理,这样在一定程度上保证了功能,又修复了这个问题。


http://www.niftyadmin.cn/n/4990461.html

相关文章

CUDA小白 - NPP(2) - Arithmetic and Logical Operations(1)

cuda小白 原文链接 NPP GPU架构近些年也有不少的变化&#xff0c;具体的可以参考别的博主的介绍&#xff0c;都比较详细。还有一些cuda中的专有名词的含义&#xff0c;可以参考《详解CUDA的Context、Stream、Warp、SM、SP、Kernel、Block、Grid》 常见的NppStatus&#xff0c…

如何在访问一个页面时,将访问时刻的时间显示在页面上

1.如何在访问一个页面时&#xff0c;将访问时刻的时间显示在页面上 GetMapping("/append") public ModelAndView append() {ModelAndView mvnew ModelAndView("expense/append");String date LocalDate.now().toString();mv.addObject("date",d…

Sql 函数传递参数 字符串拼接

使用场景 一个计算价格的函数&#xff0c;多个存储过程调用&#xff0c;因业务需求经常要新增参数&#xff0c;避免修改函数时程序执行存储过程报错&#xff0c;将多个参数拼接为一个字符串传递 -- 调用函数CalcuPrice(UnitPrice,CONCAT(MFQZC,MFQZC,&ItemNum,ItemNum,&am…

python -- 实现路径的匹配,剔除掉指定路径,并保存路径

python – 实现路径的匹配&#xff0c;剔除掉指定路径&#xff0c;并保存路径 在处理nc数据时&#xff0c;由于部分数据在插值的过程中&#xff0c;存在过多的0值&#xff0c;使得在制作标签时该时刻的数据出现报错&#xff0c;但是对于一年的数据量来说&#xff0c;无关紧要&…

常静相伴:深度解析C++中的const与static关键字

个人主页&#xff1a;北海 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C/C&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;大家一起学习交流&#xff01;&#x1f9…

基于JavaWeb和mysql实现校园订餐前后台管理系统(源码+数据库)

一、项目简介 本项目是一套基于JavaWeb和mysql实现网上书城前后端管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都…

PHP旅游管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 旅游管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 PHP 旅游管理系统 源码下载地址&#xff1a; https://download.csdn.net/download/qq_41…

CSDN每日一练 |『贝博士发奖金』『Longest Continuous Increasing Subsequence』『最小差值』2023-09-01

CSDN每日一练 |『贝博士发奖金』『Longest Continuous Increasing Subsequence』『最小差值』2023-09-01 一、题目名称:贝博士发奖金二、题目名称:Longest Continuous Increasing Subsequence三、题目名称:最小差值一、题目名称:贝博士发奖金 时间限制:1000ms内存限制:25…