使用非托管内存时奇怪的内存使用情况

时间:2019-07-17 06:59:25

标签: c# unmanaged

我们的应用程序需要通过互操作调用某个不受管理的第三方库。该应用程序具有大量线程,并且每分钟要调用该函数数千次,因此为什么不泄漏内存至关重要。

在服务器上运行该应用程序时,我注意到该应用程序的内存占用量正在增加,经过数周的浏览,终于将其范围缩小到了这个非常奇怪的问题:

使用非托管代码时,我观察到一个非常奇怪的行为,即在分配和取消分配非托管指针之后,内存似乎并没有按预期减少。

这是重现该问题的最少代码:

Platform: x64
Build: Release (optimised)
Windows Server 2012
64GB memory
Dual Xeon processors with 64 logical cores
<gcServer enabled="true />

和代码

class Program
{
    static void Main()
    {
        // LargeFile.abc is representing a byte[] of ~300kb
        byte[] largeArray = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "Data", "LargeFile.abc"));
        TestMemoryAllocation(largeArray);

        Console.WriteLine("Done");
        Console.ReadLine();
    }

    private static void TestMemoryAllocation(byte[] byteArray)
    {
        Parallel.For(0,
            1000000,
            i =>
            {
                FromStream(byteArray, ptr =>
                {
                    // simulate some work with the unmanaged pointer
                    Thread.Sleep(50);
                });
            });
    }

    private static void FromStream(byte[] src, Action<IntPtr> func)
    {
        IntPtr unmanagedArray = IntPtr.Zero;
        try
        {
            // allocate the memory on the unmanaged heap
            unmanagedArray = Marshal.AllocHGlobal(src.Length);

            // do something with this unmanaged pointer
            func(unmanagedArray);
        }
        finally
        {
            // free the space
            Marshal.FreeHGlobal(unmanagedArray);
        }
    }
}

通常,我的期望是,取决于核的数量和线程调度,内存将上升和下降,但最终将保持稳定并在最小和最大之间徘徊。相反,这是我正在观察的:

ProcessExplorer / TaskManager(这是Windows服务器)中的专用工作字节不断增加,并且内存分析器(使用ANTS和JetBrains dotMemory)都报告了运行时内存消耗的增加。

这是运行应用程序直至完成的dotMemory的屏幕截图: enter image description here

红色的行是实际发生的情况(运行上述代码示例时),而蓝色的行是我期望的样子。

我也尝试过添加/删除GC压力(GC.Add / RemoveMemoryPressure),但没有帮助,但这是可以理解的,因为这里没有发生GC。我在Windows 10桌面上尝试了相同的操作,但是观察到了相同的行为。

这是怎么回事?我们的服务器按进程控制内存使用情况,如果内存超过一定水平,它将终止并重新启动进程,这意味着我们必须谨慎使用内存使用情况,并且需要能够对此进行控制。

更新:使用Thread.Sleep(0)而不是Thread.Sleep(50)的图形:

enter image description here 非常感谢,

1 个答案:

答案 0 :(得分:4)

好吧,对您的测试代码进行一些内存分析(注意,我将分配增加到100倍,以便更快地到达那里:):

  1. 从未发生过GC。这并不奇怪,因为您实际上没有分配太多的托管内存-一分钟后(当我遇到内存不足崩溃时),堆增长了大约10 kiB,没有理由让GC介入。 / li>
  2. 我得到了四个工作线程(在4核CPU上)。这也不是太令人惊讶,但可能是造成问题的原因。当您使用的同步代码执行的CPU工作量不足时,工作线程池将增加线程池以容纳更多的并发工作负载。它不在乎您使用多少内存。这意味着,如果您的线程完成的速度不比新线程快,那么您的内存使用量将一直增加,直到进程崩溃。

检查随着内存增加,服务器上正在运行多少个线程。使用率的提高似乎很好地遵循了新创建的工作线程。请注意,您的代码中只有Thread.Sleep(50),这限制了您获得的最大线程数。我希望您的实际工作量可能还要更多。

所以,在我看来,这并不是真正的内存泄漏问题,而是一个节流问题。您可以通过建立一个系统来限制此特定问题允许使用的线程数来解决此问题。在您的测试应用程序中,这很简单:

Parallel.For(0, 1000000, new ParallelOptions { MaxDegreeOfParallelism = 10 },
  i =>
  {
    FromStream(byteArray, ptr =>
    {
       // simulate some work with the unmanaged pointer
       Thread.Sleep(50);
    });
  }
);

Voilá-您的内存使用量达到峰值并保持稳定。调整并行度,以适应您实际可以并行执行的工作量和可用的内存量。