6

我有一个内存泄漏非常慢的服务。如果我分析 .NET CLR Loading 计数器,我会看到Current Classes Loaded计数器不断增加,并且始终与Total Classes Loaded计数器匹配。这给我的印象是内存泄漏与未释放的资源有关(这只是一个猜测)。

该服务每次执行任务时都会创建新的 appDomain(插件架构)。

我需要找出类名,以便缩小泄漏的原因。我对 WinDbg 不是很精通,但我想知道是否有人可以引导我完成枚举这些Loaded类的步骤。

我确实有源代码,因此我可以在必要时获取符号文件。提前感谢您的帮助!

4

9 回答 9

2

这是 .net 2.0 还是更高版本?如果是这样,您可能不会卸载AppDomain(正如 Jon Skeet 在评论中所说)。

如果是 1.1 或更低版本,则AppDomain卸载功能中存在错误。AppDomain即当“卸载”时它不会释放内存或释放资源。

(这是从 .net 2.0 开始修复的)

于 2008-12-05T14:27:53.293 回答
2

您可以随时检查 AppDomain 中加载了哪些程序集:

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
      Console.WriteLine(assembly.FullName);
}

因此,如果您不小心将程序集加载到错误的域中,也不会很难看到。

编辑:

如果你想使用 WinDgb SOS,这里是支持的命令。您最有可能对以下内容感兴趣:DumpDomain、DumpClass、DumpAssembly、EEHeap ...

于 2008-12-05T16:59:15.593 回答
2

我相信这个问题实际上是由一系列未处理的 FileSystemWatcher 实例引起的,这些实例嵌套在 RemoteTaskRunner MBRO 中。我仍然不确定我是否已经完全解决了内存泄漏,但我绝对可以分辨出区别。

这似乎不是 FileSystemWatchers 第一次给我带来问题。:)

感谢大家(尤其是 leppie)帮助我解决这个问题!

于 2008-12-05T20:44:27.400 回答
1

我建议使用适当的内存分析器——比如“.NET Memory Profiler”——http: //memprofiler.com/ 你当然可以在评估时尝试一下,看看它是否是一种有用的工具。

这将使您比核心 WinDBG/SoS 的东西更容易看到所有活动对象。

于 2008-12-05T14:23:16.140 回答
1

正如另一个答案所说,应该使用 AppDomain.Unload() 。

但是,您需要注意不要在多个位置加载程序集,尤其是主应用程序域。

查看 AppDomain.Load(AssemblyName) 的 MSDN 文档以了解上述情况如何发生。

同样在同一行中,您确定您使用的是正确的远程类吗?如果没有,上述情况肯定会发生。

于 2008-12-05T14:31:59.510 回答
1

我刚刚在 Suzanne Cook 的博客上读到了这篇文章。

http://blogs.msdn.com/suzcook/archive/2003/06/12/57169.aspx

确保不传递任何类型/程序集/等。实例(除了您的 MarshalByRefObject 类型)返回到原始 appdomain。如果这样做,它将导致这些程序集被加载到原始 appdomain 中。如果两个 appdomain 之间的 appdomain 设置不同,则这些程序集可能无法在此处加载。另外,即使它们被成功加载,在卸载目标 appdomain 后,即使原始 appdomain 从未使用它们,程序集也会保持加载和锁定状态。

当她说任何类型/组装/等时。她可能是什么类型的?我问的原因是因为我的 MarshalByRefObject (RemoteTaskRunner) 在任务运行后确实返回了一个 DateTime 对象。这会导致插件程序集加载到我的主 appDomain 中(并最终导致内存泄漏)吗?

于 2008-12-05T16:13:56.823 回答
1

每当有人报告内存泄漏时,我总是把它扔在那里,因为它让我忙了几个星期。不要在调试模式下运行您的应用程序。如果您在 .Net 2.0+ 中以调试模式运行您的应用程序(在 .Net 1.1 中没有发生),并且您实例化了一个包含事件的类,即使您不引发该事件,它也只会容纳一个小一块记忆。这会极大地影响长时间运行的应用程序,例如服务和 Web 应用程序,因为随着时间的推移,在实例化对象后消耗的少量内存可能会增加很多。

于 2008-12-05T20:51:00.400 回答
0

编辑:阅读其他一些帖子后,在使用更好的分析器和解决 appDomain 问题后,应考虑这些。

您可能希望将性能计数器添加到您的服务中,以便至少可以跟踪您创建的对象。这将帮助您确定 Classes.Loaded 是您的类还是 CLR 类。

在显式创建和销毁对象时添加一些调试日志记录可能会有所帮助。

作为最后的手段,您可以尝试将 GC.Collect() 放在那里,看看调用它是否可以解决您的问题。它不是修复,但测试它会让你知道它是一个选项。

于 2008-12-05T14:27:16.087 回答
0

AppDomain 已卸载,但来自 leppie 的响应让我想知道插件程序集是否同时加载到主 AppDomain 和辅助 AppDomain 中。当我查看性能计数器时,当前的 AppDomain 计数不会不断增加。

该应用程序应该创建一个辅助 appDomain,然后加载一个单独的插件程序集。也许一些代码会有所帮助:

从主 appDomain 创建辅助 AppDomain:

AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationName = "RemoteAgentLib";
ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
ads.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
ads.ShadowCopyDirectories = AppDomain.CurrentDomain.BaseDirectory;
ads.ShadowCopyFiles = "true";

m_domain = AppDomain.CreateDomain("RemoteTaskRunner", null, ads);

使用 RemoteTaskRunner 在辅助 appDomain 中加载插件:

RemoteTaskRunner taskRunner = m_domain.CreateInstanceAndUnwrap(
                    Assembly.GetExecutingAssembly().FullName,
                    typeof (RemoteTaskRunner).FullName) as RemoteTaskRunner;
taskRunner.LoadTask(taskInfo.Assembly, taskInfo.Type);

使用 RemoteTaskRunner 在辅助 appDomain 中执行任务:

[Serializable]
internal class RemoteTaskRunner : MarshalByRefObject
{
    private ITask m_task;

    public RemoteTaskRunner()
    {
    }

    internal void LoadTask(string assembly, string type)
    {
        // This assembly should load in the secondary appDomain.
        Assembly taskAssembly = AppDomain.CurrentDomain.Load(assembly);
        m_task = taskAssembly.CreateInstance(type) as ITask;
    }

    internal void RunTask(string taskConfig)
    {
        // This method should run in the secondary appDomain.
        m_task.RunTask(taskConfig, m_logger);
    }
...
...

要执行插件任务,在 Primary appDomain 中使用以下代码行:

taskRunner.RunTask(taskInfo.TaskConfig);

任务完成后,appDomain 被卸载:

AppDomain.Unload(m_domain);
于 2008-12-05T15:50:27.353 回答