如何在 C# 中强制 Linux 内核回收未使用的进程内存?

问题描述 投票:0回答:0

此代码创建 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);
            }
        }
    }
}
c# linux-kernel garbage-collection heap-memory libc
© www.soinside.com 2019 - 2024. All rights reserved.