聊一聊 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. 一个简单的测试例子
为了方便讲述两者的区别,使用 对象复活
来做测试。
- 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 析构已执行...");
}
}
}
- 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;
}
源码中有几个注意点:
- 如何判断一个对象为垃圾
gc 在标记时,将有根的对象mt的第一位设为 1
来表示当前已经标记过,即有用对象,未被标记的即为垃圾对象。
- 终结器线程真的被启动了吗
从简化的源码看,一旦有垃圾对象被送入到 终结器队列的 预备区
时,就会通过 GCToEEInterface::EnableFinalization(true)
启动终结器线程,所以在测试代码中加了 GC.WaitForPendingFinalizers();
就是为了等待终结器线程执行完毕然后才判断 Target,这样结果就会更加准确。
4. 切断逻辑在哪里
有些朋友会好奇那个 weakHandle.Target=null
的逻辑到底在 coreclr 的何处,这个比较简单,可以用 windbg 下 ba 断点即可,我们还是拿弱引用来举例,截图如下:
三:总结
WeakReference 的内部玩法有很多,更深入的理解还需要对 g_HandleTableMap
进行深度挖掘,后面有机会再聊吧,有时候dump分析还是挺苦逼的,需要对相关领域底层知识有一个足够了解,否则谈何修复呢?
聊一聊 C# 弱引用 底层是怎么玩的的更多相关文章
- Lua弱引用table
弱引用table 与python等脚本语言类似地,Lua也采用了自动内存管理(Garbage Collection),一个程序只需创建对象,而无需删除对象.通过使用垃圾收集机制,Lua会自动删除过期对 ...
- 【JVM从小白学成大佬】3.深入解析强引用、软引用、弱引用、幻象引用
关于强引用.软引用.弱引用.幻象引用的区别,在很多公司的面试题中经常出现,可能有些小伙伴觉得这个知识点比较冷门,但其实大家在开发中经常用到,如new一个对象的时候就是强引用的应用. 在java语言中, ...
- 【JVM学习】3.深入解析强引用、软引用、弱引用、幻象引用
来源:公众号:猿人谷 关于强引用.软引用.弱引用.幻象引用的区别,在很多公司的面试题中经常出现,可能有些小伙伴觉得这个知识点比较冷门,但其实大家在开发中经常用到,如new一个对象的时候就是强引用的应用 ...
- 【转载】objective-c强引用与弱引用
形象比喻蛮好玩的^_^ __weak 和 __strong 会出现在声明中 默认情况下,一个指针都会使用 __strong 属性,表明这是一个强引用.这意味着,只要引用存在,对象就不能被销毁 ...
- 分析ThreadLocal的弱引用与内存泄漏问题
目录 一.介绍 二.问题提出 2.1内存原理图 2.2几个问题 三.回答问题 3.1为什么会出现内存泄漏 3.2若Entry使用弱引用 3.3弱引用配合自动回收 四.总结 一.介绍 之前使用Threa ...
- Java的强引用、软引用、弱引用、虚引用
背景 工程中用到guava的本地缓存.它底层实现和API接口上使用了强引用.软引用.弱引用.所以温故知新下,也夯实下基础. 预备知识 先来看下GC日志每个字段的含义 Young GC示例解释 [GC ...
- Android性能优化之巧用软引用与弱引用优化内存使用
前言: 从事Android开发的同学都知道移动设备的内存使用是非常敏感的话题,今天我们来看下如何使用软引用与弱引用来优化内存使用.下面来理解几个概念. 1.StrongReference(强引用) 强 ...
- Android 弱引用和软引用
软引用 和 弱引用 1. SoftReference<T>:软引用-->当虚拟机内存不足时,将会回收它指向的对象:需要获取对象时,可以调用get方法. 2. WeakRefere ...
- 简单说说.Net中的弱引用
弱引用是什么? 要搞清楚什么是弱引用,我们需要先知道强引用是什么.强引用并不是什么深奥的概念,其实我们平时所使用的.Net引用就是强引用.例如: Cat kitty = new Cat(); 变量ki ...
- Lua table之弱引用
Lua采用了基于垃圾收集的内存管理机制,因此对于程序员来说,在很多时候内存问题都将不再困扰他们.然而任何垃圾收集器都不是万能的,在有些特殊情况下,垃圾收集器是无法准确的判断是否应该将当前对象清理.这样 ...
随机推荐
- Golang 开发常用代码片段
Struct to JsonString type BaseRequest struct { httpMethod string domain string path string params ma ...
- wordpress多站点设置,移除 多站点链接中的 /blog 前缀
★★★最近想给自己的wordpress添加多个站点,就查了查相关的设置方法,以下我亲自尝试可行 1.首先需要开始配置:在网站根目录下的 wp-config.php 添加: define('WP_ALL ...
- XML Schema(XSD)详解:定义 XML 文档结构合法性的完整指南
XML Schema描述了 XML 文档的结构.XML Schema语言也称为 XML Schema Definition(XSD). <?xml version="1.0" ...
- webapi创建WCF WebService+WCF WebService远程服务调用
首先需要引入soapcore包 这个包提供了所需的类和soap终结点中间件. 引入这个这个包之后,我们需要定义提供的服务. 这里我写了一个用于查询省份面积的服务. 省份信息服务 /// <sum ...
- SSL/TSL 总结
参考:https://blog.csdn.net/qq153471503/article/details/109524764 (一)生成CA证书 1.创建CA证书私钥openssl genrsa -a ...
- go 通过指针修改结构体小写字段的值
package main import ( "fmt" "unsafe" ) type W struct { b int32 c int64 } func ma ...
- Android 13 - Media框架(31)- ACodec(七)
关注公众号免费阅读全文,进入音视频开发技术分享群! 之前的章节中我们解了 input buffer 是如何传递给 OMX 的,以及Output buffer 是如何分配并且注册给 OMX 的.这一节我 ...
- Android 13 - Media框架(20)- ACodec(二)
关注公众号免费阅读全文,进入音视频开发技术分享群! 这一节开始我们就来学习 ACodec 的实现 1.创建 ACodec ACodec 是在 MediaCodec 中创建的,这里先贴出创建部分的代码: ...
- WPF插件之 - PropertyChanged.Fody使用详解
总目录文章目录总目录一.PropertyChanged.Fody是什么?二.PropertyChanged.Fody的安装三.PropertyChanged.Fody的功能1. 特性1 实现属性通知的 ...
- WPF ListBox 控件绑定 Binding
当我们需要用到循环的列表内容,并且模板化程度高的时候,建议使用 ListBox 来做绑定.XAML: <Window.DataContext> <local:VMTempTest/& ...