By default, a type is loaded into the same domain as its caller. This means that if the same assembly is referenced by code in two domains it will be jit'd twice and each domain will maintain separate copies of all the loader and EE data structures used to describe the class. In addition, these types have different runtime identities. TypeA loaded in DomainX and TypeA loaded in DomainY are not equivalent (the == operator will fail).
There are cases where the extra memory overhead introduced by this scenario is unacceptable. This is often the case for large assemblies that are typically shared by all the domains in a process. Examples of such assemblies are generally "system" assemblies like the Base Class Libraries, NGWS runtime, the ASP+ libraries, and so on. One could also imagine an ISV wanting to load an assembly that is shared across a family of products this way.
To address these issues, the NGWS runtime supports the notion of "shared assemblies". When a type from a shared assembly is loaded the first time, it is automatically mapped into all Domains instead of being loaded into each domain separately. As a result, shared assemblies consume fewer resources if multiple domains refer to them. Shared assemblies also speed up the creation of domains since the NGWS runtime doesn't need to re-jit the code or build multiple copies of the EE data structures used to represent the types. Shared assemblies are not unloaded until the runtime leaves the process.
While shared assemblies do reduce memory usage, they do result in slightly larger jitted code. In addition, since statics are scoped by domain, references to statics must be indirected when the assembly is shared instead of being accessed by direct memory reference. As a result, access to statics will be slower when the assembly is loaded shared than when it is not.
Assemblies can be loaded as “shared” by setting a flag on the calls to AppDomain::Load and Assembly::Load.