对于类型的静态数据,每个AppDomain都有其私有副本。至于AppDomain是否需要可执行代码的私有副本,则取决于其他某些因素。为了理解这一点,我们首先看看CLR是如何管理代码的。
AppDomain会对JIT编译器的工作方式造成影响,尤其是JIT编译器能够以按进程或者按AppDomain为基础生成代码,当所有的AppDomain都在一个进程中时,它们之间会共享机器码,从而导致对工作集(working set)的影响下降:然而,只要程序访问类型的静态字段,未经处理的调用就会影响速度。相比之下,当CLR为每一个AppDomain生成机器码后,静态字段的访问就会快一些,但是对工作集的影响也就大一些,基于上述原因,CLR允许开发人员控制JIT编译的代码的管理方式。
当CLR首次初始化AppDomain时,CLR将获得一个加载器优化标志(System.LoaderOptimization)。对于被该AppDomain加载的模块,这个优化标志控制代码是如何被JIT编译的。如表8.3所示,该标志有3个可能的值。
SingleDomain标志(默认情形)假定进程只包含一个AppDomain,因而,每个域的机器码分别由JIT编译。这样,静态字段的访问就会快些,并且,由于只有一个AppDomain.所以只需要一个机器码的拷贝,对工作集也不会产生什么影响。当然,如果创建了多个AppDomain,那么,每个AppDomain都会有自己的机器码副本,这也是MultiDomain标志存在的原因。
MultiDomain标志假定进程包含多个AppDomain,并运行在同一个应用程序中。对于整个进程而言,只生成了一个机器码的副本。这使得静态字段的访问有些慢,不过,降低了多个AppDomain对内存的影响。
图8.9说明了加载器优化设置的效果。这个特征是通过简单的C#类型定义,以及JIT编译的IA-32机器码(针对各种设置)表现出来。如果使用SingleDomain标志,JIT编译器就会将静态字段地址(例如,ds:[3E5llDh])插入到本机代码流中。这是合理的,原因就是每个AppDomain将拥有自己的方法代码副本,以及不同的字段地址。当使用MultiDomain加载器优化时,访问静态字段的方法就有一个附加的序言,即调用CLR内置的GetCurrentDoaminData例程,这个例程将从硬TLS中加载AppDomain的静态数据的基地址。这个额外的步骤对每个使用静态字段的方法而言,将增加大约15条IA-32指令。然而,代码是通用的,在内存中只需要有一个副本,而不管有多少AppDomain在使用它。
很显然,这两种加载器优化策略各有千秋。对于许多应用程序而言,都需要在这两种情形下进行折衷。其结果就是MultiDomainHost。
MultiDomainHost标志假定进程将包含几个AppDomain,各个AppDomain将运行不同的应用程序代码。在这种混台模式下,只有从全局程序集缓存中加载的程序集,才能共享机器码(按MultiDomain的方式)。没有从GAC加载的程序集,只能被加载的AppDomain使用,并且使用加载它们的各个AppDomain的私有机器码(按SingleDomain的方式)。
这里需要强调的是:不论使用哪种加载器优化方式,进程中的AppDomain都将其机器码共享给microlib。LoaderOptimization不会对microlib产生什么影响。
当mscroee首次在os进程中初始化运行环境时,宿主程序会对默认域指定3个加载器优化策略中的一个。对于托管的可执行代码,os进程加载器直接加载(例如.Windows NT下的CreateProcess),主入口点方法(c#程序的Main方法)可以通过使用System.Loader.Optimization
Attribute特性,表明使用哪个策略。示例8.7中将一个托管的可执行程序的优化策略设置为MultiDomain。
示例8.7 设置托管的可执行代码LoaderOptimization
using System;
public class MyApp{
//重写SingleDomain的默认方式
[LoaderOptimization(LoaderOptimization.MultiDomain)]
public static void Main(){
//在这里生成域,并开始工作
}
}
有意思的是,Asp.Net的工作进程使用MultiDomainHost选项。在asp.net的工作进程中,来自各个web应用程序目录(私有)的代码使用较快的按AppDomain的代码;但是,所有Web应用程序使用的公共代码(例如,数据访问和XML堆栈)则是每进程只JIT编译一次,这样便减少了整个工作集的大小。