此代码创建 2 GiB 的对象,然后对它们进行垃圾回收。之后,该进程仍在使用 2 GiB+ 的内存。在 Windows 中,我能够通过从
SetProcessWorkingSetSize()
调用 kernel32.dll
来强制 Windows 回收内存(参见下面的代码)。结果:
在 Linux (
Ubuntu 20.04
) 中,我尝试使用 madvise
中的 libc
,但是它返回 -1
,错误代码为 0
(获取最后一个错误代码的代码也可能被破坏) ,进程工作集仍然是 2 GiB+。如何使用 Linux 获得与使用 Windows 相同的结果?如果不可能,最好的策略是什么?谢谢!
using System.Diagnostics;
using System.Runtime;
using System.Runtime.InteropServices;
namespace GCtest
{
internal class Program
{
public static async Task Main()
{
PrintMemoryUsage();
UseLotsOfMemory();
MemoryUtility.ForceGcAndSetProcessWorkingSet();
PrintMemoryUsage();
Console.ReadLine();
}
private static void UseLotsOfMemory()
{
const int objectSize = 1024*1024*100; // 100 MiB
const int numberOfObjectsToCreate = 20;
var datas = new List<byte[]>();
var rng = new Random(Guid.NewGuid().GetHashCode());
for (int i = 0; i < numberOfObjectsToCreate; i++)
{
byte[] data = new byte[objectSize];
rng.NextBytes(data);
datas.Add(data);
PrintMemoryUsage();
}
}
private static long GetMemoryUsage()
{
var process = Process.GetCurrentProcess();
return process.WorkingSet64;
}
private static void PrintMemoryUsage()
{
Console.WriteLine();
Console.WriteLine($"Process working set: {GetMemoryUsage():n0} bytes");
Console.WriteLine($"GC total memory : {GC.GetTotalMemory(false):n0} bytes");
}
}
public static class MemoryUtility
{
public static void ForceGcAndSetProcessWorkingSet()
{
Console.WriteLine("Forcing blocking GC collection and compacting of gen2 LOH and updating OS process working set size...");
var sw = Stopwatch.StartNew();
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(generation: 2, GCCollectionMode.Forced, blocking: true, compacting: true);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
WindowsMemoryUtility.ReleasedUnusedProcessWorkingSetMemory();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
LinuxMemoryUtility.ReleasedUnusedProcessWorkingSetMemory();
}
Console.WriteLine($"Completed GC and setting process working set in {sw.Elapsed.TotalMilliseconds} ms");
}
public class LinuxMemoryUtility
{
[DllImport("libc", SetLastError = true)]
private static extern int madvise(IntPtr addr, UIntPtr length, int advice);
public static void ReleasedUnusedProcessWorkingSetMemory()
{
try
{
var startMemoryAddress = Process.GetCurrentProcess().MainModule.BaseAddress;
var memoryLength = new UIntPtr((ulong)Process.GetCurrentProcess().WorkingSet64);
Console.WriteLine($"Calling madvise with start: {startMemoryAddress} and length: {memoryLength}");
int result = madvise(startMemoryAddress, memoryLength, 8 /* MADV_DONTNEED */);
Console.WriteLine($"madvise result: {result}");
if (result != 0)
{
Console.WriteLine($"madvise errno: {Marshal.GetLastSystemError()}");
}
}
catch (Exception exc)
{
Console.WriteLine(exc);
}
}
}
public class WindowsMemoryUtility
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetProcessWorkingSetSize(IntPtr proc, int minSize, int maxSize);
public static void ReleasedUnusedProcessWorkingSetMemory()
{
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1);
}
}
}
}