当前位置: 首页 > news >正文

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

相关文章:

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

一&#xff1a;背景 1. 讲故事 最近在分析dump时&#xff0c;发现有程序的卡死和WeakReference有关&#xff0c;在以前只知道怎么用&#xff0c;但不清楚底层逻辑走向是什么样的&#xff0c;借着这个dump的契机来简单研究下。 二&#xff1a;弱引用的玩法 1. 一些基础概念 …...

蜘蛛池规矩采集优化与运用技巧 什么是蜘蛛池/SEO蜘蛛池怎么养?(蜘蛛池新手入门虚良SEO)

作为一名网络内容修改&#xff0c;我常常需求从各种网站上收集文章并转载到咱们的网站上。而在这个过程中&#xff0c;我深深感受到了蜘蛛池对我的帮助。今日&#xff0c;我就来共享一下我对蜘蛛池收集规矩的亲自感受。 归纳 本文将分9个方面具体介绍蜘蛛池收集规矩的长处和运…...

SerDes介绍以及原语使用介绍(1)OSERDESE2

文章目录 前言&#xff1a;为什么需要serdes一、OSERDESE2框图二、OSERDESE2端口信号二、OSERDESE2原语参数三、OSERDESE2时序3.1、SDR模式3.2、DDR模式3.3、DDR模式下三态传输 前言&#xff1a;为什么需要serdes 需要 SerDes&#xff08;串行器/解串器&#xff09;主要是为了…...

基于单片机和组态王的温度监控系统的设计

摘 要 : 介绍了以 MSP430 单片机为核心 , 建立基于 DS18B20 和组态王的温度采集和监控系统。主要研究了单片机和组态王的通用通讯协议。按照 KingView 提供的通信协议 , 设计组态王与单片机的通信程序 , 实现了组态王与M SP430 单片机的直接串行通讯。在中药提取装置的…...

unity 导入的模型设置讲解

咱们先讲Model这一栏 Model Scene&#xff1a;场景级属性&#xff0c;例如是否导入灯光和照相机&#xff0c;以及使用什么比例因子。 Scale Factor&#xff1a;缩放因子&#xff08;也就是模型导入后大小如果小了或者大了在这里直接改是相当于该模型的大小的&#xff0c;而且在…...

汽车 vSOC安全运营管理平台开发解决方案

汽车 vSOC 安全解决方案 一、引言 随着汽车行业的快速发展,汽车的智能化和互联化程度越来越高,汽车网络安全问题也日益凸显。汽车 vSOC(Vehicle Security Operations Center)作为汽车网络安全的重要组成部分,其作用越来越受到重视。本方案旨在提供一套可实施落地的汽车 vS…...

python 第三方库

一、什么是第三方库 python的三方库指的是&#xff0c;需要通过pip install 安装后才能使用的 python 工具 三方库有很多&#xff1a; 做web自动化测试的库&#xff1a;selenium单元测试框架&#xff1a;pytest、unittest做app自动化测试&#xff1a;Python-Appium-Client做接…...

VMware Workstation环境下,DHCP服务的安装配置,用ubuntu来测试

需求说明: 某企业信息中心计划使用IP地址17216.11.0用于虚拟网络测试,注册域名为xyz.net.cn.并将172.16.11.2作为主域名的服务器(DNS服务器)的IP地址,将172.16.11.3分配给虚拟网络测试的DHCP服务器,将172.16.11.4分配给虚拟网络测试的web服务器,将172.16.11.5分配给FTP服务器…...

CSS实现文字颜色渐变

直接上代码和效果图&#xff1a; <p class"linecolor">文字颜色渐变</p><style type"text/css">.linecolor{font-size: 30px;background-image:-webkit-linear-gradient(bottom,red,#fd8403,yellow);-webkit-background-clip:text;-web…...

《每天5分钟用Flask搭建一个管理系统》第4章:模板渲染

第4章&#xff1a;模板渲染 4.1 模板的概念和使用 模板是一种用于生成输出的方法&#xff0c;它允许您将Python代码和HTML标记混合在一起&#xff0c;从而创建动态网页。 示例代码&#xff1a;基本模板 <!-- templates/home.html --> <!DOCTYPE html> <html…...

逆向学习汇编篇:指令的操作

本节课在线学习视频&#xff08;网盘地址&#xff0c;保存后即可免费观看&#xff09;&#xff1a; ​​https://pan.quark.cn/s/660c759dea95​​ 在逆向工程中&#xff0c;深入理解汇编语言的指令操作是至关重要的。汇编指令是计算机硬件与软件之间的桥梁&#xff0c;它们直…...

VB.net实战(VSTO):VSTOwpf体验框架打包教程

如果是考虑到Wps用户较多&#xff0c;就不建议采用侧边栏的形式 只是个体验框架&#xff0c;界面未作美化&#xff0c;office的用户可以用任意一种窗体&#xff0c;喜欢那个界面就写那个界面&#xff0c;wps的侧边栏只能弹出一部分&#xff0c;每次需要的手动拖动。 打包了案例…...

Jquery 获得Form下的所有text、checkbox等表单的值

Jquery使用表单我主要是想获得某一个表单下的所有text获得checkbox的值: 可以这样写: var parameter{}; $("input[typetext]",document.forms[0]).each(function(){ alert(this.name); }); 获得所有名为hobby的选中的checkbox的值和form2下的所有text的值 function s…...

stl之string

构造函数 void test1() {string s1;//不传参cout << s1 << endl;string s2("123456");cout << s2 << endl;string s3(s2);cout << s3 << endl;string s4(s2, 1, 5);cout << s4 << endl;string s5("123456&quo…...

Vue3学习笔记<->nginx部署vue项目

安装nginx vue项目通常部署到nginx上&#xff0c;所以先安装一个nginx。为了方便安装的是windows版nginx&#xff0c;解压就能用。 项目参考上一篇文章《Vue3学习笔记&#xff1c;-&#xff1e;创建第一个vue项目》《Vue3学习笔记&#xff1c;-&#xff1e;创建第一个vue项目》…...

使用 WebGL 创建 3D 对象

WebGL Demohttps://mdn.github.io/dom-examples/webgl-examples/tutorial/sample5/index.html 现在让我们给之前的正方形添加五个面从而可以创建一个三维的立方体。最简单的方式就是通过调用方法 gl.drawElements() 使用顶点数组列表来替换之前的通过方法gl.drawArrays() 直接…...

百度地图3d区域掩膜,最常见通用的大屏地图展现形式

需求及效果 原本项目使用的是百度地图3.0,也就是2d版本的那个地图&#xff0c;客户不满意觉得不够好看&#xff0c;让把地图改成3d的&#xff0c;但是我们因为另外的系统用的都是百度地图&#xff0c;为了保持统一只能用百度地图做 经过3天的努力&#xff0c;最后我终于把这个…...

小区物业管理收费系统源码小程序

便捷、透明、智能化的新体验 一款基于FastAdminUniApp开发的一款物业收费管理小程序。包含房产管理、收费标准、家属管理、抄表管理、在线缴费、业主公告、统计报表、业主投票、可视化大屏等功能。为物业量身打造的小区收费管理系统&#xff0c;贴合物业工作场景&#xff0c;轻…...

C++实现一个简单的Qt信号槽机制

昨天写这个文章《深入探讨C的高级反射机制&#xff08;2&#xff09;&#xff1a;写个能用的反射库》的时候就在想&#xff0c;是不是也能在这套反射逻辑的基础上&#xff0c;实现一个类似Qt的信号槽机制&#xff1f; Qt信号槽机制简介 所谓的Qt的信号槽&#xff08;Signals …...

微信小程序常用的传值

1.通过 URL 传参 在页面跳转时&#xff0c;可以在 URL 中携带参数进行传递&#xff0c;然后在目标页面的 onLoad 生命周期中获取参数。 // 在页面 A 中跳转到页面 B 并传递参数 wx.navigateTo({url: /pages/detail/index?id123 });// 在页面 B 的 onLoad 生命周期中获取参数…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

PLC入门【4】基本指令2(SET RST)

04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C)&#xff0c;从 文件 - 主画面&#xff0c;“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...