聊一聊 C# 弱引用 底层是怎么玩的

news2024/11/25 3:31:22

一:背景

1. 讲故事

最近在分析dump时,发现有程序的卡死和WeakReference有关,在以前只知道怎么用,但不清楚底层逻辑走向是什么样的,借着这个dump的契机来简单研究下。

二:弱引用的玩法

1. 一些基础概念

用过WeakReference的朋友都知道这里面又可以分为弱短弱长两个概念,对应着构造函数中的trackResurrection参数,同时它也是对底层GCHandle.Alloc 方法的封装,参考源码如下:


public WeakReference(object? target, bool trackResurrection)
{
    Create(target, trackResurrection);
}

private void Create(object target, bool trackResurrection)
{
    nint num = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
    _taggedHandle = (trackResurrection ? (num | 1) : num);
    ComAwareWeakReference.ComInfo comInfo = ComAwareWeakReference.ComInfo.FromObject(target);
    if (comInfo != null)
    {
        ComAwareWeakReference.SetComInfoInConstructor(ref _taggedHandle, comInfo);
    }
}

public enum GCHandleType
{
    //
    // Summary:
    //     This handle type is used to track an object, but allow it to be collected. When
    //     an object is collected, the contents of the System.Runtime.InteropServices.GCHandle
    //     are zeroed. Weak references are zeroed before the finalizer runs, so even if
    //     the finalizer resurrects the object, the Weak reference is still zeroed.
    Weak = 0,
    //
    // Summary:
    //     This handle type is similar to System.Runtime.InteropServices.GCHandleType.Weak,
    //     but the handle is not zeroed if the object is resurrected during finalization.
    WeakTrackResurrection = 1
}

从上面的 GCHandleType 的注释来看。

  • Weak 会在终结器执行之前判断持有的对象是否为垃圾对象,如果是的话直接切断引用。
  • WeakTrackResurrection 会在终结器执行之后判断对象是否为垃圾对象,如果是的话直接切断引用。

可能这么说有点抽象,画张图如下:

2. 一个简单的测试例子

为了方便讲述两者的区别,使用 对象复活 来做测试。

  1. Weak 的情况

因为在 ScanForFinalization 方法之前做的判断,所以与垃圾对象的联系会被马上切断,参考代码如下:


    class Program
    {
        static void Main()
        {
            WeakReferenceCase();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(weakHandle.Target ?? "Person 引用被切断");

            Console.ReadLine();
        }

        public static GCHandle weakHandle;

        static void WeakReferenceCase()
        {
            var person = new Person() { ressurect = false };
            weakHandle = GCHandle.Alloc(person, GCHandleType.Weak);
        }
    }

    public class Person
    {
        public bool ressurect = false;

        ~Person()
        {
            if (ressurect)
            {
                Console.WriteLine("Person 被永生了,不可能被消灭的。。。");
                GC.ReRegisterForFinalize(this);
            }
            else
            {
                Console.WriteLine("Person 析构已执行...");
            }
        }
    }

  1. WeakTrackResurrection 的情况

因为是在 ScanForFinalization 之后做的判断,这时候可能会存在 对象复活 的情况,所以垃圾又变成不垃圾了,如果是这种情况就不能切断,参考代码如下:


static void WeakReferenceCase()
{
    var person = new Person() { ressurect = true };
    weakHandle = GCHandle.Alloc(person, GCHandleType.WeakTrackResurrection);
}

3. coreclr源码分析

在 coreclr 里有一个 struct 枚举强对应 GCHandleType 结构体,而且名字看的更加清楚,代码如下:


typedef enum
{
	HNDTYPE_WEAK_SHORT = 0,
	HNDTYPE_WEAK_LONG = 1,
}
HandleType;

接下来看下刚才截图源码上的验证。


void gc_heap::mark_phase(int condemned_gen_number, BOOL mark_only_p)
{
	// null out the target of short weakref that were not promoted.
	GCScan::GcShortWeakPtrScan(condemned_gen_number, max_generation, &sc);

	dprintf(3, ("Finalize marking"));
	finalize_queue->ScanForFinalization(GCHeap::Promote, condemned_gen_number, mark_only_p, __this);

	// null out the target of long weakref that were not promoted.
	GCScan::GcWeakPtrScan(condemned_gen_number, max_generation, &sc);
}

BOOL CFinalize::ScanForFinalization(promote_func* pfn, int gen, BOOL mark_only_p, gc_heap* hp)
{
    for (unsigned int Seg = startSeg; Seg <= gen_segment(0); Seg++)
    {
        Object** endIndex = SegQueue(Seg);
        for (Object** i = SegQueueLimit(Seg) - 1; i >= endIndex; i--)
        {
            CObjectHeader* obj = (CObjectHeader*)*i;

            if (!g_theGCHeap->IsPromoted(obj))
            {
                if (method_table(obj)->HasCriticalFinalizer())
                {
                    MoveItem(i, Seg, CriticalFinalizerListSeg);
                }
                else
                {
                    MoveItem(i, Seg, FinalizerListSeg);
                }
            }
        }
    }

    if(finalizedFound) GCToEEInterface::EnableFinalization(true);

    return finalizedFound;
}

源码中有几个注意点:

  1. 如何判断一个对象为垃圾

gc 在标记时,将有根的对象mt的第一位设为 1 来表示当前已经标记过,即有用对象,未被标记的即为垃圾对象。

  1. 终结器线程真的被启动了吗

从简化的源码看,一旦有垃圾对象被送入到 终结器队列的 预备区 时,就会通过 GCToEEInterface::EnableFinalization(true) 启动终结器线程,所以在测试代码中加了 GC.WaitForPendingFinalizers(); 就是为了等待终结器线程执行完毕然后才判断 Target,这样结果就会更加准确。

4. 切断逻辑在哪里

有些朋友会好奇那个 weakHandle.Target=null 的逻辑到底在 coreclr 的何处,这个比较简单,可以用 windbg 下 ba 断点即可,我们还是拿弱引用来举例,截图如下:

三:总结

WeakReference 的内部玩法有很多,更深入的理解还需要对 g_HandleTableMap 进行深度挖掘,后面有机会再聊吧,有时候dump分析还是挺苦逼的,需要对相关领域底层知识有一个足够了解,否则谈何修复呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1872426.html

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

相关文章

主播美颜工具开发全攻略:美颜SDK从基础到进阶的技术指南

今天&#xff0c;笔者将为你详细介绍美颜SDK的基础知识以及如何进行进阶开发。 一、美颜SDK基础知识 什么是美颜SDK&#xff1f; 美颜SDK是一种软件开发工具包&#xff0c;包含了一系列用于图像处理的算法和功能&#xff0c;主要用于实时视频处理和图像优化。开发者可以将美…

计算机基础——经典排序算法总结2

直接插入排序的过程&#xff1a;先将序列第一个记录暂时作为有序子序列&#xff0c;从第二个开始逐个进行插入&#xff0c;直至整个序列有序。一趟排序将elem[i]插入到已排好序elem[0…i-1]中各元素做比较后的任何对应位置&#xff0c;所以未必能选出一个元素放在其最终位置上。…

自定义 vant 的 van-calendar 日历控件

最近在做 vue 微信公众号项目&#xff0c; 有个自定义日历控件展示的需求&#xff0c;经过查阅资料&#xff0c;最终实现了如图所示效果&#xff0c;这里做了总结&#xff0c;需要的小伙伴可以参考一下&#xff1a; HTML代码&#xff1a; <template><div class"…

YOLOv10(7):YOLOv10训练(以训练VOC数据集为例)

YOLOv10&#xff08;1&#xff09;&#xff1a;初探&#xff0c;训练自己的数据_yolov10 训练-CSDN博客 YOLOv10&#xff08;2&#xff09;&#xff1a;网络结构及其检测模型代码部分阅读_yolov10网络结构图-CSDN博客 YOLOv10&#xff08;4&#xff09;&#xff1a;损失&…

每日一题——Python实现蓝桥杯1. 坤坤的破译任务(举一反三+思想解读+逐步优化)三千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 时间复杂度&#xff1a; 空间复杂度&#xff1a; 我要更强 时间复杂度分析…

Flutter循序渐进==>与基金mysql数据库交互

导言 债基基金的注意事项&#xff0c;别看收益不高&#xff0c;注意事项可真不少。最近买了CS一支基金&#xff0c;三周时间就亏掉两三个点&#xff08;水平全网最差、赎回费和管理费全网最高&#xff09;。就是冲着它的历史成绩去的&#xff0c;突然发现已经换了基金经理&…

向日葵API集成服务:三大优势助力企业搭建专属远控方案

为了方便企业用户更加灵活的搭建满足各自需求的软件解决方案&#xff0c;降低开发和部署门槛&#xff0c;很多软件方案供应商提供了多样化的合作模式。 以贝锐向日葵远程控制为例&#xff0c;他们就向客户提供了基础的SaaS服务模式、私有化部署模式、SDK嵌入模式&#xff0c;以…

第二证券:近300亿资金抄底,低估值+高增长“错杀”股名单来了,仅11只

成绩表现优质公司或存在“错杀”的状况。 头部宽基ETF成交额继续放量&#xff0c;近300亿资金抄底 6月21日&#xff0c;上证指数跌破3000点后&#xff0c;商场谨慎情绪延伸&#xff0c;成交量继续走低&#xff0c;本周4个交易日均缺乏7000亿元。 关于3000点以下的地量行情&a…

推荐系统中冷启动环节的设计实现

目录 推荐系统中冷启动环节的设计实现 推荐系统中冷启动环节的设计实现 唐楠烊 [IT阅读排行榜](javascript:void(0)&#x1f609; 2024-06-28 07:15 北京 推荐系统中的冷启动分为物料冷启动和用户冷启动。用户冷启动主要是针对新用户&#xff0c;但有时候也用于低活用户拉活。…

CVPR24已开源:刷新监督学习SOTA,无监督多目标跟踪时代来临!

论文标题&#xff1a; Matching Anything by Segmenting Anything 论文作者&#xff1a; Siyuan Li, Lei Ke, Martin Danelljan, Luigi Piccinelli, Mattia Segu, Luc Van Gool, Fisher Yu 导读&#xff1a; 在计算机视觉的征途中&#xff0c;多目标跟踪&#xff08;MOT&…

武汉凯迪正大—漆包圆线软化击穿试验仪 漆包线检测仪器

产品功能 武汉凯迪正大电气有限公司生产KDZD-JC软化击穿试验仪用于检测3.00mm及以下漆包线在热状态下漆膜软化后的绝缘性能&#xff0c;根据设定的预热温度、试验时间、仪器自动完成对试样的预加热、加负荷、加试验电压&#xff0c;达到试验时间或试样被击穿时即卸负荷&#x…

您渴望提升交易技能吗?

您渴望提升交易技能吗&#xff1f;Eagle Trader就是您的理想选择&#xff01;我们精心设计了多样化的挑战&#xff0c;配备真实数据模拟交易环境&#xff0c;让您轻松精进交易技术。更有精选交易工具和卓越服务助您一臂之力&#xff0c;优化策略&#xff0c;洞察市场&#xff0…

锂电池的串并联特性

1节锂电池电芯的规格是10000mah&#xff0c;4v&#xff08;总能量10000*4&#xff09; 那么3节电芯串联电池的规格是10000mah&#xff0c;12v&#xff08;总能量10000*12&#xff09;注意&#xff0c;这里电池的规格不是30000mah 3节电芯并联的规格是30000mah&#xff0c;4v …

【数据采集】亮数据浏览器、亮网络解锁器实战指南

前言 继上次我们写了数据采集与AI分析&#xff0c;亮数据通义千问助力跨境电商前行的文章之后&#xff0c;好多小伙伴来后台留言&#xff0c;表示对亮数据的数据采集非常感兴趣&#xff0c;并且感觉用起来非常顺手&#xff0c;大大减少了小白用户获取数据的成本。 在这儿&…

Redis和PHP的Bitmap于二进制串的相互转换

Redis和PHP的Bitmap于二进制串的相互转换 场景 错题集的存储&#xff0c;需要有正确的题号id集合&#xff0c;错误的题号id集合&#xff0c;两者并集后在全量题的集合中取反就是未答题号id 选型 基于场景的数据结构设计&#xff0c;有试过列表等&#xff0c;测试结果&#xff1…

Debian linux忘记root密码如何重置

重启电脑, 到下图再按 e 键 在页面中可以看到有个ro的行&#xff0c;在ro行的尾部&#xff0c;添加 rw init/bin/bas 3. ctrl X 启动系统&#xff0c;最后会进入命令行模式 4. 重设root密码&#xff0c;输入命令 passwd root&#xff0c;按照提示输入新密码并确认 5. 重启系…

Spring项目报错解读与全部报错详解

你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​ gitee https://gitee.com/Qiuner &#x1f339; 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 &#x1f604; (^ ~ ^) 想看更多 那就点个关注吧 我…

智能语音热水器:置入NRK3301离线语音识别ic 迈向智能家居新时代

一、热水器语音识别芯片开发背景 在科技的今天&#xff0c;人们对于生活品质的追求已不仅仅满足于基本的物质需求&#xff0c;更渴望通过智能技术让生活变得更加便捷、舒适。热水器作为家庭生活中不可或缺的一部分&#xff0c;其智能化转型势在必行。 在传统热水器使用中&#…

ProfibusDP主站转Modbus模块连接称重仪配置案例

在工业自动化领域&#xff0c;常常需要将不同协议的设备进行连接。比如&#xff0c;将ProfibusDP主站转Modbus模块&#xff08;XD-MDPBM20&#xff09;用于连接称重仪&#xff0c;可以实现不同设备之间的数据交换和通信。ProfibusDP主站转Modbus网关&#xff08;XD-MDPBM20&…

一站式AI服务平台:MaynorAI助您轻松驾驭人工智能

一站式AI服务平台&#xff1a;MaynorAI助您轻松驾驭人工智能 在当前的数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术已经成为推动各行各业创新发展的核心动力。为了更好地满足企业和个人用户对AI服务的需求&#xff0c;MaynorAI 作为一个领先的一站式调用国内…