【发布时间】:2019-07-17 06:59:25
【问题描述】:
我们的应用程序需要通过互操作调用某个非托管的第 3 方库。该应用程序线程密集,每分钟调用此函数数千次,这就是为什么我们不泄漏内存至关重要。
在我们的服务器上运行应用程序时,我注意到应用程序的内存占用量在增加,经过数周的解决问题后,我终于将其缩小到这个非常奇怪的问题:
我在使用非托管代码时观察到一个非常奇怪的行为,在分配和解除分配非托管指针之后,内存似乎没有按预期下降。
这是重现问题的最少代码:
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 运行应用程序直到完成时的屏幕截图:
红线是实际发生的情况(在运行上述代码示例时),而蓝线是我预期它的样子。
我也尝试过添加/移除 GC 压力 (GC.Add/RemoveMemoryPressure),但这并没有帮助,但这是可以理解的,因为这里没有发生 GC。我在 Window 10 桌面上尝试了相同的操作,但观察到了相同的行为。
发生了什么事?我们的服务器通过进程控制内存使用,如果内存超过一定水平,它会杀死并重新启动进程,这意味着我们必须小心我们的内存使用并需要能够控制这一点。
更新:使用 Thread.Sleep(0) 而不是 Thread.Sleep(50) 的图表:
【问题讨论】: