我正在尝试为我最近写的一些代码写一个测试,它通过分配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");
}
}
}
2条答案
按热度按时间bhmjp9jg1#
主要问题是所示代码没有在
MemUser
的Clear()
方法中调用dataHandle.Free()
。不能依靠垃圾回收来释放GCHandle,因为它是一个结构体,不是在堆上创建的。
我将您的代码修改为以下内容,其中显示内存正在为数组清除,尽管没有少量额外的字节数,这可能与控制台对象使用的进程和内存中的字符串实习有关。
如果运行这个命令,您可以看到,
GC.Collect()
和GC.WaitForPendingFinalizers()
是不需要的。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。这应该更安全,并且允许在本机代码不主动使用时移动内存。长时间固定小对象会导致内存碎片。