From http://geekswithblogs.net/akraus1/archive/2012/07/25/150301.aspx

I have written already some time ago how big a .NET object is. John Skeet as also made a very detailed post about object sizes in .NET. I wanted to know if we can deduce the object size not by experiments (measuring) but by looking at the Rotor source code. There is indeed a simple definition in the object headers how big a .NET object minimally can be. A CLR object is still a (sophisticated) structure which is at an address that is changed quite often by the garbage collector.

image

The picture above shows that every .NET object contains an object header which contains information about which thread in which AppDomain has locked the object (means called Monitor.Enter). Next comes the Method Table Pointer which defines a managed type for one AppDomain. If the assembly is loaded AppDomain neutral this pointer to the type object will have the same value in all AppDomains. This basic building block of the CLR type system is also visible in managed code via Type.TypeHandle.Value which has IntPtr size.

\sscli20\clr\src\vm\object.h

//
// The generational GC requires that every object be at least 12 bytes
// in size.
#define MIN_OBJECT_SIZE (2*sizeof(BYTE*) + sizeof(ObjHeader))
A .NET object has basically this layout:

class Object
{
protected:
MethodTable* m_pMethTab;

};
class ObjHeader
{
private:
// !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader.
DWORD m_SyncBlockValue; // the Index and the Bits
};

For x86 the minimum size is therefore 12 bytes = 2*4+4. And for x64 it is 24 bytes = 2*8+8. The ObjectHeader struct is padded with another 4 bytes in x64 which does add up to 24 bytes for every object instance. The MIN_OBJECT_SIZE definition has actually a factor two inside it whereas we would expect 8 as minimum empty object size. The previous sentence does contain already the answer to it. It makes little sense to define empty objects. Most meaningful objects have at least one member variable of class type which is indeed another pointer sized member hence the minimum size of 12 bytes (24) bytes in x86/x64.

It is interesting to know that the garbage collector does not know anything about AppDomains. For him the managed heap does only consist of objects which have roots or not and does clean up everything which is not rooted anymore. I found this during the development of WMemoryProfiler which uses DumpHeap of Windbg to get all object references from the managed heap. When I did access all objects found this way I got actually objects from other AppDomains as well. And they did work! It is therefore possible to share objects directly between AppDomains.

Why would you want to do that? Well it is fun and you can do really dirty stuff with that. Do you remember that you cannot unload assemblies from an AppDomain? Yes that is still true but why would you ever want to unload an assembly? Mostly because you were doing some dynamic code generation which will at some point in time dominate your overall memory consumption if you load generated assemblies into your AppDomain. I have seen this stuff many times for dynamic query generation. The problem is that if you load the dynamically created code into another AppDomain you need to serialize the data to the other AppDomain as well because you cannot share plain objects between AppDomains. To serialize potentially much data across AppDomain is prohibitively slow and therefore people live with the restriction that code gen will increase the working set quite a lot. With some tricks you can now share plain objects between AppDomain and get unloadable code as well.

Warning: This following stuff well beyond the specs but it does work since .NET 2.0 up to 4.5.
Do not try this at work!

When you load an assembly into your (default) AppDomain you will load it only for your current AppDomain. The types defined there are not shared anywhere. There is one exception though: The types defined in mscorlib are always shared between all AppDomains. The mscorlib assembly is loaded into a so called Shared Domain. This is not a real AppDomain but simply a placeholder domain where all assemblies are loaded which can be shared between AppDomains. An assembly loaded into the Shared Domain is loaded therefore AppDomain neutral. Assemblies loaded AppDomain neutral have one special behavior:

AppDomain neutral assemblies are never unloaded even when no “real” AppDomain is using them anymore.
The picture below shows in which scenarios assemblies are loaded AppDomain neutral (green) from the Shared Domain.

image

The first one is the most common one. You load an assembly into the default AppDomain. This defaults to LoaderOptimization.SingleAppDomain where every assembly is compiled from scratch again when other AppDomains with no special flags are created. Only the basic CLR types located in mscorlib are loaded AppDomain neutral and always shared between AppDomains no matter what (with .NET 1.1 this was not the case) flags are used.

If you create e.g. 5 AppDomains with the default settings you will load and JIT every assembly (except mscorlib) again and get different types for each AppDomain although the were all loaded from the same assembly in the same loader context.

The opposite is LoaderOptimization.MultiDomain where every assembly is loaded as AppDomain neutral assembly which ensures that all assemblies loaded in any AppDomain which have this attribute set are loaded and JITed only once and share the types (=Same Method Table pointer) between AppDomains.

An interesting hybrid is LoaderOptimization.MultiDomainHost which does load only these assemblies AppDomain neutral which are loaded from the GAC. That means if you load the same assembly one time from the GAC and a second time the same one unsigned from your probing path you will not get identical types but different Method Table pointers for the types.

Since we know that the GC does not know anything about AppDomains we (at least the managed heap objects do not contain infos in which AppDomain they reside) we should be able to pass object via a pointer to another AppDomain. This could come in handy if we generate a lot of code dynamically for each query which is made against a data source but we want a way to get rid of the compiled code by unloading the Query AppDomain from time to time without the normally required cost to copy the data to be queried every time from the Default AppDomain into the Query AppDomain.

image

You can see this in action in the sample Application AppDomainTests which is part of the test suite for WMemoryProfiler. Here is the code for the main application which does create AppDomains in a loop send data via an IntPtr to another AppDomain and get the calculated result back without passing the data via Marshalling by value to the other AppDomain.

`class Program
{
/// <summary>
/// Show how to pass an object by reference directly into another appdomain without serializing it at all.
/// </summary>
/// <param name="args"></param>
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
static public void Main(string[] args)
{
for (int i = 0; i < 10000; i++) // try it often to see how the AppDomains do behave
{
// To load our assembly appdomain neutral we need to use MultiDomainHost on our hosting and child domain
// If not we would get different Method tables for the same types which would result in InvalidCastExceptions
// for the same type.
// Prerequisite for MultiDomainHost is that the assembly we share the data is
// a) Installed into the GAC (which requires as strong name as well)
// If you would use MultiDomain then it would work but all AppDomain neutral assemblies will never be unloaded.
var other = AppDomain.CreateDomain("Test"+i.ToString(), AppDomain.CurrentDomain.Evidence, new AppDomainSetup
{
LoaderOptimization = LoaderOptimization.MultiDomainHost,
});

// Create gate object in other appdomain
DomainGate gate = (DomainGate)other.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(DomainGate).FullName);

// now lets create some data
CrossDomainData data = new CrossDomainData();
data.Input = Enumerable.Range(0, 10).ToList();

// process it in other AppDomain
DomainGate.Send(gate, data);

// Display result calculated in other AppDomain
Console.WriteLine("Calculation in other AppDomain got: {0}", data.Aggregate);

AppDomain.Unload(other);
// check in debugger now if UnitTests.dll has been unloaded.
Console.WriteLine("AppDomain unloaded");

}
}`
To enable code unloading in the other AppDomain I did use LoaderOptimzation.MultiDomainHost which forces all non GAC assemblies to be unloadable. At the same time we must ensure that the assembly that defines CrossDomainData is loaded from the GAC to get an equal MethodTable pointer accross all AppDomains. The actual magic does happen in the DomainGate class which has a method DoSomething which expects not a CLR object but the object address as parameter to weasel a plain CLR reference into another AppDomain. This sounds highly dirty and it certainly is but it is also quite cool ;-).

/// <summary>
/// Enables sharing of data between appdomains as plain objects without any marsalling overhead.
/// </summary>
class DomainGate : MarshalByRefObject
{
/// <summary>
/// Operate on a plain object which is shared from another AppDomain.
/// </summary>
/// <param name="gcCount">Total number of GCs</param>
/// <param name="objAddress">Address to managed object.</param>
public void DoSomething(int gcCount, IntPtr objAddress)
{
if (gcCount != ObjectAddress.GCCount)
{
throw new NotSupportedException("During the call a GC did happen. Please try again.");
}

// If you get an exception here disable under Projces/Debugging/Enable Visual Studio Hosting Process
// The appdomain which is used there seems to use LoaderOptimization.SingleDomain
CrossDomainData data = (CrossDomainData) PtrConverter<Object>.Default.ConvertFromIntPtr(objAddress);;

// process input data from other domain
foreach (var x in data.Input)
{
Console.WriteLine(x);
}

OtherAssembliesUsage user = new OtherAssembliesUsage();

// generate output data
data.Aggregate = data.Input.Aggregate((x, y) => x + y);
}

public static void Send(DomainGate gate, object o)
{
var old = GCSettings.LatencyMode;
try
{
GCSettings.LatencyMode = GCLatencyMode.Batch; // try to keep the GC out of our stuff
var addandGCCount = ObjectAddress.GetAddress(o);
gate.DoSomething(addandGCCount.Value, addandGCCount.Key);
}
finally
{
GCSettings.LatencyMode = old;
}

}

}
To get an object address I use Marshal.UnsafeAddrOfPinnedArrayElement and then try to work around the many race conditions this does impose. But it is not as bad is it sounds since you do need only to pass an object via a pointer once into the other AppDomain and use this as data gateway to exchange input and output data. This way you can pass data via a pointer to another AppDomain which can be fully unloaded after you are done with it. To make the code unloadable I need to use LoadOptimization.MultiDomainHost for all AppDomains. The data exchange type is located in another assembly which is strong named and you need to put it into the GAC before you let the sample run. Otherwise it will fail with this exception

Unhandled Exception: System.InvalidCastException: [A]AppDomainTests.CrossDomainData cannot be cast to [B]AppDomainTests.CrossDomainData. Type A originates from 'StrongNamedDomainGateDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=98f280cda3cbf035' in the context 'Default' at location 'C:\Source\WindbgAuto\bin\AnyCPU\Release\StrongNamedDomainGateDll.dll'. Type B originates from 'StrongNamedDomainGateDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=98f280cda3cbf035' in the context 'Default' at location 'C:\Source\WindbgAuto\bin\AnyCPU\Release\StrongNamedDomainGateDll.dll'.
at AppDomainTests.DomainGate.DoSomething(Int32 gcCount, IntPtr objAddress) in C:\Source\WindbgAuto\Tests\AppDomainTests\DomainGate.cs:line 24
at AppDomainTests.DomainGate.DoSomething(Int32 gcCount, IntPtr objAddress)
at AppDomainTests.DomainGate.Send(DomainGate gate, Object o) in C:\Source\WindbgAuto\Tests\AppDomainTests\DomainGate.cs:line 50
at AppDomainTests.Program.Main(String[] args) in C:\Source\WindbgAuto\Tests\AppDomainTests\Program.cs:line 41

at first it looks a little pointless to deny a cast to an object which was loaded in the default loader context for the very same assembly. But we do know now that the Method Table pointer for CrossDomainData is different between the two AppDomains. When you install the assembly into the GAC (be sure to use the .NET 4 gacutil!) the error goes away and we then get:

0
1
2
3
4
5
6
7
8
9
Calculation in other AppDomain got: 45

which shows that we can get data and are able to modify it directly between AppDomains. If you use this code in production and it does break. I have warned you. This is far beyond what the MS engineers want us to do and it can break the CLR in subtle unintended ways I have not found yet. Now you have got (hopefully) a much better understanding how the CLR type system and the managed heap does work. If questions are left. Start the application and look at !DumpDomain and !DumpHeap –stat and its related commands to see for yourself.

The Truth About .NET Objects And Sharing Them Between AppDomains的更多相关文章

  1. .NET 类型(Types)的那些事

    引言 您是.Net工程师?那 .NetFramework中的类型您知道有三大类吗?(除了引用类型和值类型,还有?) 引用类型一定在“堆”上,值类型一定在“栈”上? 那引用类型在内存中的布局细节您又知道 ...

  2. 【转】What is an entity system framework for game development?

    What is an entity system framework for game development? Posted on 19 January 2012 Last week I relea ...

  3. 论文阅读之 Inferring Analogous Attributes CVPR 2014

    Inferring Analogous Attributes     CVPR  2014 Chao-Yeh Chen and Kristen Grauman Abstract: The appear ...

  4. Object Pascal中文手册 经典教程

    Object Pascal 参考手册 (Ver 0.1)ezdelphi@hotmail.com OverviewOverview(概述)Using object pascal(使用 object p ...

  5. JCIP chap3 share objects

    "同步"确保了操作的原子性执行,但它还有其它重要的方面:memory visibility.我们不但要确保当一个线程在使用一个对象的时候,其它线程不能修改这个对象,而且还要保证该线 ...

  6. Adding AirDrop File Sharing Feature to Your iOS Apps

    http://www.appcoda.com/ios7-airdrop-programming-tutorial/ Adding AirDrop File Sharing Feature to You ...

  7. Add sharing to your app via UIActivityViewController

    http://www.codingexplorer.com/add-sharing-to-your-app-via-uiactivityviewcontroller/ April 4, 2014 Ev ...

  8. Python string objects implementation

    http://www.laurentluce.com/posts/python-string-objects-implementation/ Python string objects impleme ...

  9. 论文阅读笔记五十:CornerNet: Detecting Objects as Paired Keypoints(ECCV2018)

    论文原址:https://arxiv.org/pdf/1808.01244.pdf github:https://github.com/princeton-vl/CornerNet 摘要 本文提出了目 ...

随机推荐

  1. 【动态规划】bzoj1669 [Usaco2006 Oct]Hungry Cows饥饿的奶牛

    #include<cstdio> #include<algorithm> using namespace std; int n,a[5001],b[5001],en; int ...

  2. 【kd-tree】bzoj4154 [Ipsc2015]Generating Synergy

    区间修改的kd-tree,打标记,下传. 每次询问的时候,从询问点向上找到根,然后依次下传下来,再回答询问. #include<cstdio> #include<algorithm& ...

  3. python中xrange和range的异同

    本文章系转载,原文来源不详. range    函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列.range示 ...

  4. LightOJ1027 A Dangerous Maze(期望)

    题目大概说你正在起点,面前有$n$个门,每个门有一个数字$x$,正数表示开这个门$x$分钟后会出去,负数表示开这个门$-x$分钟后会回到起点.选择门的概率是一样的且每次选择互不影响.问出去的时间期望是 ...

  5. Ubuntu安装sougou输入法

    1. 按照[1]的步骤进行,完美实现就好. 2. 必须重启后才能实现功能. Reference: [1] http://pinyin.sogou.com/linux/

  6. 技术文档--volley 框架

    Volley 框架 参考文档:http://cache.baiducontent.com/c?m=9f65cb4a8c8507ed4fece763105392230e54f73e7e808c027fa ...

  7. 关于Yaf的一些说明[转Yaf作者]

    关于Yaf的一些说明[转Yaf作者] 说明下,这篇文章转至于yaf作者鸟哥的博文,因为最近在系统的学习Yaf框架,发现了这篇文章,感觉对于了解Yaf框架还是很有用处,所以转过来和大家分享一下 Yaf是 ...

  8. Magcodes.WeiChat——通过CsvFileResult以及DataAnnotations实现导出CSV文件

    我们先来看看效果图: 从上图中可以看出,导出的文件中列名与表格名称保持一致,并且忽略了某些字段. 相关代码实现 我们来看相关代码: 页面代码: @using (Html.BeginForm(" ...

  9. Asp.Net Web API 2第四课——HttpClient消息处理器

    Asp.Net Web API 导航   Asp.Net Web API第一课:入门http://www.cnblogs.com/aehyok/p/3432158.html Asp.Net Web A ...

  10. 在Linux CentOS上编译并安装Clang 3.5.0

    编译CoreCLR需要Clang 3.5,而CentOS上安装的是Clang 3.4.2(yum repos中最新版也是这个),只能自己手工编译LLVM的源代码进行安装. (注:CentOS的版本是6 ...