如何强制.NET缩减内存

4si2a6ki  于 2023-05-08  发布在  .NET
关注(0)|答案(2)|浏览(187)

我正在尝试为我最近写的一些代码写一个测试,它通过分配GCHandle(用于与C++的互操作的持久使用)将内存标记为固定的。
我在一个包含一些float和int数组的单个对象中使用一个非常大的分配进行测试。我想检查一下,一旦我为每个数组释放了GCHandle,就可以对内存进行垃圾收集了。
目前,我可以看到分配的内存随着调用GC.GetTotalAllocatedBytes(true);currentProcess.WorkingSet64;而增加,并且可以确认增加的大小与我对正在创建的对象的粗略估计(只是将对象中的大型数组的大小相加)相同。
然而,我似乎不能让数字在删除对象后缩小,即使没有固定上面的数组。我试过了

GC.Collect();            
GC.WaitForPendingFinalizers();

强制收集所有代的数据,但我的内存使用量从未下降。我知道GC可能会保留内存以供重用,但我希望有一种方法可以解除分配我为此目的而创建的大块内存。
我是不是看错了指标,还是这只是我无法控制的事情?
示例:

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace TestApp
{
    public class MemUser
    {        
        private float[] data;
        private GCHandle dataHandle;
        public float[] Data { get { return data; } set { data = value; } }
        public MemUser(int numData)
        {
            data = new float[numData];
        }
        public long RoughSize()
        {
            return data.Length * sizeof(float);
        }
        public void GetPinnedHandles()
        {
            if (dataHandle.IsAllocated)
                dataHandle.Free();
            dataHandle = GCHandle.Alloc(dataHandle, GCHandleType.Pinned);
        }

        public void FreePinnedHandles()
        {
            if (dataHandle.IsAllocated)
                dataHandle.Free();
        }
        public void Clear()
        {
            FreePinnedHandles();
            data = null;
        }
    }

    public class Program
    {

        public static void Main(string[] args)
        {
            const int numFloats = 500 * 1000 * 1000;
            long beforeCreation = GetProcessMemory();
            // Create the object using a large chunk of memory 
            MemUser testMemory = new MemUser(numFloats);
            long roughMemSize = testMemory.RoughSize();
            // in practice, will pin the memory, but testing without for now
            //testMemory.GetPinnedHandles();

            //confirm memory in use jumps
            long afterCreation = GetProcessMemory();            
            long difference = afterCreation - beforeCreation;
            TestResult(difference, roughMemSize);

            // get rid of the object
            testMemory.Clear();
            testMemory = null;
           
            // this test fails, memory may have even gone up
            long afterDeletion = GetProcessMemory();
            difference = afterCreation - afterDeletion;
            TestResult(difference, roughMemSize);
        }

        public static long GetProcessMemory()
        {
            Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            long totalBytesOfMemoryUsed = GC.GetTotalAllocatedBytes(true);
            //long totalBytesOfMemoryUsed = currentProcess.WorkingSet64;
            return totalBytesOfMemoryUsed;
        }
        public static void TestResult(long difference, long expected)
        {
            
            if (difference >= expected)
                Console.Write($"PASS, difference ({difference}) >= {expected}\n");
            else
                Console.Write($"FAIL, difference ({difference}) < {expected}\n");
        }
    }
}
bhmjp9jg

bhmjp9jg1#

主要问题是所示代码没有在MemUserClear()方法中调用dataHandle.Free()
不能依靠垃圾回收来释放GCHandle,因为它是一个结构体,不是在堆上创建的。
我将您的代码修改为以下内容,其中显示内存正在为数组清除,尽管没有少量额外的字节数,这可能与控制台对象使用的进程和内存中的字符串实习有关。
如果运行这个命令,您可以看到,GC.Collect()GC.WaitForPendingFinalizers()是不需要的。

public class MemUser
        {
            private float[] data;
            private GCHandle dataHandle;
            public MemUser(int numData)
            {
                data = new float[numData];
                dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
            }
            public long RoughSize()
            {
                return data.Length * sizeof(float);
            }

            public void Clear()
            {
                dataHandle.Free();
                data = null;
            }
        }

        public static void Main(string[] args)
        {
            Console.WriteLine("====================================================================================================");
            const int numFloats = 500 * 1000 * 1000;
            long beforeCreation = GC.GetTotalMemory(true);
            Console.WriteLine("Memory used by process: " + beforeCreation);

            // Create the object using a large chunk of memory 
            MemUser testMemory = new MemUser(numFloats);
            long afterCreation = GC.GetTotalMemory(true);
            Console.WriteLine("Memory used by process after creation of large array pinned with GCHandle: " + afterCreation);

            //deduce the delta
            long delta = afterCreation - beforeCreation;
            Console.WriteLine("Additional memory used by process for the array and GCHandle: " + delta);
            GC.AddMemoryPressure(delta);

            // free the memory
            testMemory.Clear();
            testMemory = null;

            // did we reclaim all the memory? new delta should be close to zero once we free memory
            long afterDeletion = GC.GetTotalMemory(true);
            long newDelta = afterDeletion - beforeCreation;
            Console.WriteLine("Memory after deletion: " + afterDeletion);
            Console.WriteLine("Memory delta relative to before creation: " + newDelta);
            Console.ReadLine();
        }
rbl8hiat

rbl8hiat2#

您可能应该使用GC.GetTotalMemory(true)来获取当前的内存使用情况。How to get the amount of memory used by an application
int findDuplicate(true);
从文档中看,这似乎是计算 * 分配 * 的总数,而不是当前内存。
currentProcess.WorkingSet64;
由于这是进程内存,我假设它包括由垃圾收集器管理但当前未使用的内存。当内存被收集时,GC不会立即将其返回给OS,因为它可能很快就会再次被需要。
但还有其他问题。当使用GCHandle.Alloc时,您自愿退出垃圾收集。因此,每个alloc调用都与一个free调用相匹配是非常重要的,至少对于任何长时间运行的进程来说是如此。
如果可能的话,我可能会推荐使用fixed statement。这应该更安全,并且允许在本机代码不主动使用时移动内存。长时间固定小对象会导致内存碎片。

相关问题