传递给已卸载 AppDomain 的服务的内存泄漏

Posted

技术标签:

【中文标题】传递给已卸载 AppDomain 的服务的内存泄漏【英文标题】:Memory leaking for service passed to unloaded AppDomain 【发布时间】:2014-06-14 17:47:00 【问题描述】:

我在AppDomains 的上下文中遇到内存泄漏。我将其简化为以下内容:

我有 3 个项目、两个库项目和一个控制台项目:Shared、DynamicallyLoadableRemotingTimeoutPrototype(控制台程序)。 Shared 包含DynamicallyLoadableRemotingTimeoutPrototype 使用的接口。两者都在编译时引用 Shared。任何项目之间都没有其他编译时引用。

共享包含以下内容:

public interface IHostService

    string GetStuff();

public interface IRemoteClass

    IHostService Alpha  get; set; 
    string CallHostServices();

DynamicallyLoaded 只包含一个类型:

public class RemoteClass : MarshalByRefObject, IRemoteClass

    public IHostService Alpha  get; set; 
    public string CallHostServices()
    
        Console.WriteLine("Domain 0, RemoteClass.CallHostServices():", AppDomain.CurrentDomain.Id);
        return Alpha.GetStuff();
    

控制台项目包含IHostService的实现:

public class Alpha : MarshalByRefObject, IHostService

    readonly byte[] mBuffer = new byte[100*1024*1024];
    public Alpha()
    
        for (var i = 0; i < mBuffer.Length; ++i)
            mBuffer[i] = (byte) (i%256);
    
    public string GetStuff()
    
        return "Alpha";
    

Program.Main 包含以下内容:

while (true)

       var otherDomain = AppDomain.CreateDomain("OtherDomain");
       var proxy =
           (IRemoteClass)
            otherDomain.CreateInstanceFromAndUnwrap("../../../DynamicallyLoadable/bin/debug/DynamicallyLoadable.dll",
                        "DynamicallyLoadable.RemoteClass");
       var alpha = new Alpha();
       proxy.Alpha = alpha;
       Console.WriteLine(proxy.CallHostServices());
       Thread.Sleep(TimeSpan.FromSeconds(2));
       AppDomain.Unload(otherDomain);
       RemotingServices.Disconnect(alpha); // this was just an attempt, doesn't change a thing whether it's there or not
       GC.Collect(); // same here, this shouldn't really be necessary, I just tried it out of despair

现在,当我让它运行一段时间后,内存消耗不断增加,最终我遇到了 OutOfMemoryException。

我希望如果持有代理的域被卸载,那么代理也被卸载,并且不再有对具体 Alpha 的引用,所以这将被收集。但显然不是。

请注意,我还通过引用 mscoree 并使用以下代码枚举加载的域来检查域是否真的被卸载:

var runtimeHost = new CorRuntimeHost();
runtimeHost.EnumDomains(out handle);
// etc.

另外,如果我将一个处理程序附加到 otherDomain.DomainUnload(),这个处理程序就可以正常调用。

请问有人能解释一下吗?

【问题讨论】:

【参考方案1】:

这可能是阻塞终结线程的典型问题(因为您可能在 STA 线程中有 while-loop,并且永远不会显式地将控制权交给其他线程)。即使您卸载/处置域,它也可能在内存中,因为它尚未最终确定。在此处阅读更多内容:http://alexatnet.com/articles/memory-leaks-in-net-applications - 特别是“阻塞的终结线程”部分(我是作者)

【讨论】:

我刚刚阅读了您的文章。明天我将根据您的描述使用 WinDbg(现在我住的地方几乎是凌晨 2 点;)检查我是否在“准备完成”对象中看到任何堆积。然而,我想知道在我的情况下会有什么解决方法。也许相关:while 循环是我的 Main 的组成部分,仅此而已。 我只是尝试了一种更简单的方法来检查代理的 RemoteClass 实例是否被破坏,方法是向 RemoteClass 添加一个析构函数,如下所示: ~RemoteClass() Console.WriteLine("Being destroy"); - 我看到控制台消息,因此代理实例正在被破坏。哪个会打败终结器理论,不是吗? 我猜这个线程中应该销毁/终结的不是 RemoteClass,而是代理。无论如何,您是否尝试在 GC.Collect 之后添加 WaitForPendingFinalizers?

以上是关于传递给已卸载 AppDomain 的服务的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

即使我卸载 appdomain,我的 dll 也不会卸载

卸载 appdomain 不会清除 C++ COM 对象静态成员

将 c# 程序集动态加载和卸载到 appdomain

卸载 appdomain 时出错。 (来自 HRESULT 的异常:0x80131015),在 Windows 服务中

Vue.js 3 卸载和内存泄漏

如何处理以设置间隔卸载组件以避免内存泄漏