【问题标题】:What is the point of using GC.AddMemoryPressure with an unmanaged resource?将 GC.AddMemoryPressure 与非托管资源一起使用有什么意义?
【发布时间】:2009-07-19 03:36:26
【问题描述】:

我在 MSDN 和 CLR 上通过 c# 阅读了有关此问题的信息。

假设我们分配了一个 2Mb 的非托管 HBITMAP 和一个指向它的 8 字节托管位图。如果它永远无法对对象进行任何处理,因为它被分配为非托管资源,因此不易受到垃圾回收的影响,使用 AddMemoryPressure 告诉 GC 有什么意义?

【问题讨论】:

    标签: c# .net vb.net garbage-collection


    【解决方案1】:

    提供它是为了让 GC 在收集期间知道对象的真实成本。如果对象实际上比托管大小反映的要大,则它可能是快速(更)收集的候选对象。

    Brad Abrams entry 非常清楚:

    考虑一个有一个非常小的类 托管实例大小,但拥有 指向一个非常大的块的指针 非托管内存。即使在没有人之后 正在引用它的托管实例 可以活一段时间因为 GC 只看到托管实例 尺寸它认为它不“值得” 它”来释放实例。所以我们需要 “教导” GC 真正的成本 这个实例,以便它会 准确地知道什么时候踢 收集以释放更多内存 过程。

    【讨论】:

    • 实际上,现在我重新阅读了您的帖子,我的问题仍然存在。在我使用我的位图之后,我会调用它的 dispose,所以它实际上只需要 8 个字节的内存。另一方面,如果我没有调用 dispose,那么它确实占用了 2mb 的内存。第三种情况是没有 dispose 方法,所以 GC 无能为力。所以,知道类型的实际大小的 GC 只会在情况 2 中有所帮助。还是我错过了什么?
    • @Jorge Branco:我想我看到了你的问题。我已经编辑了我的原始答案以回复您的最后评论。
    • 我想你理解得很好。您的示例中缺少的一件事是,由于对象的内存压力,GC 可能会启动一个集合(尽管它不会收集该对象,因为它正在使用中)。除此之外,如果您的对象始终正确放置,我认为它不会为这些对象提供太多帮助。如果您无法控制对象的使用,那么使用这些 API 可能会更有用。
    • 请告诉我 GC.AddMemoryPressure() 在哪里以及如何与任何特定对象实例相关?在没有关系的情况下,GC 怎么知道“对象的真实成本”?
    • 当 GC 运行时,它并不关心一个对象在“决定”它是否是垃圾时与它相关联的内存压力有多大。 GC 压力的唯一作用是鼓励 GC 比其他情况下运行得更快。
    【解决方案2】:

    AddMemoryPressure 的目的是告诉垃圾收集器该对象分配了大量内存。如果它是非托管的,垃圾收集器不知道它;只有托管部分。由于托管部分相对较小,GC 可能会让它多次通过垃圾收集,实际上浪费了可能需要释放的内存。

    是的,您仍然需要手动分配和解除分配非托管内存。你无法摆脱它。您只需使用 AddMemoryPressure 来确保 GC 知道它的存在。

    编辑:

    好吧,以防万一,我可以做到,但这没什么大不了的,因为如果我理解正确,GC 将无法对我的类型做任何事情:1)我会声明我的变量,8 个托管字节,2mb 非托管字节。然后我会使用它,调用 dispose,这样非托管内存就被释放了。现在它只会占用 8 个字节。现在,在我看来,在最后调用了开头的 AddMemoryPressure 和 RemoveMemoryPressure 并没有什么不同。我怎么了?很抱歉对此感到如此恼火。 -- Jorge Branco

    我想我看到了你的问题。

    是的,如果你能保证你总是调用Dispose,那么是的,你不需要为 AddMemoryPressure 和 RemoveMemoryPressure 操心。没有等价性,因为引用仍然存在并且永远不会收集该类型。

    也就是说,为了完整起见,您仍想使用 AddMemoryPressure 和 RemoveMemoryPressure。例如,如果您班级的用户忘记调用 Dispose 怎么办?在这种情况下,假设您正确地实现了 Disposal 模式,您最终将在完成时回收非托管字节,即在收集托管对象时。在这种情况下,您希望内存压力仍然处于活动状态,以便更有可能回收对象。

    【讨论】:

    • 我说的几乎和 Steven Lyons 说的完全一样,只是方式不同。它怎么没有回答问题?
    • 好的。谢谢,现在我想你明白了我的意思,我同意你关于使用它的完整性。
    • 对象和非托管内存之间没有关联。见my answer
    【解决方案3】:

    这些方法允许运行时了解进程分配了多少非托管内存。如果不调用这些函数,它可能无法看到进程中正在使用的真实非托管内存量。

    但是我不同意这里关于被引用的内存和特定 GC 对象之间的关联的其他答案。

    考虑:

    var buffer = IntPtr.Zero;
    try
    {
        buffer = Marshal.AllocHGlobal(size);
        GC.AddMemoryPressure(size);
    
        // ... use buffer ...
    }
    finally
    {
        Marshal.FreeHGlobal(buffer);
        GC.RemoveMemoryPressure(size);
    }
    

    GC 无法将size 分配给特定对象。这段代码甚至可以存在于静态方法中。

    因此,我断言声明我们需要“教导”GC 这个实例的真实成本,以便它准确地知道何时启动一个集合以释放进程中的更多内存 不正确且具有误导性。

    相反,此方法可能会导致 GC 比其他方法更早收集,以避免内存不足。

    【讨论】:

    【解决方案4】:

    这样说,仍然假设每个 8 字节的托管对象都引用一个 2 MB 的非托管图像。 GC 可能会等待很长时间才能收集成百上千个小的托管对象,因为它们太小了。这意味着成百上千个链接的 2 MB 非托管块也将保持活动状态,等待删除。这可能会成为一个大问题。通过在构造函数中添加 2 MB 的内存压力,您将使 GC 认为托管对象不是 8 字节大,而是 8 字节 + 2 MB。这将更早地触发收集方式。

    不要忘记 Remove 调用。

    当然,如果你放弃自己,那么你就不需要所有这些了。

    【讨论】:

    • 让 GC 认为托管对象不是 8 字节大,而是 8 字节 + 2 MB ...这不是真的。任何对象与引用的非托管内存之间都没有关联。
    猜你喜欢
    • 1970-01-01
    • 2013-02-02
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 2013-09-06
    相关资源
    最近更新 更多