当前位置: 首页 > 编程语言 > C#

C#垃圾回收器如何找到唯一引用为内部指针的对象?分享

时间:2023-04-11 10:48:37 C#

C#垃圾回收器如何找到被唯一引用为内部指针的对象?在C#中,据我所知,ref和out参数只是传递关联值的原始地址。地址可以是指向数组中的元素或对象中的字段的内部指针。如果发生垃圾收集,对对象的唯一引用可能是通过内部指针之一,如:公共类Foo{公共int字段;publicstaticvoidIncrement(refintx){System.GC.Collect();x=x+1;控制台.WriteLine(x);}publicstaticvoidMain(){增量(refnewFoo().field);在这种情况下,GC需要找到对象的开头并把整个对象视为可达。它是怎么做到的?是否必须扫描整个堆以查找包含该指针的对象?这似乎很慢。垃圾收集器将有一种快速的方法从托管内部指针找到对象的开始。从那里开始,它显然可以在扫描阶段将对象标记为“参考”。Microsoft收集器没有代码,但他们会使用类似于Go的跨度表的东西,它可以快速查找内存的不同“跨度”,您可以通过键入指针的最高有效X位来选择跨度。从那里他们使用每个跨度包含X个相同大小的对象的事实来快速找到您拥有的对象的标题。这几乎是一个O(1)操作。显然,Microsoft堆将有所不同,因为它是按顺序分配的,而不管对象大小,但它们将具有某种O(1)查找结构。https://github.com/puppeh/gcc-6502/blob/master/libgo/runtime/mgc0.c//否则查阅跨度表以找到开始。//(手动内联MHeap_LookupMaybe的副本。)k=(uintptr)obj>>PageShift;x=k;x-=(uintptr)runtime_mheap.arena_start>>PageShift;s=runtime_mheap.spans[x];如果(s==nil||kstart||(constbyte*)obj>=s->limit||s->state!=MSpanInUse)returnfalse;p=(byte*)((uintptr)s->startelemsize;int32i=((constbyte*)obj-p)/size;obj=p+i*size;}请注意,.NET垃圾收集器是一个复制收集器,因此无论何时在垃圾收集周期中移动对象,都需要更新托管/内部指针。GC将根据JIT时间参数知道堆栈内部指针在每个堆栈帧中的位置已知的方法来执行此操作。你的代码编译为IL_0001:newobj实例voidFoo::.ctor()IL_0006:ldfldaint32Foo::'field'IL_000b:调用voidFoo::Increment(int32&)AFAIK,ldflda指令创建对包含该字段的对象的引用只要该地址在堆栈上(直到调用完成)。垃圾收集器具有三个基本步骤:标记所有仍然存在的对象。收集未标记为活动的对象。压缩内存。您关心的是第1步:如何GC是否确定它不应该收集ref和paras后面的对象?当GC进行收集时,它从一个没有对象被认为是存活的状态开始。然后它从根开始引用,并将所有这些对象标记为活动的。根引用是堆栈和静态字段上的所有引用。然后GC递归地进入标记的对象并将引用它们的所有对象标记为活动的。重复此过程,直到找不到未标记为活动的对象。这个操作的结果是一个对象图。ref或out参数在堆栈上具有引用,因此GC会将相应的对象标记为活动对象,因为堆栈是对象图的根。在该过程结束时,只有内部引用的对象不会被标记,因为在根引用中没有指向它们的路径。这也会处理任何循环引用。这些对象被认为是死的,将在下一步中收集(包括调用终结器,即使不能保证)。最后,GC会将所有活动对象移动到堆开头的连续内存区域。其余的内存将用零填充。这简化了创建新对象的过程,因为它们的内存总是可以在堆的末尾分配,并且所有字段都已经有默认值。GC确实需要一些时间来完成所有这些工作,但由于一些优化,它仍然可以相当快地完成。一种这样的优化是将堆分成几代。所有新分配的对象都是第0代。所有在第一个集合中存活的对象都是第1代,依此类推。仅当通过收集低代释放了足够的内存时,才会收集高代。所以,不,GC并不总是必须扫描整个堆。您必须考虑到虽然收集需要一些时间,但分配新对象(比垃圾收集更频繁)比在其他实现中要快得多,在其他实现中,堆看起来更像瑞士奶酪,您需要一些时间来处理新对象洞足够大(你仍然需要初始化)。以上是C#学习教程:C#垃圾回收器如何找到被引用为内部指针的唯一对象?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: