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

一:背景

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分析还是挺苦逼的,需要对相关领域底层知识有一个足够了解,否则谈何修复呢?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA 2024.1.4 的 AI Assistant 终于被激活了,我是这样干的!

ai assistant激活成功后&#xff0c;如图 ai assistant渠道&#xff1a;https://web.52shizhan.cn/activity/ai-assistant 在去年五月份的 Google I/O 2023 上&#xff0c;Google 为 Android Studio 推出了 Studio Bot 功能&#xff0c;使用了谷歌编码基础模型 Codey,Codey 是…

加载数据到mysql并解决原始数据乱码问题

查看linux上数据&#xff1a; 使用命令转换编码&#xff1a; iconv -f GBK -t UTF-8 toutiao.csv -o toutiao2.csv加载数据到mysql: load data local infile /root/toutiao2.csv INTO TABLE pdz FIELDS TERMINATED BY , LINES TERMINATED BY \r\n;

「ETL趋势」FDL数据开发支持版本管理、实时管道支持多对一、数据源新增支持神通

FineDataLink作为一款市场上的顶尖ETL工具&#xff0c;集实时数据同步、ELT/ETL数据处理、数据服务和系统管理于一体的数据集成工具&#xff0c;进行了新的维护迭代。本文把FDL4.1.8最新功能作了介绍&#xff0c;方便大家对比&#xff1a;&#xff08;产品更新详情&#xff1a;…

口碑最好的麦克风品牌有哪些?轻揭无线领夹麦克风哪个牌子好!

​无线领夹麦克风&#xff0c;无疑是现代音频技术的杰出代表。它摆脱了传统有线麦克风的束缚&#xff0c;让声音的传播更加自由、灵活。无论是追求极致音质的音乐爱好者&#xff0c;还是需要高效沟通的商务人士&#xff0c;无线领夹麦克风都能满足你的需求&#xff0c;让你的声…

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

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

高考完的假期想学c语言 要注意那些问题?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 大学教得少、内容落后时…

Python应用开发——30天学习Streamlit Python包进行APP的构建(11)

st.bokeh_chart 显示互动式虚化图。 Bokeh 是 Python 的一个图表库。此函数的参数与 Bokeh 的 show 函数的参数非常接近。有关 Bokeh 的更多信息,请访问 https://bokeh.pydata.org。 要在 Streamlit 中显示 Bokeh 图表,请在调用 Bokeh 的 show 时调用 st.bokeh_chart。 Fu…

SDIO学习(2)--SD卡 2.0协议

本文参考文档&#xff1a; 《SD Specifications Part 1 Physical Layer Simplified Specification Version 2.00》 1 SD卡简介 1.1 SD卡概念 1.2 SD卡外形和接口 Clk&#xff1a;时钟线&#xff0c;由SDIO主机产生 CMD&#xff1a;命令控制线&#xff0c;SDIO主机通过改…

flutter开发实战-ListWheelScrollView与自定义TimePicker时间选择器

flutter开发实战-ListWheelScrollView与自定义TimePicker 最近在使用时间选择器的时候&#xff0c;需要自定义一个TimePicker效果&#xff0c;当然这里就使用了ListWheelScrollView。ListWheelScrollView与ListView类似&#xff0c;但ListWheelScrollView渲染效果类似滚筒效果…

Oracle新特性速递:未来数据库技术的无限可能

文章目录 一、自治数据库&#xff1a;智能化与自动化的革命二、机器学习集成&#xff1a;智能数据分析的新境界三、区块链技术&#xff1a;确保数据完整性与透明性四、云原生数据库&#xff1a;灵活扩展与快速部署五、人工智能优化器&#xff1a;智能查询执行计划《Oracle从入门…

上海约瑟电器 JOBS(KG9001)拉绳开关 严格质量细节监控

基本信息 品牌&#xff1a;JOSEF约瑟 型号&#xff1a;JOBS(KG9001) 技术参数 动作角度&#xff1a;30 电源电压&#xff1a;220V 工作电压&#xff1a;380V 额定电流&#xff1a;5A 防护等级&#xff1a;IP65 复位方式&#xff1a;支持自动&#xff08;I&#xff09;和手动&am…

高考填报志愿三连问,从人格优势分析兴趣和专业

“我的兴趣爱好什么&#xff1f;” “我的理想是什么&#xff1f;” “我想成为什么&#xff1f;” ------高考填报志愿三连问&#xff01; 最近我在知乎上看过一个比较有意义的提问&#xff0c;提问的也是高考填报志愿的同学&#xff0c;自从高考后&#xff0c;每日三连问&…

Python基于逻辑回归分类模型、决策树分类模型、LightGBM分类模型和XGBoost分类模型实现车辆贷款违约预测项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 随着经济的发展和人民生活水平的提高&#xff0c;汽车消费在居民消费中所占比例逐渐增加&#xff0c;汽…

华为实训案例

案例下载 拓扑图 任务清单 &#xff08;一&#xff09;基础配置 根据附录1拓扑图、附录2地址规划表、附录3设备编号表&#xff0c;配置设备接口及主机名信息。 将所有终端超时时间设置为永不超时。 在全网Trunk链路上做VLAN修剪&#xff0c;仅允许必要的流量通过&#xff0…

什么是GPIO口,GPIO口最简单的input/output

目录 一&#xff0c;什么是GPIO口 二&#xff0c;GPIO内部结构 三&#xff0c;GPIO口工作模式 一&#xff0c;什么是GPIO口 1.GPIO口是通用输入输出端口&#xff08;General-purpose input/output&#xff09;的英文缩写&#xff0c;是所有的微控制器必不可少的外设之一&…

基于C++标准库实现定时器类

基于C标准库实现定时器类 定时器类是多线程编程中经常设计到的工具类 简单的定时器原理其实很简单&#xff08;是不是有点GNU is not unix的味道;&#xff09;&#xff1a; 创建一个新线程在那个线程里等待等待指定时长后做任务 python标准库中就有这么一个定时器类&#xf…

车载测试工程师在行业中有哪些挑战需要面对?

车载测试工程师在行业中面临着多方面的挑战&#xff0c;这些挑战涵盖了技术、安全、法规以及市场环境等多个层面。 1. 技术挑战&#xff1a; 复杂性与集成性&#xff1a;现代汽车系统由众多模块和子系统组成&#xff0c;包括发动机控制、安全系统、娱乐系统、导航系统等。这些系…

查普曼大学团队使用惯性动捕系统制作动画短片

道奇电影和媒体艺术学院是查普曼大学的知名学院&#xff0c;同时也是美国首屈一指的电影学院之一&#xff0c;拥有一流电影制作工作室。 最近&#xff0c;道奇学院的一个学生制作团队接手了一个项目&#xff0c;该项目要求使用真人动作、视觉效果以及真人演员和CG角色之间的互动…

鸿蒙NEXT开发知识:工具常用命令—ohpm config

设置ohpm用户级配置项。 命令格式 ohpm config set <key> <value> ohpm config get <key> ohpm config delete <key> ohpm config list 说明 配置文件中信息以键值对<key> <value>形式存在。 功能描述 ohpm 从命令行和 .ohpmrc 文件中…

AI专区上新啦!豆包、通义、360AI、天工AI、澜舟智库等入驻麒麟软件商店

继百度文心一言、讯飞星火、博思白板、雅意等AI产品上架后&#xff0c;麒麟软件商店再添新成员&#xff01;近日&#xff0c;豆包、通义、360AI搜索、360智脑、360智绘、昆仑万维天工AI、澜舟智库等重磅AI产品登陆麒麟软件商店人工智能专区&#xff0c;涵盖了AI对话、AI写作、A…