我有一个执行内存密集型模拟的程序。下面我写了一个小型控制台应用程序,它复制了我遇到的问题。
class Program {
static void Main(string[] args) {
var t = new Task(() => DoMemoryHog(20000000));
t.Start();
t.Wait();
t.Dispose();
t = null;
GC.Collect();
Console.WriteLine("Done");
Console.ReadLine();
}
static void DoMemoryHog(int n) {
ConcurrentBag<double> results = new ConcurrentBag<double>();
Parallel.For(0, n, (i) => {
results.Add(Math.Sqrt(i.GetHashCode()));
});
}
}
当我运行程序时,我可以看到Windows任务管理器中已用内存量的增加,但是当任务完成(并显示“完成”)时,内存不会恢复到原始级别,只有在我关闭应用程序时才会发生。
有没有人知道如何释放并行任务使用的内存,而主应用程序一直在运行?正如你所看到的,我已经尝试过处理它,设置它对null的引用并手动运行垃圾收集器(我知道你不应该这样做。)
答案 0 :(得分:6)
这是由于concurrentBag的性质(请参阅我之前关于ConcurrentBag的问题(Possible memoryleak in ConcurrentBag?))。
基本上,并发包将项目存储在本地线程上。如果您不使用这些项目,这些项目将保留在本地线程上。请检查以下示例:
class TrackedItem
{
public TrackedItem()
{
Console.WriteLine("Constructor!");
}
~TrackedItem()
{
Console.WriteLine("Destructor!");
}
}
static void Main(string[] args)
{
Action allCollect = () =>
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
};
// create a single item and loose it directly thereafter
TrackedItem item = new TrackedItem();
item = null;
allCollect();
// Constructor!, Destructor!
ConcurrentBag<TrackedItem> bag = new ConcurrentBag<TrackedItem>();
bag.Add(new TrackedItem());
bag = null;
allCollect();
// Constructor!
// Note that the destructor was not called since there is still a collection on the local thread
Console.ReadLine();
}
concurrentBag使用ThreadLocal类,这样可以方便地为每个线程保存1个实例。在ThreadLocal中处理数据的预期方法是通过调用ThreadLocal上的Dispose方法(ThreadLocal实现IDisposable)。这仅处理当前线程的数据。 concurrentBag虽然没有处理它的ThreadLocals。相反,它依赖于所有正在使用的项目 - 或托管ThreadLocal的线程被处置。当你在ThreadPool中共享线程时,这可能非常讨厌。
答案 1 :(得分:5)
我确定你的内存没有问题,.NET只是不会缩小已使用的内存,以便将来可以使用它。这节省了将来内存分配的时间。尝试在完成后重新运行循环,我确定内存不会增长。
所以,请试试这个,我会对结果感兴趣!
class Program {
static void Main(string[] args) {
Process currentProcess = Process.GetCurrentProcess();
for (int i = 0; i < 10; i++) {
var t = new Task(() => DoMemoryHog(20000000));
t.Start();
t.Wait();
t.Dispose();
t = null;
GC.Collect();
Console.WriteLine("Done" +i);
Console.WriteLine("Memory: " + GC.GetTotalMemory(false));
Console.WriteLine("Paged: " + currentProcess.PagedMemorySize64);
Console.WriteLine("-------------------------");
}
Console.ReadLine();
}
static void DoMemoryHog(int n) {
ConcurrentBag<double> results = new ConcurrentBag<double>();
Parallel.For(0, n, (i) =>
{
results.Add(Math.Sqrt(i.GetHashCode()));
});
}
}
这两种方法用于打印出内存使用情况:
GC.GetTotalMemory();
currentProcess.PagedMemorySize64;
我的输出是:
Done0
Memory: 480080992
Paged: 520753152
-------------------------
Done1
Memory: 480081512
Paged: 520753152
-------------------------
Done2
Memory: 480082160
Paged: 520753152
-------------------------
Done3
Memory: 480083476
Paged: 520753152
-------------------------
Done4
Memory: 480083496
Paged: 520753152
-------------------------
Done5
Memory: 480083516
Paged: 520753152
-------------------------
Done6
Memory: 480083536
Paged: 520753152
-------------------------
Done7
Memory: 480084204
Paged: 520753152
-------------------------
Done8
Memory: 480084204
Paged: 520753152
-------------------------
Done9
Memory: 480085500
Paged: 520753152
-------------------------
据我所知,这里没有记忆问题。对象是清理的,内存可以重复使用。问题解决了吗?
专用字节行为:
正如您所看到的,GC正在收集对象并释放内存,但它当前没有释放它,因此可以分配新对象。