当垃圾收集器在堆中移动数据时,引用是否会更新?

 湛蓝天空jk 发布于 2022-12-04 02:23

我读到GC(垃圾收集器)出于性能原因在Heap中移动数据,我不太明白为什么因为它是随机存取内存,可能是为了更好的顺序访问但是我想知道当这样的移动发生时Stack中的引用是否得到更新在堆里.但也许偏移地址保持不变,但垃圾收集器会移动其他部分数据,但我不确定.

我认为这个问题与实现细节有关,因为并非所有垃圾收集器都可以执行此类优化,或者他们可能会执行此操作但不更新引用(如果它是垃圾收集器实现中的常见做法).但我想得到一些特定于CLR(公共语言运行时)垃圾收集器的总体答案.

还有,我也读埃里克利珀的"参考文献都没有地址"的文章在这里,和下面的段落搞糊涂了一点:

如果您认为引用实际上是一个不透明的GC句柄,那么很明显,要找到与句柄关联的地址,您必须以某种方式"修复"该对象.您必须告诉GC"直到另行通知,具有此句柄的对象不得在内存中移动,因为有人可能有一个指向它的内部指针".(有多种方法可以做到这一点超出了这种熨平板的范围.)

听起来像参考类型,我们不希望移动数据.那么我们在堆中存储了什么,我们可以移动以进行性能优化?也许我们存储的类型信息?顺便说一下,如果你想知道那篇文章是关于什么的,那么Eric Lippert正在比较对指针的一点点,并尝试解释如何说引用只是地址,即使它是C#如何实现它也可能是错误的.

而且,如果我上面的任何假设是错误的,请纠正我.

2 个回答
  • 看看C++\CLI在Action中,有一个关于内部指针与固定指针的部分:

    C++/CLI提供了两种解决此问题的指针.第一种称为内部指针,由运行时更新,以反映每次重新定位对象时指向的对象的新位置.内部指针指向的物理地址永远不会保持不变,但它始终指向同一个对象.另一种称为钉扎指针,它可以防止GC重新定位对象; 换句话说,它将对象固定到CLR堆中的特定物理位置.通过一些限制,可以在内部指针,固定指针和本机指针之间进行转换.

    从那里,您可以得出结论,引用类型确实在堆中移动,并且它们的地址确实发生了变化.在Mark和Sweep阶段之后,对象在堆内被压缩,从而实际移动到新地址.CLR负责跟踪实际的存储位置并使用内部表更新这些内部指针,确保在访问时它仍然指向对象的有效位置.

    这里有一个例子:

    ref struct CData
    {
        int age;
    };
    
    int main()
    {
        for(int i=0; i<100000; i++) // ((1))
            gcnew CData();
    
        CData^ d = gcnew CData();
        d->age = 100;
    
        interior_ptr<int> pint = &d->age; // ((2))
    
        printf("%p %d\r\n",pint,*pint);
    
        for(int i=0; i<100000; i++) // ((3))
            gcnew CData();
    
        printf("%p %d\r\n",pint,*pint); // ((4))
        return 0;
    }
    

    解释如下:

    在示例代码中,您创建了100,000个孤立CData对象((1)),以便您可以填充CLR堆的大部分内容.然后创建一个存储在变量中的CData对象和((2))指向此CData对象的int成员年龄的内部指针.然后打印出指针地址以及指向的int值.现在, ((3))你创建了另外100,000个孤儿CData对象; 沿着这条线的某个地方,发生了垃圾收集循环(先前创建的孤儿对象((1))被收集,因为它们没有在任何地方被引用).请注意,您不使用GC :: Collect调用,因为不能保证强制执行垃圾收集循环.正如您在上一章中对垃圾收集算法的讨论中已经看到的那样,GC通过删除孤立对象来释放空间,以便它可以进行进一步的分配.在代码结束时(此时发生了垃圾收集),再次((4))打印出指针地址和age的值.这是我在我的机器上得到的输出(请注意,地址因机器而异,因此您的输出值将不同):

    012CB4C8 100
    012A13D0 100
    

    2022-12-11 02:04 回答
  • 是的,引用在垃圾回收期间得到更新.必然如此,在压缩堆时会移动对象.压缩有两个主要目的:

    它可以更有效地使用处理器的数据缓存,从而提高程序的效率.对于现代处理器来说,这是一个非常非常大的问题,与执行引擎相比,RAM非常慢,两个数量级.当必须等待RAM提供可变值时,处理器可以停止数百条指令.

    它解决了堆积所遭受的碎片问题.释放由活动对象包围的小对象时发生碎片.除了尺寸相等或更小的物体外,不能用于任何其他物体的洞.内存使用效率处理器效率不佳.注意LOH(.NET中的大对象堆)如何不会被压缩,因此会受到这种碎片问题的影响.关于那个问题很多问题.

    尽管Eric有说服力,但对象引用确实只是一个地址.指针,与您在C或C++程序中使用的指针完全相同.非常有效,必然如此.并且所有GC在移动对象后都必须更新存储在该指针中的地址以移动对象.CLR还允许为对象分配句柄,额外引用.在.NET中作为GCHandle类型公开,但仅在GC需要帮助确定对象应该保持活动还是不应该移动时才需要.仅当您与非托管代码互操作时才有意义.

    什么不是那么简单就是找回指针.CLR投入巨资确保可以可靠,高效地完成.这些指针可以存储在许多不同的地方.更容易找到的是存储在对象字段,静态变量或GCHandle中的对象引用.硬盘是存储在处理器堆栈或CPU寄存器中的指针.例如,发生方法参数和局部变量.

    CLR需要提供的一个保证就是GC始终可靠地遍历线程的堆栈.因此,它可以找到存储在堆栈帧中的局部变量.然后,它需要知道哪里在这样一个堆栈帧看,这就是JIT编译器的工作.当它编译方法时,它不仅生成方法的机器代码,还构建一个表来描述这些指针的存储位置.你会在这篇文章中找到更多相关细节.

    2022-12-11 02:14 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有