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

news2025/4/27 10:52:45

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.coloradmin.cn/o/959852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

现货白银投资快速入门的方法

虽然在现货白银投资之中有很多&#xff0c;所谓的速成班都是不靠谱的&#xff0c;他们一般都是营销的噱头&#xff0c;但是会普通投资者来说&#xff0c;通过一些方法的训练&#xff0c;可以实现快速入门。所谓的快速入门&#xff0c;就是能够在市场中掌握一定的投资方法&#…

手机建模自定义方法,你会用了吗?

易模App借助影像即可完成三维建模&#xff0c;除了在各个建模模式中直接拍摄采集数据&#xff0c;还用开放了宽容度更高的建模方法——自定义建模。 自定义方法可以使用在【人脸模式】【人像模式】【主体模式】【场景模式】中&#xff0c;上传满足建模条件的视频或照片组即可实…

小编亲测八款Ai工具,建议收藏,最后一个你懂的

随着AI大模型不断更新迭代&#xff0c;AI应用也呈现出爆炸式的增长趋势&#xff0c;而利用人工智能工具提高工作效率&#xff0c;已经成为今年来越来越受欢迎的工作方式。除了大名鼎鼎的chatgpt之外&#xff0c;小编在体验了超多各种类型、各种功能的AI工具后&#xff0c;给大家…

count(1)、count(*)和count(列名)及官网解释

最近面试并且看网上的资料说count(1)和count(*)参差不同&#xff0c;就查看了官网&#xff0c;特别记录一下。 共同点&#xff1a;都是用来统计我们的表中的行数不同点&#xff1a; 执行效果上来说&#xff1a;count(1)和count(*)都不会忽略列值为null的行数&#xff0c;而cou…

smartbi token回调获取登录凭证漏洞(二)

2023年8月8日Smartbi官方又修复了一处权限绕过漏洞。该漏洞是上一个特定场景下设置Token回调地址漏洞的绕过&#xff0c;未经授权的攻击者可利用该漏洞&#xff0c;获取管理员token&#xff0c;完全接管管理员权限。 于是研究了下相关补丁并进行分析。 0x01 分析过程 阅读相关…

一篇文章帮你拯救70%的购物车放弃率

想象一下&#xff0c;您倾注所有的心血到您的在线业务中&#xff0c;却面临着令人痛苦的购物车放弃率。这可能会让你痛苦不堪&#xff0c;错失了销售和收入的机会。 此外&#xff0c;你忠实的顾客可能会叛逃去使用竞争对手的产品&#xff0c;雪上加霜。通过再次在广告上支付客…

教程i.MX8MPlus开发板SPI转CAN操作

飞凌嵌入式OKMX8MP-C核心板有两路原生CAN总线&#xff0c;但用户在开发产品时可能需要用到更多的CAN&#xff0c;这该如何解决呢&#xff1f;今天小编将为大家介绍一种SPI转CAN的方法&#xff0c;供各位工程师小伙伴参考。 说明 OKMX8MP-C核心板有两路原生的SPI总线&#xff0c…

如何让看书变听书?

听书神器 安卓 页面简单&#xff0c;易操作&#xff0c;全网小说随便听 各种声音帮你读你喜欢听的小说&#xff0c;带你进入主人公世界 支持网页版小说、本地小说、图片&#xff0c;都能读给你听 想看小说&#xff0c;又怕伤眼睛的宝子&#xff0c;可以试试看&#xff01;…

堆的基本存储(Java 实例代码)

堆的基本存储 一、概念及其介绍 堆(Heap)是计算机科学中一类特殊的数据结构的统称。 堆通常是一个可以被看做一棵完全二叉树的数组对象。 堆满足下列性质&#xff1a; 堆中某个节点的值总是不大于或不小于其父节点的值。堆总是一棵完全二叉树。 二、适用说明 堆是利用完…

typescript的~~和双感叹号符号使用

&#xff08;标题不给用“!”&#xff09; "~~"符号使用 1.对于number类型的值&#xff0c;~~是取整作用 const num: number 3.14; const roundedNum: number ~~num; // 3 2.对于boolean类型的值&#xff0c;~ ~ true还是true&#xff0c;false还是false “!!”…

无涯教程-JavaScript - FLOOR函数

描述 FLOOR函数将数字向下舍入为零,直到最接近的有效倍数。 语法 FLOOR (number, significance)争论 Argument描述Required/OptionalNumberThe numeric value you want to round.RequiredSignificanceThe multiple to which you want to round.Required Notes 如果数字的符…

设计模式--多例模式(Multition pattern)

一、什么是多例模式&#xff08;Multition pattern&#xff09; 多例模式&#xff08;Multition pattern&#xff09;是单例模式的一种扩展&#xff0c;它属于对象创建类型的设计模式。在多例模式中&#xff0c;一个类可以有多个实例&#xff0c;并且这些实例都是该类本身。因…

APM32F4XX USB OTA

近期在研究USB CDC协议&#xff0c;使用USB Virtual Port Com功能与上位机通讯做了OTA功能。开发平台&#xff1a;MDK529开发硬件&#xff1a;APM32F411首先看下手册Flash分布&#xff0c;Flash总共8个扇区。 接下来进行Flash分区。 扇区 0 和 扇区 1做Boo区。 扇区 2做APP跳…

LeetCode(力扣)98. 验证二叉搜索树Python

LeetCode98. 验证二叉搜索树 题目链接代码 题目链接 https://leetcode.cn/problems/validate-binary-search-tree/ 代码 递归 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # …

基于RabbitMQ的模拟消息队列之五——虚拟主机设计

文章目录 一、创建VirtualHost类二、初始化三、API1.创建交换机2.删除交换机3.创建队列4.删除队列5.创建绑定6.删除绑定7.发送消息转发规则 8.订阅消息1.消费者管理2.推送消息给消费者 3.添加一个消费者管理ConsumerManager9.确认消息 创建VirtualHost类。 1.串起内存和硬盘的数…

ppt怎么压缩?这里有4个方法

ppt怎么压缩&#xff1f;PPT作为我们常用的文档格式&#xff0c;在演讲、总结汇报以及个人简历等场合中起到重要作用。一份PPT的页数可能从十几页到上百页不等&#xff0c;有些小伙伴为了追求ppt文件的美观度&#xff0c;会在文件中插入大量的高清图片&#xff0c;然而这种操作…

(附源码)使用 javascript 制作网页端 3D 贪吃蛇游戏

3D 网页版贪吃蛇游戏&#xff01;下面来具体讲一下如何实现。 该游戏使用 Hightopo 的 SDK 制作&#xff0c;总共 100 多行代码&#xff0c;没有 WebG L基础的同学们也可很快掌握。 场景初始化 首先&#xff0c;我们对页面进行初始化&#xff0c;包括初始化3D场景&#xff0c;…

护眼灯值不值得买?开学给孩子买什么样的护眼台灯

如果不想家里的孩子年纪小小的就戴着眼镜&#xff0c;从小就容易近视&#xff0c;那么护眼灯的选择就非常重要了&#xff0c;但是市场上那么多品类&#xff0c;价格也参差不齐&#xff0c;到底怎么选呢&#xff1f;大家一定要看完本期内容。为大家推荐五款热门的护眼台灯 一、…

XSS结合CSRF

假设我们获得了目标CMS的源码&#xff0c;搭建了一个相同的网站&#xff0c;我们在自己的网站执行添加用户的操作&#xff0c;并且用bp抓包 如图&#xff0c;这是我们抓到的添加用户的数据包 接下来&#xff0c;我们可以根据数据包构造js代码 <script> xmlhttp new XML…

WEBGL(1):WEBGL介绍

1 WebGL容器&#xff08;坐标系&#xff09; 在2D绘图环境中的坐标系统&#xff0c;默认情况下是与窗口坐标系统相同&#xff0c;它以canvas的左上角为坐标原点&#xff0c;沿x轴向右为正值&#xff0c;沿y轴向下为正值。其中canvas坐标的单位都是 "px"。 在浏览器中…