【问题标题】:GC.AddMemoryPressureGC.AddMemoryPressure
【发布时间】:2010-04-11 17:53:14
【问题描述】:

我正在用 C# 编写一个使用第 3 方 COM DLL 的应用程序,该 dll 在非托管内存中创建了大量资源(如位图、视频、数据结构)。在四处挖掘时,我遇到了以下对垃圾收集器的调用:

GC.AddMemoryPressure(long long bytesAllocated)

这里的 MSDN 中有记录:

http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx

这听起来像是我应该调用的东西,因为这个外部 dll 正在创建 CLR 不知道的大量资源。

我想我有两个问题......

  1. 当 dll 是第 3 方时,我如何知道要添加多少内存压力,而我无法确切知道该 dll 分配了多少内存。
  2. 这样做有多重要?

【问题讨论】:

  • 您不太可能需要使用此 API,但出于好奇——“创造大量资源”背后的数字是什么(这是一个非常模糊的说法)?具体来说,分配的内存量是多少?
  • 可能是几兆字节(甚至几十兆字节)的数据迈克尔,确切的大小取决于视频的数量,编码,有很多因素会影响这一点。我希望找到一种方法来了解在初始化这个特定的 com 对象之前和之后使用了多少非托管内存,这样我就可以使用某种启发式方法来确定要添加的合理内存压力水平。由于代码是第 3 方,我不知道具体细节

标签: c# garbage-collection


【解决方案1】:

在任何混合的本机/托管进程中,都会混合使用本机/托管内存。如果两者之间没有 GC 控制的关系,那么就不需要这个 API。例如,如果托管代码中存在某些确定性的状态更改导致本机内存被分配和释放,那么 GC 可以做的任何事情都不会强制释放本机内存。

然而,很多时候,有终结器的托管对象持有本机内存。因此,GC 可以减小本机堆的大小,只需触发一个集合并让这些终结器运行即可。

因此,如果您有很多这样的事情发生,很可能有必要调用此 API(就像文档中所说的那样)。

至于你应该告诉它多少,这可能不是你可以通过纯粹的分析得出答案的东西,尤其是使用 3rd 方库。您需要运行性能监视器,运行分配大量第 3 方对象的测试,并查看 Native Bytes 和 CLR 内存计数器以了解它们之间的关系。

当您使用 COM 对象时,您实际上可以通过使用Marshal.ReleaseComObject 确定性地强制实例在您知道不再需要它们时进行清理。请注意,您需要使用愚蠢的循环来使其摆脱对象:

while (Marshal.ReleaseComObject(obj) != 0) 
{
}

【讨论】:

  • 好的,很酷,所以手动告诉运行时可调用包装器释放它的所有引用(同时非常小心不要有任何可能开始抛出 System.NullReferenceException 的杂散指针)。关于内存使用情况,我查看了性能计数器,但根据视频的大小,内存可以改变,所以这并没有太大帮助。我希望找到一种方法来动态解决这个问题……这个 dll 可以使用一段时间,所以让 GC 知道后台有一个内存大象似乎是有意义的。
  • 注意。如果您始终以确定性的方式强制关闭 COM 对象,那么您将处于我在第一段中描述的情况:GC 只需要负责它可以直接检测到的内存使用情况,因此您不需要需要调用内存压力API。
  • 换个角度看,.NET GC 永远不会管理非托管对象。那是开发人员的责任,IDisposable 模式就是你的工作方式。因此,没有任何设置可以影响 GC 以使其更好地清理非托管对象。
  • @Cylon Cat - GC 可以通过终结器(或者最好是安全句柄)间接管理任何资源。它只直接管理内存。问题是“非托管”这个词有很多包袱,并且在几乎所有出现的上下文中都有不同的含义。
  • GC 通过将未引用的对象推入终结队列来确定终结器何时运行。终结器是一种将一些自定义代码挂接到 GC 中的方法,以使 GC间接管理某些资源的状态,否则这些资源需要手动管理。 (为什么人们在我的 cmets 中似乎漏掉了“直接”和“间接”这两个词?)
【解决方案2】:

假设我有一个这样的对象:

public class SomeImageType : IDisposable
{
    public int Width { get; private set; }
    public int Height { get; private set; }
    public PixelFormat PixelFormat { get; private set; }
    IntPtr ImageData { get; private set; }
    // implementation of constructor and IDisposable not shown
}

这需要多少内存? 20 字节 + 对象开销?当需要收集时,如果该对象没有引用,则根本没有区别。但它需要 20 个字节吗?不,这是一个图像。因此,取 20 并乘以宽度、高度和每个像素的字节数,你就有了占用(可能)兆字节的东西。通过告诉 GC 增加内存压力,您是在说那里有一座正在吞噬资源的冰山。当你释放对象时,你当然会释放内存并告诉 GC 释放压力。这两个调用让您向 GC 暗示 Circle K 中正在发生一些奇怪的事情,它可能希望以不同的方式安排收集。

【讨论】:

    【解决方案3】:

    您可以做的最重要的事情是在这些第三方 DLL 类上调用 Dispose() 方法,并且对这样做绝对狂热。 (或者,当然,使用“使用”块,因为它们会在退出块时自动调用 Dispose()。)

    关于非托管资源的问题是包装或使用它们的 .NET 类需要具有终结器,以及实现 IDisposable() 模式。如果调用 Dispose(),它应该立即处理非托管资源,并且还应该抑制终结器。如果你不调用 Dispose(),那么你有真正的内存问题,给垃圾收集器增加“压力”并不能解决它。

    垃圾收集器的基本逻辑是,在所有其他可能性都已经用尽之前,不会处理任何具有活动终结器的类。这意味着 gen0、gen1 和 gen2 清理已经发生。没有被抑制的终结器几乎和内存泄漏一样糟糕。

    所以调用 Dispose()。确保您不会错过它。

    编辑:刚刚注意到第 3 方 DLL 是一个 COM DLL。最佳实践是确保您的 .NET 包装器完全实现 IDisposable()(而不仅仅是提供 Dispose 方法)。然后确保在完成 COM 对象后始终调用 Dispose()。

    【讨论】:

    • 感谢您的建议,是的,我对使用 Dispose 非常狂热,所以听起来我应该很好。再次感谢!
    • 在调用Dispose 时,包装类应该做什么? (我已经用这个答案更新了我的答案!)
    【解决方案4】:

    这样做对您来说并不重要,除非您注意到您认为是由 GC 引起的内存问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-06
      • 1970-01-01
      相关资源
      最近更新 更多