- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
C# 采用基于代的回收机制,并使用了更复杂的 链式跟踪算法 来识别对象是否为垃圾.
截至到.NET 8,GC触发的原因有18种 。
enum gc_reason
{
reason_alloc_soh = 0,//小对象堆,快速分配预算不足
reason_induced = 1,//主动触发GC,没有关于压缩和阻塞的选项
reason_lowmemory = 2,//操作系统发出内存不足通知信号
reason_empty = 3,//操作系统内存耗尽
reason_alloc_loh = 4,//大对象堆,快速分配预算不足
reason_oos_soh = 5,//小对象堆段,慢速分配预算不足
reason_oos_loh = 6,//大对象堆段,慢速分配预算不足
reason_induced_noforce = 7, // it's an induced GC and doesn't have to be blocking.
reason_gcstress = 8, // this turns into reason_induced & gc_mechanisms.stress_induced = true
reason_lowmemory_blocking = 9,
reason_induced_compacting = 10,
reason_lowmemory_host = 11,//主机发出内存不足通知信号
reason_pm_full_gc = 12, // provisional mode requested to trigger full GC
reason_lowmemory_host_blocking = 13,
reason_bgc_tuning_soh = 14,
reason_bgc_tuning_loh = 15,
reason_bgc_stepping = 16,
reason_induced_aggressive = 17,
reason_max
};
根据命名总体分为5类 。
https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gc.h 。
以非并发工作站GC为蓝本,投石问路,先介绍最简单的一种,后面文章再展开。说人话就是挖坑待埋.
https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/garbage-collection.md 。
在mark_phase阶段,CLR会从根对象出发,寻找所有可到达对象,也就是依旧存在引用关系的对象。主要历经以下几个步骤 。
当标记堆栈中,不存在未访问的对象时。即代表遍历操作完成 。
注意:pinned与marked标记都是在mrak_phase阶段被设置,plan_phase阶段被清除。在正常情况下,是不会存在objectheader与methodtable中的.
标记阶段最重要的任务,就是标记引用根(root),对于引用跟踪算法而言,这是万物的起点。 C#中,root分为四种,组成了标记阶段要扫描的所有root 。
查找stack上的根对象,如果实时扫描stack与寄存器,在面对高频次的小对象GC中会显得力不从心,因此,CLR团队采用了更高性能的查询策略.
static void Main(string[] args)
{
var name = "lewis.liu";
var age = 155;
var person=new Person();
Debugger.Break();
}
static void Main(string[] args)
{
Timer timer = new Timer((obj) =>
{
Console.WriteLine($"我是 Timer 线程,当前时间:{DateTime.Now}");
}, null, 0, 100);
var person = new Person();
GC.Collect();
Console.WriteLine("GC已触发。。。");
Console.ReadLine();
}
使用.NET Core 2.1 分别以Debug与Release来运行。会发现惊喜! 。
1. Debug 2. Release 。
简单来说,不同版本的JIT对作用域有不同的判断策略,.NET Core 2.1中,会认为在触发GC.Collect()之前,timer与person都不再被stack所引用,因此会被GC所释放掉。这个过程叫做激进式根回收 。
.NET Core 3 之后已经修复此问题.
带有析构函数的对象,会单独在终结器队列中持有一份对这个对象的引用 。
internal class Program
{
static void Main(string[] args)
{
Run();
Debugger.Break();
GC.Collect();
Console.WriteLine("11");
Debugger.Break();
}
static void Run()
{
var person = new Person();
}
}
class Person
{
public int Id { get; set; }
~Person()
{
Console.WriteLine("析构已执行");
}
}
通过GCHandle.Alloc或者fix产生的固定,以及CLR内部固定的对象 它们全部存储在CLR内部的全局句柄映射表中 ,可以使用!gchandles命令查看 。
GCHandleType总共有五种,GC只会对Normal,Pinned视为root, 。
还有一个fixed关键字,也可以固定对象。两者结果相同,但是root不同,使用GCHandle时是句柄表,使用fixed关键词是标记堆栈 。
internal class Program
{
static void Main(string[] args)
{
Run();
Debugger.Break();
}
static void Run()
{
var person = new Person();
GCHandle.Alloc(person, GCHandleType.Normal);
}
}
class Person
{
}
为了解决跨代引用问题产生的卡表,卡包 。
图中举例了一个常见的情况 。
那么问题来了,CLR将SOH分为0代,1代,2代。目的就是想根据生命周期的不同。对不同的代设置不同的GC频率。 现在好了,我回收0代内存,还因为被1代和2代引用。导致GC标记的时候,要遍历整个对象图 。
那内存分代图个什么?
为了处理跨代引用的问题,CLR团队引入了remembered sets(记忆集)。简单来说,就是记录对象被哪些对象所引用。拿空间换时间 。
太阳底下没有新鲜事,JVM也是使用记忆集解决跨代引用的问题 。
举个例子,回收1代内存。在某个时机(对象创建,指针移动),冗余了引用关系。那么只需要扫描对象的GC Root与remembered sets 便可轻松得知对象是否能被回收 。
思考一个问题,这里还有优化空间吗?
上面可以看到,当回收1代内存时,要同时维护与0代和2代的关系。实现记忆集并不是一件容易的事 。
思考一下单链表与双链表的维护成本 。
那么如何减少维护成本呢?CLR团队做了一个决定:”回收N代意味着回收该代以及N-1代的对象“ 简单来说就是:
这样的话,记忆集只需要维护比对象代更老的关系,大大减轻了维护成本 。
将每个对象的引用存储在记忆集中,记忆集虽然很小(只包含了更老对象的引用),但在大型系统中,几万甚至十几万个对象都是正常的现象,那么意味着也会有同等数量的记忆集。维护数量如此庞大的记忆集,将会为GC管理带来极大的开销 因此,CLR团队必须做出一些妥协,在性能与准确性之间进行权衡 。
这样虽然会导致即使只有一个记忆集,也必须访问card中的所有对象。但也极大提高了GC的性能 。
有点类似涂抹算法 源码地址:https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/amd64/JitHelpers_FastWriteBarriers.asm 。
在.NET Runtime中,一个bit位(clean/dirty代表0/1)对应256byte(64位)与128byte(32位) . 以64位系统为例,1byte可以表示8256=2048byte的存储区域。 一个卡表字的大小为4字节(DWORD).它所覆盖的大小为42048=8192byte 。
想象一下,一个占用内存32gb的,超过5万个对象的服务器。它们的扫描量有多大。这会严重拖慢GC时间 。
因此,基于Card Tables的思路,CLR团队又引入了Card Bundles的概念。 以64位系统为例,每个bit位代表32个卡表字。因此一个bit覆盖范围为328192=262144,256kb 一个卡包位占用4字节空间,因此一个卡包可以覆盖256kb8*4=8192kb ,8mb空间.
这时候再算一下1gb内存使用量的web服务器,1024/8=128 .扫描量是不是就大大减少了。 其核心思路类似涂抹算法+二级缓存 。
static void Main(string[] args)
{
var address = CreatedAddress();
GC.Collect();//gc后 address 升为1代
Debugger.Break();
var person = CreatedPerson();
person.Address = address;//0代的person引用1代的address
Debugger.Break();
}
static Address CreatedAddress()
{
var address = new Address()
{
City = "xxx",
County = "yyy"
};
return address;
}
static Person CreatedPerson()
{
var person = new Person()
{
Address = null,
};
return person;
}
最后此篇关于.NETCoreGC标记阶段(mark_phase)底层原理浅谈的文章就讲到这里了,如果你想了解更多关于.NETCoreGC标记阶段(mark_phase)底层原理浅谈的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这是贪吃蛇游戏的部分代码。我想做的是制作关卡(大约3个),如果分数达到一定的分数(100或200),关卡就会改变。 在这段代码中,我尝试让分数达到 100 时进入第 2 阶段。但正如我编码的那样,它只
我是移相器新手。我开始看 youtube 系列,我首先有问题。我的背景图片无法加载。我有这个js代码 /* global Phaser */ var game = new Phaser.Game(12
我有一个包含 2 个阶段的应用程序,我不希望用户关闭第二个阶段,只需将其图标化即可。 目前我正在使用 oncloseRequest 处理程序来最小化窗口 - secondaryStage.setOnC
现在,我有一台运行服务器的基本LAMP配置。生产服务器是slicehost。但是我想知道将代码/数据库实例推送到阶段dev> stage> production的最佳方法是什么。它与您创建阶段的方式有
我在舞台上有一个场景。场景的宽度为 337.0 像素。但是,当我将它添加到舞台时,舞台的大小为 337.6 像素,由于 0.6 像素的差异,在屏幕的右边缘留下了一个白色间隙。 我尝试使用 stage.
我有这个未修饰的窗口: public static void initStartPage(final Stage primaryStage) { final Stage startPa
有什么方法可以在 Maven 构建中执行特定阶段。例如,如果我只想运行那些在预集成阶段执行的插件,Maven 是否提供了一种方法来做到这一点? e.g. mvn pre-integration-pha
仅在构建特定分支时如何运行构建步骤/阶段? 例如,仅当分支名为 deployment 时才运行部署步骤,其他所有内容保持不变。 最佳答案 在声明性管道语法中执行相同的操作,下面是一些示例: stage
我有一个简单的查询,试图在Hive 0.14中运行: select sum(tb.field1), sum(tb.field2), tb.month from dbwork.mytable tb gr
在 Mercurial 中,我经常使用 secret 变更集来跟踪我对尚未准备好推送的内容的工作。然后,如果我需要对某些文件进行紧急更改,我可以更新到公共(public)修订版,进行更改并推送它,而不
我一直在为 Heroku 的新附加组件工作,目前它是 alpha 阶段。因此,目前,我无法在我创建的应用程序上添加该附加组件,因为没有按钮可供我添加它。有人可以向我指出一些可以帮助我解决问题的资源吗?
我有 2 个线程正在运行,一个正在监听 soket 等待命令,另一个启动 javafx 应用程序 public class GraphicInterface extends Application i
在我的 Java Fx 应用程序中,我创建了两个阶段。第一阶段是主 Controller 类 HomeController 中的默认阶段。第二个 AddNewEmailController 是通过调用
我正在编写一个简单的 JavaFX 应用程序,它具有三个阶段:登录、注册 (Anmeldung) 和欢迎 (Anwendung)。 抱歉采用德语命名! 我已经在 App 类中创建了每个舞台及其场景,在
问题是我正在使用 jQuery("form")[0].reset(); 在需要时重置表单。此方法正在将形式重置到初始阶段。这里初始阶段的意思是“表单第一次加载到页面时带有一些值的阶段”。 但我需要的是
我有一个带有 pre-integration-test 和 post-integration-test 阶段的 Maven POM,如下所示。 start-server pre-in
我遇到一个错误,我已经为网络制作了一个 UIPageController,但我似乎无法找到它的问题,只有一个错误,请帮忙。代码如下 - 更多代码点播。 @interface ContentViewCo
考虑在其中放置一些文本的大型 (2000x1000) 舞台。舞台缩小到 1000x500,使文本不可读。然后我们尝试通过放大来放大文本。 预期:文本应该在某个时候再次变得可读。 实际:无论我们放大多少
试图在网页中居中 KineticJS 阶段。 尝试过: 但它集中在舞台的左侧,而不是舞台的中间。我错过了什么? 最佳答案 margin:auto 可以对齐这个div中心 关于htm
我正在 jboss 中部署一个简单的 Web 应用程序,其中包含一个 servlet、一个 jsp 文件和一个 easy EJB。这是 servlet 的代码: package webejb; imp
我是一名优秀的程序员,十分优秀!