.Net 7 GC垃圾回收对象跨代简析
楔子
很久没有发.Net 7 CLR的文章了,本篇来看下之前研究过的跨代的两个函数
问题
跨代对象的引用的定义是:比如第一代的某个对象(ObjOne)的成员(MemberOne)引用了第0代的某个对象(ObjTwo),如果此时GC垃圾回收的时候,回收的是第0代。假设第0代上没有对象引用ObjTwo,ObjTwo可能会被回收掉。而此时,因为对象被回收了,所以MemberOne引用的就是空值。这会导致出错。
为了解决这个问题,Card_table出现了。
代码
先上一段简单的代码,作为例子说明
internal class Program
{
class A
{
public Program PMA=null;
}
static void Main(string[] args)
{
Console.WriteLine("Tian Xia Feng Yun Chu Wo Bei!");
Console.ReadLine();
A a=new A();
GC.Collect();
Program PM =new Program();
a.PMA = PM;
GC.Collect();
}
}
对象a因为被GC过一次,所以升代了。PM对象在第一次GC之后,第二次GC之前,所以它一定是第0代。不同代的对象a和PM,对象a的成员PMA被赋值为了0代的PM对象,所以造成了对象引用的跨代。
Card_table
卡片表(card_table),主要是在第1代某个对象的成员引用第0代某个对象的时候,设置第1代对象的成员在卡片表里面相对应的字节为0XFF,二进制为1111 1111。
这里,CLR规定了卡片表一个位(bit)为对应256个字节也就是2的8次方。8个bit位也就是一个字节则对应了2的11次方个字节,十进制的2048个字节。如果再升级下,比如,4个字节对应了2的13次方个字节,十进制是8192个字节。
设置卡片表
先看一段代码:
inline
void gc_heap::set_card (size_t card)
{
size_t word = card_word (card);
card_table[word] = (card_table [word] | (1 << card_bit (card)));
#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES
// Also set the card bundle that corresponds to the card
size_t bundle_to_set = cardw_card_bundle(word);
card_bundle_set(bundle_to_set);
dprintf (3,("Set card %Ix [%Ix, %Ix[ and bundle %Ix", card, (size_t)card_address (card), (size_t)card_address (card+1), bundle_to_set));
#endif
}
局部变量card就是一个字节的长度,局部变量word则是表示四个字节的长度。card_bit是一个字节的长度模(%)上2的五次方的结果。设置card_table[word]可以看到它的值实际上是它自身与(|)上1左移card_bit的结果。
寻找卡片表
同样先上代码
BOOL gc_heap::find_card(uint32_t* card_table,
size_t& card,
size_t card_word_end,
size_t& end_card)
{
uint32_t* last_card_word;
uint32_t card_word_value;
uint32_t bit_position;
if (card_word (card) >= card_word_end)
return FALSE;
// Find the first card which is set
last_card_word = &card_table [card_word (card)];
bit_position = card_bit (card);
{
card_word_value = (*last_card_word) >> bit_position;
}
if (!card_word_value)
{
do
{
++last_card_word;
}
while ((last_card_word < &card_table [card_word_end]) && !(*last_card_word));
if (last_card_word < &card_table [card_word_end])
{
card_word_value = *last_card_word;
}
else
{
// We failed to find any non-zero card words before we got to card_word_end
return FALSE;
}
#endif //CARD_BUNDLE
}
// card is the card word index * card size + the bit index within the card
card = (last_card_word - &card_table[0]) * card_word_width + bit_position;
do
{
bit_position++;
card_word_value = card_word_value / 2;
end_card = (last_card_word - &card_table [0])* card_word_width + bit_position;
} while (card_word_value & 1);
end_card = (last_card_word - &card_table [0])* card_word_width + bit_position;
return TRUE;
}
这里面的整个过程就是设置扫描档范围,当发现0代的某个对象在卡片表相对应的位置设置了,那么这个对象将不会被回收。虽然它没有被0代其它对象引用。
局部变量 last_card_word 是卡片表四字节处的地址。 局部变量bit_position是card_bit计算的结果。
card_word_value = (*last_card_word) >> bit_position;
上面这句就是找到真正的card_table[card_word(card)]所在的卡片标记值。因为set_card到时候,设置了位于1左移card_bit的值。 ++last_card_word实际上是找到卡片表结束的地址。
card = (last_card_word - &card_table[0]) * card_word_width + bit_position;
卡片表的1字节表示公式,具体为:结束四字节卡片表的地址减去四字节卡片表的起始地址然后乘以三十二,再加上被去除掉的余数,结果就是卡片表1字节地址
end_card = (last_card_word - &card_table [0])* card_word_width + bit_position;
这个bit_postion经过循环之后,找到的结束卡片表1字节的地址。
原理
它的原理主要是通过遍历当前卡片表所表示的范围,其实范围,和结束范围。在这个里面搜索第0代被其它代所引用的对象。然后进行标记,不至于被误回收。
结尾
作者:江湖评谈(公众号同名)
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
相关文章
- 新 – 适用于 Amazon Elastic File System (EFS) 的不频繁访问存储类
- 使用来自 AWS Serverless Application Repository 的组件构建无服务器应用程序
- 最新 AWS 精英简介 – 2019 年冬
- 了解 AWS 服务和解决方案 — AWS 3 月在线技术讲座
- 新增功能 – 适用于网络负载均衡器的 TLS 终止
- FOSDEM 2019 大会开源热
- python Socket服务器转发
- Amazon WorkLink – 单击一下,即可通过移动设备安全地访问内部网站和应用程序
- 通过 AWS CodeCommit 将 Phabricator 与 AWS CodePipeline 集成
- AWS 备份 – 自动化和集中管理您的备份
- Java web入门
- 通过乐鑫原生 SDK-ESP IDF 连接 AWS IoT 平台
- 幕后英雄、毯下巨人 – 支持 AWS re:Invent 2018 大会的 CenturyLink 网络
- 招募在 AWS 工作的开源贡献者
- 使用 AWS IoT 按钮实现按需 VPN 访问
- 2018 年十大热门文章
- 了解 AWS 服务和解决方案 – AWS 1 月在线技术讲座
- 在 EKS 上使用 Kubernetes Service Catalog 和 AWS Service Broker
- KubeCon 西雅图 2018 年回顾
- 回顾:2018 年 re:Invent 大会上的开源