The Truth About .NET Objects And Sharing Them Between AppDomains
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的更多相关文章
- .NET 类型(Types)的那些事
引言 您是.Net工程师?那 .NetFramework中的类型您知道有三大类吗?(除了引用类型和值类型,还有?) 引用类型一定在“堆”上,值类型一定在“栈”上? 那引用类型在内存中的布局细节您又知道 ...
- 【转】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 ...
- 论文阅读之 Inferring Analogous Attributes CVPR 2014
Inferring Analogous Attributes CVPR 2014 Chao-Yeh Chen and Kristen Grauman Abstract: The appear ...
- Object Pascal中文手册 经典教程
Object Pascal 参考手册 (Ver 0.1)ezdelphi@hotmail.com OverviewOverview(概述)Using object pascal(使用 object p ...
- JCIP chap3 share objects
"同步"确保了操作的原子性执行,但它还有其它重要的方面:memory visibility.我们不但要确保当一个线程在使用一个对象的时候,其它线程不能修改这个对象,而且还要保证该线 ...
- Adding AirDrop File Sharing Feature to Your iOS Apps
http://www.appcoda.com/ios7-airdrop-programming-tutorial/ Adding AirDrop File Sharing Feature to You ...
- Add sharing to your app via UIActivityViewController
http://www.codingexplorer.com/add-sharing-to-your-app-via-uiactivityviewcontroller/ April 4, 2014 Ev ...
- Python string objects implementation
http://www.laurentluce.com/posts/python-string-objects-implementation/ Python string objects impleme ...
- 论文阅读笔记五十:CornerNet: Detecting Objects as Paired Keypoints(ECCV2018)
论文原址:https://arxiv.org/pdf/1808.01244.pdf github:https://github.com/princeton-vl/CornerNet 摘要 本文提出了目 ...
随机推荐
- hibernate cascade=CascadeType.All
因为时间关系,我在这里测试的环境是一对多的关系里面用到的注解方式的级联,网上也有很多贴子,我也看过了,但是呢,我还是自己总结一下吧,这觉得级联是单向的,不是双向的,意思就是说,我们在设置两个类的对象之 ...
- UIView的剖析(转)
转自:http://blog.csdn.net/mengtnt/article/details/6716289 前面说过UIViewController,但是UIView也是在MVC中非常重要的一层 ...
- Android性能优化方法(九)
通常我们写程序,都是在项目计划的压力下完成的,此时完成的代码可以完成具体业务逻辑,但是性能不一定是最优化的.一般来说,优秀的程序员在写完代码之后都会不断的对代码进行重构.重构的好处有很多,其中一点,就 ...
- rename() 是原子的么
对一个文件做修改, 通常认为 直接 open-seek-write-write-close 不是原子的. 1. write 本身 不一定是原子的: https://blogs.msdn.microso ...
- Visual Studio 2013 支持MVC3不完善,Razor智能提示不完整或者不提示
以下只是针对MVC3. 前天试用Orchard 1.8,用VS2013新建C#类库项目(ClassLibrary project),然后新建Views文件夹,新建cshtml,然后引用MVC3的相关d ...
- java反射保存
前言 代码是我师父的,代码是我师父的,代码是我师父的,如有需要拿走的时候请标注 copyright by 山人Wu 记录这篇是为了加深理解,前段时间只是当做工具类来用,才有时间好好看一下,加深理解 ...
- PHP字符处理基础知识
<?php class StrDemo { function StrTest() { $s = "abcd"; print '$s length:'.strlen($s).& ...
- JavaScript text highlighting JQuery plugin
介绍一个JQuery的插件,用来在页面上高亮显示匹配到的字符串. Demo 点击下面的两个链接以查看效果: highlight javascript 点击Remove highlights移除高亮显示 ...
- C#与数据库访问技术总结(十一)之数据阅读器(DataReader)1
数据阅读器 当执行返回结果集的命令时,需要一个方法从结果集中提取数据. 处理结果集的方法有两个: 第一,使用数据阅读器(DataReader): 第二,同时使用数据适配器(Data Adapter)和 ...
- js中的hasOwnProperty()和isPrototypeOf()
js中的hasOwnProperty()和isPrototypeOf() 这两个属性都是Object.prototype所提供:Object.prototype.hasOwnProperty()和Ob ...