简体   繁体   English

C#:释放 memory 使用

[英]C#: Releasing memory usage

I need to execute a long-heavy process for a question out of the box.对于开箱即用的问题,我需要执行一个长期繁重的过程。 So I have divided the process in multiple subprocess.所以我把这个过程分成了多个子过程。 The question now, is how to release memory, before the execution of each window.现在的问题是如何在每个 window 执行之前释放 memory。

It's easier to explain with an example.用一个例子更容易解释。 Let's take a look to this pseudo-code.让我们看一下这个伪代码。

1. Some earlier code to do other things
2. Do
3.     Raise a Task
4.     If raised-task > 1000
5.         Wait all raised task to finish
6.         Release Memory
7.     End If
8. Wile Something not relevant

With that idea, I have developed the next method, which gets executed every time it's reached the thread limitation:有了这个想法,我开发了下一个方法,每次达到线程限制时都会执行该方法:

List<Task> LTask();
//It's not relevant, but this list is populate like
//var task = Task.Run(() => something());
//LTask.Add(task);

private void waitForAll()
{
    //Break point 1
    Task.WhenAll(LTasks).Wait();

    LTasks.Clear();
    LTasks = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();

    //Break point 2
    LTasks = new List<Task>();
}

I expected memory gets constant (whit some variation) around some values.我预计 memory 在某些值附近会保持不变(有一些变化)。 I mean:我是说:

  1. Thread limit is reached已达到线程限制
  2. Snapshot of memory usage with visual studio diagnosis tools in BreakPoint 1 --> 100MB memory 在 BreakPoint 1 中使用 Visual Studio 诊断工具的快照 --> 100MB
  3. Snapshot of memory usage with visual studio diagnosis tools in BreakPont 2 --> 100 MB.在 BreakPont 2 --> 100 MB 中使用 Visual Studio 诊断工具的 memory 使用快照。 First question, why this has not decreased?第一个问题,为什么没有减少? All the threads are finished and I forced Garbage Collector to execute.所有线程都完成了,我强制垃圾收集器执行。

Next time the limit is reached and this code is executed, if I take a snapshot again, the memory keeps increasing: 200, 300, ...下次达到限制并执行此代码时,如果我再次拍摄快照,memory 会不断增加:200、300、...

This is a capture of diagnosis tools.这是诊断工具的捕获。 Odd snapshots are taken every time break point 1 is reached, and Even snapshots on break point 2.每次到达断点 1 时拍摄奇数快照,并在断点 2 处拍摄偶数快照。

在此处输入图像描述

Second question, this will continue increasing whit no limit until it throws an Out of memory Exception ?第二个问题,这将继续无限制地增加,直到抛出Out of memory Exception

Last question, any alternatives to solve the problem, and release the memory?最后一个问题,有什么办法可以解决这个问题,并发布memory?

UPDATE 1: After some tests, and thanks to the comments, I have developed a test code, to dig into it.更新 1:经过一些测试,感谢评论,我开发了一个测试代码来深入研究它。 There has to be involved something else.必须涉及其他事情。 Plase, take a look to the next piece of code.请看下一段代码。 The memory continue increasing with no limit. memory 继续无限制地增加。

private List<Task> LTasks = new List<Task>();
private void manageThreadholdLimit()
{
    waitForAll();
    realeaseMemory();
}

private void waitForAll()
{
    Task.WhenAll(LTasks).Wait(); 
    LTasks.Clear();
    LTasks = null;  
}
private void realeaseMemory()
{   
    GC.Collect();
    GC.WaitForPendingFinalizers();

    LTasks = new List<Task>();
}
public void Main(){
    int i = 0;

    while (true)
    {
        i++;

        var task = Task.Run(() => Thread.Sleep(100));
        LTasks.Add(task);

        //Si hemos alcanzado el máximo de paralelismo, esperamos la conclusión
        if (i % 1000 == 0) manageThreadholdLimit();

    }
}

GC collection is slightly different in debug, see:( John Skeet knows all ) So I would do allot of logging when running this in release mode to verify all behaviours. GC 收集在调试中略有不同,请参阅:( John Skeet 知道所有)所以我会在发布模式下运行它时分配日志以验证所有行为。

Any solution will be very dependent on your actual code and if there are unmanaged resources being accessed.任何解决方案都将非常依赖于您的实际代码以及是否有非托管资源被访问。

That said I have had to deal with a problem like this before and I have "solved" it in 2 different ways before.也就是说,我以前必须处理过这样的问题,并且我以前曾以两种不同的方式“解决”过它。

One solution一种解决方案

Have counters that are incremented in constructor and decremented in the finalizer in the class that does the actual work and wait for the counter to fall under a defined threshold and importantly run collect again for the finalized objects to be collected.在执行实际工作的 class 中,让计数器在构造函数中递增并在终结器中递减,并等待计数器低于定义的阈值,重要的是再次运行 collect 以收集最终对象。

Be careful checking the total memory consumption before continuing otherwise you can end up with out of memory exception.在继续之前请仔细检查 memory 的总消耗量,否则您可能会遇到 memory 异常。

Now this will actually increase your memory consumption slightly.现在这实际上会稍微增加您的 memory 消耗。 For more info see有关更多信息, 请参阅

Another solution另一种解决方案

Have a loop waiting for the memory consumption to fall by using GC.GetTotalMemory() or even better performance counters and wait for it to come down.通过使用 GC.GetTotalMemory() 甚至更好的性能计数器,有一个循环等待 memory 消耗下降,然后等待它下降。

This can end up not doing any work at all if your resources are not being collected.如果您的资源没有被收集,这最终可能根本不做任何工作。

It is not guaranteed that the GarbageCollector will run immediately after you unreference the object.不保证在您取消引用 object 后GarbageCollector将立即运行。 In fact, it probably won't.事实上,它可能不会。 Even if you want to call it manually for testing purposes with GC.Collect() you have no real guarantees it will run immediately.即使您想手动调用它以使用GC.Collect()进行测试,您也不能真正保证它会立即运行。 Also, there is a cost to calling GC often.此外,经常调用 GC 也是有代价的。 RAM is there to be used (at least in newer machines with lots of RAM...).可以使用 RAM(至少在具有大量 RAM 的较新机器中......)。 You have a problem if your memory goes up, and stays up after a longer while.如果您的 memory 上升,并在较长时间后保持上升,您就会遇到问题。 This usually indicates that you have some other memory problem.这通常表明您还有其他一些 memory 问题。 Maybe you have a leak?也许你有泄漏?

If we're talking about free solutions, you can use ProcessExplorer and CLR Profiler to look for potential memory problems.如果我们谈论的是免费解决方案,您可以使用ProcessExplorerCLR Profiler来查找潜在的 memory 问题。 Here is an article on how to do that. 是一篇关于如何做到这一点的文章。

Things to look out for:需要注意的事项:

  1. Set references to your objects to null as soon as you can.尽快将对象的引用设置为null You are done with the list?你完成了清单吗? Set its value to null .将其值设置为null
  2. Split the method that is working with a lot of data into smaller methods - even if you set something to null , the GC won't run until a method is exited.将处理大量数据的方法拆分为更小的方法 - 即使您将某些内容设置为null ,GC 也不会在方法退出之前运行。
  3. Check if you have finalizers properly implemented where needed.检查您是否在需要的地方正确实施了终结器。
  4. Make sure you do not have leaks: check if the objects you are working with aren't referenced from outside the worker method.确保您没有泄漏:检查您正在使用的对象是否没有从工作方法外部引用。 Be especially careful about event handlers and data bindings.对事件处理程序和数据绑定要特别小心。

Also, Task.WhenAll() apparently keeps the references to all its "child" tasks.此外, Task.WhenAll()显然保留了对其所有“子”任务的引用。 If you manually call GC immediately after it in the same method, I think there is a chance it won't be able to touch that memory, as the method itself is still "referencing" it.如果您以相同的方法立即手动调用 GC,我认为它可能无法触及 memory,因为方法本身仍在“引用”它。

Here is a MSDN article about garbage collection. 是一篇关于垃圾收集的 MSDN 文章。

I think I found an explanation, thanks to @ScottChamberlain's answer at: Why Garbage Collector doesn't collect Tasks objects我想我找到了一个解释,感谢@ScottChamberlain 的回答: Why Garbage Collector doesn't collect Tasks objects

I think the memory gets increasing because TaskScheduler is static, and still stores the task's reference, despite the task is finished.我认为 memory 会增加,因为 TaskScheduler 是 static,并且仍然存储任务的引用,尽管任务已完成。 So the amount of references is which keeps increasing the memory, not the memory usage of each task itself.因此,引用的数量不断增加 memory,而不是每个任务本身的 memory 使用量。

I have not figure out yet how to remove that references, but, as an alternative solution, I could refactorize the code the use threads.我还没有弄清楚如何删除这些引用,但作为替代解决方案,我可以重构使用线程的代码。 For example, this code is an alternative of the "Update 1" in my question.例如,此代码是我问题中“更新 1”的替代方法。 And the memory remains stable on 18mb. memory 在 18mb 上保持稳定。

private static List<Thread> LThreads = new List<Thread>();
private static void manageThreadholdLimit()
{
    waitForAll();
    realeaseMemory();
}

private static void waitForAll()
{
    LThreads.ForEach(x => x.Join());
    LThreads.ForEach(x => x = null);
    LThreads.Clear();
    LThreads = null;
}
private static void realeaseMemory()
{


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

    LTasks = new List<Task>();
    LThreads = new List<Thread>();
}
public static void Main(string[] args)
{
    int i = 0;

    while (true)
    {
        i++;

        var t = new Thread(() => Thread.Sleep(100));
        LThreads.Add(t);
        t.Start();

        if (i % 1000 == 0) manageThreadholdLimit();

    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM