C# .NET垃圾回收问题解答

5
我在我的应用程序中遇到了OutOfMemoryException的问题。我的应用程序可以在文本中搜索单词。当我启动一个长时间运行的进程来搜索大约2175个不同单词的2000个不同文本时,应用程序将在处理约50%时终止,并出现OutOfMemoryException(处理大约6小时后)。
我一直在尝试找到内存泄漏。我的对象图如下:(--> 是引用)
一个静态全局应用程序对象(控制器)--> 一个算法启动对象--> 文本挖掘启动对象--> 文本挖掘算法对象(此对象执行搜索)。
文本挖掘启动对象将在单独的线程中启动文本挖掘算法对象的run()方法。
为了尝试解决这个问题,我编辑了代码,使文本挖掘启动对象将要搜索的文本分成几组,并为每组文本顺序初始化一个文本挖掘算法对象(因此,当一个文本挖掘算法对象完成时,将创建一个新的对象来搜索下一组文本)。在这里,我将前一个文本挖掘算法对象设置为null。但是,这并没有解决问题。
当我创建一个新的文本挖掘算法对象时,我必须给它一些参数。这些参数是从之前的文本挖掘算法对象的属性中获取的,然后我将该对象设置为null。这会防止文本挖掘算法对象被垃圾回收吗?
下面是文本挖掘算法启动程序创建新文本挖掘算法对象的代码:
    private void RunSeveralAlgorithmObjects()
    {

        IEnumerable<ILexiconEntry> currentEntries = allLexiconEntries.GetGroup(intCurrentAlgorithmObject, intNumberOfAlgorithmObjectsToUse);

        algorithm.LexiconEntries = currentEntries;
        algorithm.Run();

        intCurrentAlgorithmObject++;

        for (int i = 0; i < intNumberOfAlgorithmObjectsToUse - 1; i++)
        {
            algorithm = CreateNewAlgorithmObject();
            AddAlgorithmListeners();
            algorithm.Run();
            intCurrentAlgorithmObject++;
        }

    }

    private TextMiningAlgorithm CreateNewAlgorithmObject()
    {
        TextMiningAlgorithm newAlg = new TextMiningAlgorithm();

        newAlg.SortedTermStruct = algorithm.SortedTermStruct;
        newAlg.PreprocessedSynonyms = algorithm.PreprocessedSynonyms;
        newAlg.DistanceMeasure = algorithm.DistanceMeasure;
        newAlg.HitComparerMethod = algorithm.HitComparerMethod;
        newAlg.LexiconEntries = allLexiconEntries.GetGroup(intCurrentAlgorithmObject, intNumberOfAlgorithmObjectsToUse);
        newAlg.MaxTermPercentageDeviation = algorithm.MaxTermPercentageDeviation;
        newAlg.MaxWordPercentageDeviation = algorithm.MaxWordPercentageDeviation;
        newAlg.MinWordsPercentageHit = algorithm.MinWordsPercentageHit;
        newAlg.NumberOfThreads = algorithm.NumberOfThreads;
        newAlg.PermutationType = algorithm.PermutationType;
        newAlg.RemoveStopWords = algorithm.RemoveStopWords;
        newAlg.RestrictPartialTextMatches = algorithm.RestrictPartialTextMatches;
        newAlg.Soundex = algorithm.Soundex;
        newAlg.Stemming = algorithm.Stemming;
        newAlg.StopWords = algorithm.StopWords;
        newAlg.Synonyms = algorithm.Synonyms;
        newAlg.Terms = algorithm.Terms;
        newAlg.UseSynonyms = algorithm.UseSynonyms;

        algorithm = null;

        return newAlg;
    }

这里是运行整个搜索过程的线程的开始:

            // Run the algorithm in it's own thread
            Thread algorithmThread = new Thread(new ThreadStart
                (RunSeveralAlgorithmObjects));
            algorithmThread.Start();

这里有什么可以防止先前的文本挖掘算法对象被垃圾回收吗?


5
我认为看到你的AddAlgorithmListeners 方法的代码会很有趣。原因是:事件 可能 导致对象无法释放,因为某个监听器对象仍然订阅了对象的事件。只要监听器没有被释放,被监听的对象就不能够被处理掉。 - stakx - no longer contributing
你需要创建数十万个对象才能出现OOM错误。拥有那么多算法似乎不太可能。问题不在你的代码片段中。使用内存分析器。 - Hans Passant
5个回答

6
我建议首先确定到底是什么在泄漏。然后推测出原因(例如事件处理程序中的引用)。
要确定是什么在泄漏:
  1. 为项目启用本地调试。属性 -> 调试 -> 选中 启用非托管代码调试
  2. 运行程序。由于内存泄漏可能是逐渐发生的,您可能不需要让它运行整整6个小时;只需让它运行一段时间,然后选择 调试 -> 中断所有
  3. 打开立即窗口。调试 -> 窗口 -> 立即
  4. 根据您运行的是32位还是64位、.NET 2.0/3.0/3.5还是.NET 4.0,从以下命令中选择一个,在立即窗口中输入:

    .load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll 适用于32位 .NET 2.0-3.5

    .load C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\sos.dll 适用于32位 .NET 4.0

    .load C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\sos.dll 适用于64位 .NET 2.0-3.5

    .load C:\WINDOWS\Microsoft.NET\Framework64\v4.0.30319\sos.dll 适用于64位 .NET 4.0

  5. 现在可以在立即窗口中运行SoS命令。我建议检查 !dumpheap -stat 的输出,如果无法确定问题,则检查 !finalizequeue

注:

  • 启用本机调试后第一次运行程序可能需要很长时间(几分钟),如果您已经设置了 VS 来加载符号。
  • 我推荐的调试器命令都以 !(感叹号)开头。

这些说明来自微软的不可思议的TessAdvanced .NET Debugging的作者Mario Hewardt。

一旦你确定了泄漏的对象,然后提出原因并实施修复方案。 然后,您可以再次执行这些步骤,以确定修复是否有效。


非常有趣,我以前从未听说过这种技术,但它似乎是一件非常有用的事情。我也同意系统地寻找内存泄漏的原因是最好的做法(当然,没有任何内存分析工具是不可能的,这就是为什么我建议一个众所周知的嫌疑人,即事件)。+1 - stakx - no longer contributing
如果你喜欢这个,那就看看这个:http://www.lovettsoftware.com/blogengine.net/post/2010/01/15/Visualizing-Runtime-Object-Graphs.aspx :) - Stephen Cleary
感谢您提供这些有用的高级.NET调试技巧。我之前并不知道这些。我成功地使用了这种方法,并找到了我的内存泄漏源。泄漏并不在我发布的实际代码中。我使用了一个全局的LINQ-to-SQL DataContext对象来存储算法进展中的所有结果。我发现全局datacontext对象缓存了与数据库的所有通信,并保留了我存储的所有字符串的活动状态。现在,每次保存到数据库时,我都会实例化一个新的datacontext对象,这解决了我的内存泄漏问题。谢谢! - gurkan

3

1) 如我在评论中所说,如果您的代码中使用了事件(AddAlgorithmListeners使我怀疑这一点),订阅事件可能会在对象之间创建“隐藏”的依赖关系,而这种依赖关系很容易被忘记。这种依赖关系可能意味着一个对象没有被释放,因为仍然有人在监听其中的一个事件。确保在不再需要监听它们时取消订阅所有事件。


2) 另外,我想指出您的代码中一个(可能不太离题)的问题:

private void RunSeveralAlgorithmObjects()
{
    ...
    algorithm.LexiconEntries = currentEntries;
    // ^ when/where is algorithm initialized?

    for (...)
    {
        algorithm = CreateNewAlgorithmObject();
        ....
    }
}

当调用此方法时,algorithm是否已初始化?否则,设置algorithm.LexiconEntries似乎不是有效的操作。这意味着您的方法依赖于一些外部状态,这可能会在程序逻辑中引入潜在的错误。

如果我理解正确,该对象包含描述算法的一些状态,并且CreateNewAlgorithmObject从当前状态派生algorithm的新状态。如果这是我的代码,我会将algorithm作为所有函数的显式参数,作为该方法依赖于该对象的信号。那么它将不再是隐藏的“外部”状态,而是函数所依赖的状态。

P.S.:如果您不想走这条路,您可以考虑将CreateNewAlgorithmObject转换为一个void方法,并在该方法内直接重新分配algorithm


1

AddAlgorithmListeners 是否将事件处理程序附加到算法对象公开的事件?监听对象是否比算法对象存活时间长 - 在这种情况下,它们可以继续防止算法对象被收集。

如果是,则在让该对象超出范围之前尝试取消订阅事件。

for (int i = 0; i < intNumberOfAlgorithmObjectsToUse - 1; i++)
        {
            algorithm = CreateNewAlgorithmObject();
            AddAlgorithmListeners();
            algorithm.Run();
            RemoveAlgoritmListeners();    // See if this fixes your issue.
            intCurrentAlgorithmObject++;
        }

+1,事件监听器也是我的猜测,不过你比我先说了! - Blindy
Blindy,下一个问题就交给你了。@OP:如果我的回答不够清晰,请参考这个问题:https://dev59.com/jXRB5IYBdhLWcg3wz6QJ。 - Gishu

1

GetGroup() 返回的 IEnumerable 是一次性使用还是被缓存的?也就是说,它是否保留了已发出的对象,如果是的话,它显然会随着每次迭代而线性增长。

内存分析非常有用,您尝试过使用分析器检查应用程序吗?我过去发现 Red Gate 的工具很有用(它不是免费的,但有评估版本,如果我没记错的话)。


需要补充的是,即使GetGroup生成器(枚举方法)的实例保留了发出的对象,如果没有对生成器方法实例的更多活动引用,GC仍然可以释放它们;也就是说,一旦currentEntriesalgorithm.LexiconEntries都不再引用IEnumerable,那么这些对象就会被释放。 - stakx - no longer contributing

0

我的怀疑在于 AddAlgorithmListeners(); 你确定执行完成后移除了监听器吗?


当我开始写这个时,我没有看到任何答案.. :) - ktutnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接