55.GC的主要耗时及地址变化
55.1 题目
GC 的主要耗时在哪里?GC 后引用数据地址是否会改变?如何保证引用稳定?
55.2 深入解析
GC 的主要耗时
标记阶段(Mark):GC 扫描所有根对象(Root)并递归标记可达对象,这部分涉及遍历大量内存引用,开销较大。清扫阶段(Sweep):GC 清理未被标记的对象并回收内存,可能触发内存碎片整理,对大堆内存也有一定开销。压缩/整理阶段(Compact)(针对需要时):在压缩模式下,GC 会移动存活对象以合并空闲块,这一步需要复制和更新所有对象的引用,成本最高。
GC 后对象地址是否改变
- 小代和常规对象:在多数托管模式中,存活对象会被移动(Compact)到新位置,以减少碎片化,因此其内存地址会改变。
大对象堆(LOH):默认情况下,LOH 对象不参与压缩,地址通常保持不变,除非启用了 LOH 压缩。
如何保证引用稳定
- 降低GC的压力:尽量不GC或者降低GC的压力,通过避免闭包、装箱、频繁new等等GC优化手段可以保持稳定
句柄/句柄表(Handles):.NET 使用句柄间接引用对象,GC 移动对象时只需更新句柄表,无需外部修改引用。PIN(固定)对象:可以通过GCHandle.Alloc(obj, GCHandleType.Pinned)将对象固定在内存中,避免移动,但应谨慎使用以防碎片化。- 避免直接使用指针:若需与非托管代码交互,使用
GCHandle或fixed语句在作用域内固定对象,保证指针有效。
55.3 答题示例
“GC 的耗时主要集中在标记和清扫阶段,尤其是当需要压缩堆时,会移动存活对象,开销最大。大对象堆默认不压缩,所以 LOH 对象地址一般不变,而小对象会在 Compact 时改变地址。CLR 使用句柄表来间接引用对象,GC 更新句柄后,外部引用依旧有效;如果必须取直接指针,可以用
GCHandleType.Pinned或fixed关键字将对象固定,防止移动。”
55.4 关键词联想
- 标记-清扫(Mark-and-Sweep)
- 堆压缩(Heap Compacting)
- 大对象堆(LOH)
- 句柄表(GCHandle)
- 对象固定(Pinning)
- 内存碎片化
- 标记根对象(Roots)
- 托管 vs 非托管互操作 (P/Invoke)
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com