C#筆記 – AppDomain(應用域)

AppDomain
  • AppDomain是一組程序集的邏輯容器

    • CLR COM服務器初始化時會創建一個AppDomain(默認AppDomain),進程終止時銷毀

    • 目的是為了提供「隔離」

  • 具體功能

    • 一個AppDomain的代碼不能直接訪問另一個AppDomain的代碼創建的對象

      • 對象的生存期不能超過創建它的代碼所在的AppDomain

      • 一個AppDomain要訪問另一個AppDomain的對象:

        • 「按引用封送」

          • //按引用封送
            AppDomain ad = AppDomain.CreateDomain("AD #1");
            //切換至新Domain,然後在元數據中查找類型MarshalByRefType並構造
            //返回的是一個「代理」,而不是真實對象
            MarshalByRefType mbrt = (MarshalByRefType)ad
              .CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, "MarshalByRefType");
            mbrt.SomeMethod();
            AppDomain.Unload(ad);

            public class MarshalByRefType : MarshalByRefObject //繼承自MarshalByRefObject
            {
               public MarshalByRefType() { }

               public void SomeMethod() { }

               public MarshalByValType MethodWithReturn()
              {
                   return new MarshalByValType();
              }        
            }
        • 「按值封送」

          • //按值封送
            ad = AppDomain.CreateDomain("AD #2");
            //將對象序列化,加載到目標Domain後再反序列化
            //返回副本的引用
            mbrt = (MarshalByRefType)ad
              .CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, "MarshalByRefType");
            MarshalByValType mbvt = mbrt.MethodWithReturn();
            mbvt.OtherMethod();
            AppDomain.Unload(ad);

            [System.Serializable]
            public class MarshalByValType //可序列化
            {
               public void OtherMethod() { }
            }
    • AppDomain可以卸載

      • CLR不支持從AppDomain中卸載特定的程序集,但是可以讓CLR卸載一個AppDomain

    • AppDomain可以單獨保護

    • AppDomain可以單獨配置

卸載AppDomain
  • Step

    • CLR掛起進程中執行過托管代碼的所有線程

    • CLR檢查所有線程棧,查看哪些線程正在執行要卸載的AppDomain中的內容

      • 導致線程展開

      • 執行所有finally塊

    • 確定所有線程離開AppDomain後,CLR遍歷堆,為引用了「由已卸載」AppDomain創建的對象的每個代理對象設置一個標志

      • 表示它們引用的真實對象已經不在了,成為了「無效代理對象」

    • CLR強制GC

    • 恢復剩餘進程執行

監視AppDomain
  • 一旦開啟就不能關閉

  • 接口:

    • MonitoringSurvivedProcessMemorySize

      • 返回由當前CLR實例控制的所有AppDomain使用的字節數

    • MonitoringTotalAllocatedMemorySize

      • 返回特定AppDomain已分配字節數

    • MonitoringSurvivedMemorySize

      • 返回特定AppDomain當前正在使用的字節數

    • MonitoringTotalProcessorTime

      • 返回特定AppDomain的CPU占用率

    • 返回字節數的接口只保證在上一次GC時是準確的

Windows進程中的AppDomain與CLR
  • Windows進程運行著一個CLR COM服務器(執行引擎)
    • CLR管理著1個或以上的AppDomain

      • 每個AppDomain有自己的Loader堆

        • 每個Loader堆記錄了自AppDomain創建以來已訪問過的類型

          • Loader堆中每個類型對象(Type1/Type2)都有一個方法表

          • 方法表中的每個記錄項都指向JIT編譯的本機代碼(方法至少執行過1次)

        • 每個AppDomain加載了一些程序集

          • 如果兩個AppDomain都加載了同一個程序集(如圖中的System.dll),並使用了來自System.dll的一個類型,那麼兩個AppDomain的Loader堆會為相同的類型分別分配一個類型對象

          • 類型對象的內存不會由兩個AppDomain共享

          • 而調用方法時的IL代碼經JIT編譯後,生成的本機代碼也單獨與各自的AppDomain關聯

      • AppDomain中立

        • 有的程序集本來就要由多個AppDomain使用,如MSCorLib.dll

          • CLR初始化時,會自動加載該程序集,所有AppDomain共享該程序集中的類型

          • 為減少資源消耗,該程序集會以一種「AppDomain中立」的方式加載

        • CLR會為AppDomain中立方式加載的程序集維護一個特殊的Loader堆

          • 該Loader堆中的所有類型對象、類型定義的方法JIT編譯生成的本機代碼都會由進程中的所有AppDomain共享

          • 這些程序集無法卸載,只有在終止Windows進程時由Windows去回收資源

可執行應用程序(exe)對AppDomain的使用
  • 起動

    • Windows用托管exe文件初始化進程時,會加載「墊片」(MSCorEE.dll)

    • 「墊片」檢查程序集的CLR頭信息,決定加載哪個版本的CLR到進程

    • CLR加載後,CLR頭再次被檢查,查找並執行應用程序的入口方法

  • 運行

    • 代碼運行時會訪問其他類型,引用另一個程序集的類型時,CLR定位所需的程序集,將其加載到同一個AppDomain中

    • 入口方法返回後,Windows進程終止

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter