我们知道,在程序运行过程中,每个对象(object)都是对应了一块内存,这里的对象不仅仅指的是某个具体类型的实例(instance),也包括类型(type)本身。我想大家也很清楚CLR如何为我们创建一个类型的实例(instance)的:CLR计算即将被创建的Instance的size(所有的字段加上额外的成员所占的空间:TypeHandle和SyncBlockIndex);在当前AppDomain对应的managed heap中为之开辟一块连续的内存空间;初始化Instance的这两个额外的成员TypeHandle和SyncBlockIndex(TypeHandle是一个指针,指向Type的method table,SyncBlockIndex用于在多线程的条件下确保对该Instance操作的同步,它指向一块被称为Synchronization Block的内存块,我们对该Instance加锁, CLR会使instance的SyncBlockIndex指向某一个Synchronization Block,反之解锁会重置SyncBlockIndex);最后调用对应的constructor。

在面向对象的原则下,Instance的Field代表的是对象的状态(state), 而方法则体现的是对象的行为(behavior)。状态只能和具体的Instance绑定在一起,而属于同一类型的不同的Instance则具有一样的行为,所以行为是和Type绑定在一起的。同时Type定义了很多原数据的信息。这些基于Type的信息是如何保存的,今天我们就来简单地讨论这个问题。

一、 Sample

在开始介绍之前我们给出一个有趣例子。在讨论String interning的时候,我通过对具有相同字符序列的string进行加锁,证明了基于进程的string interning。今天我仍然沿用这种机制,不过进行加锁的对象不是string,而是Type对象。


上面是整个Solution的结构,为了把CustomType类型定义在一个和主程序不同的Assembly中,我添加了Artech.TypeInManagedHeap.ClassLibrary Project。CustomType是一个空的Class,没有定义任何的成员,因为我们需要的仅仅是CustomType这个Type本身:

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.TypeInManagedHeap.ClassLibrary
{
    public class CustomType
    {
    }
}

在Main所在的Artech.TypeInManagedHeap.ConsoleApp Project,我定义了一个MarshalByRefType,他继承自MarshalByRefObject,因为我需要让它在不同的AppDomain中以By Reference进行传递。唯一的方法ExecuteWithTypeLocked中,先对传入的Type对象加锁,获得锁后做一些输出,随后进行10s的时间延迟。

class MarshalByRefType : MarshalByRefObject
    {
        public void ExecuteWithTypeLocked(Type type)
        {
            lock (type)
            {
                Console.WriteLine("The operation with a Type locked is executed\n\tAppDomain:\t{0}\n\tTime:\t\t{1}\n\tType:{2}\n",
                   AppDomain.CurrentDomain.FriendlyName, DateTime.Now, type);
                Thread.Sleep(10000);
            }
        }
}
class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AppDomain appDomain1 = AppDomain.CreateDomain("Artech.AppDomain1");
                AppDomain appDomain2 = AppDomain.CreateDomain("Artech.AppDomain2");

                MarshalByRefType marshalByRefObj1 = appDomain1.CreateInstanceAndUnwrap("Artech.TypeInManagedHeap.ConsoleApp", "Artech.TypeInManagedHeap.ConsoleApp.MarshalByRefType") as MarshalByRefType;
                MarshalByRefType marshalByRefObj2 = appDomain2.CreateInstanceAndUnwrap("Artech.TypeInManagedHeap.ConsoleApp", "Artech.TypeInManagedHeap.ConsoleApp.MarshalByRefType") as MarshalByRefType;

                Thread thread1 = new Thread(new ParameterizedThreadStart(Execute));
                Thread thread2 = new Thread(new ParameterizedThreadStart(Execute));

                thread1.Start(marshalByRefObj1);
                thread2.Start(marshalByRefObj2);

                Console.Read();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.Read();
            }
        }

        static void Execute(object obj)
        {
            try
            {
                MarshalByRefType marshalByRefObj = obj as MarshalByRefType;
                marshalByRefObj.ExecuteWithTypeLocked(typeof(int));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.Read();
            }
        }
}

在主程序中,我在两个新创建的AppDomain中创建了MarshalByRefType 实例,并在各自的线程中调用ExecuteWithTypeLocked方法。该程序的目的是证明在不同线程中被加锁的Type对象是否是同一个对象,如果是同一个对象,果是两个线程中的操作的执行间隔应该是10s,否则他们几乎在同一个时刻执行。

首先进行加锁的对象是System.Int32 Type(typeof(int)))我们来运行程序,看看输出结果:


输出的两个时间刚好相差10s,这充分说明了在不同的AppDomain中进行加锁的System.Int32 Type是同一个对象。

我们现在来对我们自定义的CustomType Type进行加锁,我只需修改Execute方法:

static void Execute(object obj)
        {
            try
            {
                MarshalByRefType marshalByRefObj = obj as MarshalByRefType;
                marshalByRefObj.ExecuteWithTypeLocked(typeof(CustomType));                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.Read();
            }
        }

现在看看输出的结果:


两个操作输出了相同的时间,这说明了在两个不同AppDomain中进行加锁的CustomType Type对象并非同一个对象。

我们进一步作一些修改,在Main方法上运用LoaderOptimizationAttribute

    [LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)]
        static void Main(string[] args)
        {
            … …
        }

看看现在的输出又如何:


现在的时间间隔又变成了10s,也就是说,在这种情况下,CustomType Type虽然使用在不同的AppDomain中,但是它们实际是同一个对象。

二、Managed code的执行

我先不对上面出现的现象做出解释,我首先对在CLR下托管代码的执行过程做一个简单的介绍。我们就以上面的Sample为例,对于下面的4行code, 如果MarshalByRefType实现在另外一个Assembly中(假设叫做CustomAssembly.dll), 我们来看看CLR到底会为为我们做些什么。

AppDomain appDomain1 = AppDomain.CreateDomain("Artech.AppDomain1");
AppDomain appDomain2 = AppDomain.CreateDomain("Artech.AppDomain2");

MarshalByRefType marshalByRefObj1 = appDomain1.CreateInstanceAndUnwrap("Artech.TypeInManagedHeap.ConsoleApp", "Artech.TypeInManagedHeap.ConsoleApp.MarshalByRefType") as MarshalByRefType;
MarshalByRefType marshalByRefObj2 = appDomain2.CreateInstanceAndUnwrap("Artech.TypeInManagedHeap.ConsoleApp", "Artech.TypeInManagedHeap.ConsoleApp.MarshalByRefType") as MarshalByRefType;

CLR先创建了两个AppDomain:Artech.AppDomain1和Artech.AppDomain2。接着调用CreateInstanceAndUnwrap方法。发现一个MarshalByRefType类型,并且该类型并没有在已经加载的Assembly中定义,于是CLR会在一些特殊的目录或者GAC中试着找到定义了MarshalByRefType的CustomAssembly.dll,找到后加载该Assembly。注意该Assembly的加载是基于AppDomain的,也就是说,两个CustomAssembly.dll被分别加载到Artech.AppDomain1和Artech.AppDomain2中

每个AppDomain都具有一段限于自己使用的、被隔离的托管堆。托管堆中又具有很多不同的划分,分别基于不同的目的用于存储不同的信息。其中最重要的是GC heap和Loader heap。GC heap用于Reference type实例的存储,每个实例的生命周期受GC的管理。GC以某种机制进行垃圾收集回收垃圾对象的内存。Loader heap在存储原数据相关的信息,也就是我们说的Type。每个Type在Loader heap中体现为一个MethodTable,MethodTable主要记录了这些metadata的信息,比如Type的base type,实现的interface, Type被定义的module, static field,以及所有的方法,而System.Type则可以看成是对MethodTable的封装,在程序执行过过程中一个Type对象对应着一个具体的MethodTable。

当基于Type的Meta data被成功加载在各自AppDomain的Loader heap中之后,CLR便按照开篇介绍的Instance创建的过程创建对象,Instance对应的managed heap就是GC heap。在初始化Instance额外成员TypeHandle过程中,就是把它指向在Loader heap 的MethodTable。通过TypeHandle这个指针就可以定位到具体的Type。

如果我们执行了Intance的某个方法,那么CLR根据TypeHandle找到对应的MethodTable,随后定位到具体的方法,通过JIT Compiler把IL指令变成基于处理器的machine instruction,并执行之,该machine instruction被保存,用于下一次执行。

通过上面的介绍,我们说Assembly的加载和Type在Loader heap的加载都是基于某个单独AppDomain,是被AppDomain隔离起来,不能被其他的AppDomain共享。这样充分的利用AppDomain提供的内存隔离机制,保证了托管程序的健壮性。但是,从另一方面讲,由于在不同的AppDomain使用的Type,都会在该AppDomain中加载对应的Assembly,并在AppDomain所在的Loader heap加载基于Type的metadata,这样对于一些不是很Common的Type来说没有问题,但是对一些我们经常使用的基本类型,比如int,Array,object等,则会带来Performance的损耗和内存的压力。于是出现了另一种加载机制:以中立域的方式加载

在《再说string》中,我提到过,在CLR初始化过程中,会创建3个Domain:SystemDomainSharedDomainDefaultDomain。SharedDomain中加载的是一些AppDomain中性,能被各不同的AppDomain共享的信息。定义了一些基本类型,比如int,object,Array等的Assembly -MSCorLib.dll就是被加载到SharedDomain中供同一个进程的各个AppDomain共享。同理存储于SharedDomain的Loader heap的Type也是被各个AppDomain共享的。

MSCorLib.dll在初始化的时候被自动地加载到SharedDomain,而对于一般的Assembly,CLR为我们提供了一些方式实现这样的加载方式,比如在Main方法上运用LoaderOptimizationAttribute

三、回到Sample

有了上面的理论基础,我们再一次回到我们第一部分的Sample,对于运行的现象就很容易理解了:

首先对System.Int32 Type进行加锁,由于System.Int32 是定义在MSCorLib.dll中,并且该Assembly一中立的方式加载到SharedDomain中,同一个进程中的各个AddDomain用到的System.Int32 Type实际上是同一个对象。

后来我们又对我们自定义的CustomType进行加锁,这个Type对应的Assembly为Artech.TypeInManagedHeap.ClassLibrary.dll, 在某人的情况下两个Assembly被加载到我们新创建的两个AppDomain中,所以在AppDomain1和AppDomain2的CustomType Type是不同的对象。

最后我们在Main上运用了[LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)],实际上就是指示CLR以中立的方式把Artech.TypeInManagedHeap.ClassLibrary.dll加载到SharedDomain中,所以这时候爱两个AppDomain中的CustomType Type是相同的对象。

四、 一点补充

由于Type对象是基于Loader heap的,而非GC heap,所以Type的生命周期会保持到AppDomain被卸载,对于以中立的方式加载到SharedDomain的情况,Type对象的生命周期会延续要进程的结束。

另外,切忌对Type进行加锁,如果你对Type加锁,你实际上相当于对所有的该Type对应的instance加锁,粒度太大,极易形成死锁。

关于CLR如何创建对象,请参考:

《Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects》
  By Hanu Kommalapati and Tom Christian

转载于:https://www.cnblogs.com/artech/archive/2007/06/04/769805.html

What is Type in managed heap?相关推荐

  1. WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance

    我们知道WCF有3种典型的对service instance进行实例化的方式,他们分别与WCF的三种InstanceContextMode相匹配,他们分别是PerCall,PerSession和Sin ...

  2. 《博客园精华集---CLR/C#分册》

    <博客园精华集---CLR/C#分册> 转:http://www.cnblogs.com/anytao/archive/2008/09/04/lovechina_bestclr_3rdfi ...

  3. 聊聊flink TaskManager的managed memory

    序 本文主要研究一下flink TaskManager的managed memory TaskManagerOptions flink-core-1.7.2-sources.jar!/org/apac ...

  4. 不同dll相同Type.FullName引发的问题

    客户反应一个应用没有规律性的错误,一旦异常发生后大部分功能无法再使用,除非重新启动应用程序池.异常内容大致如下: 2282 Error 无法将[141]绑定到字段 ID(不能从System.Int32 ...

  5. transactionManager 以及datasource type解析

    transactionManager 以及datasource type解析 transactionManager 在 MyBatis 中有两种事务管理器类型(也就是 type="[JDBC ...

  6. Heap的讲解 - 介绍

    A priority queue is an abstract data type similar to a regular queue or stack data structure in whic ...

  7. dump heap及分析内存泄漏

    背景 在Android开发中,因为多了生命周期概念和相关类,很容易出现内存泄漏的问题,并且找到和复现这些问题很麻烦.不过内存泄漏是很容易被代码检测或者现成的工具检测出来的.基于这个场景,需要学会dum ...

  8. 调优 DB2 UDB v8.1 及其数据库的最佳实践-IBM developerWorks

    http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/dm-0404mcarthur/#sqltuning 级别: 初级 ...

  9. .NET深入学习笔记(3):垃圾回收与内存管理

    今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会 ...

最新文章

  1. 【CyberSecurityLearning 49】PHP与MySQL进行交互
  2. 北邮OJ 1027. 16校赛-Archer in Archery
  3. C++用二进制交换二个数的实现算法(附完整源码)
  4. 保存对象报错with two open Sessions
  5. 四则运算题目生成程序(基于控制台)
  6. JAVA多线程程序ProgressBar
  7. LindDotNetCore~职责链模式的应用
  8. 前端笔记-通过jQuery获取input数据及html中name的使用
  9. flask第七篇——URL与视图函数的映射
  10. Google Maps API 中的标注编程
  11. 谷歌翻译插件安装使用
  12. MongoDB下载安装教程(Windows)
  13. SAS硬盘优缺点概述
  14. 扫地机器人噪音响_扫地机器人噪音大的解决方法
  15. 「Medical Image Analysis」Note on 3D U-Net
  16. Source Insight 4.0安装破解及简单使用
  17. STP Security之BPDU Filter、BPDU Guard、Root Guard
  18. python矩阵内积乘_numpy矩阵向量乘法
  19. JRebel安装、使用
  20. Java单元测试实践-06.Mock后Stub静态方法

热门文章

  1. 查看磁盘uuid命令_Win10推出新的命令行工具,可以查看磁盘空间使用情况
  2. aba会导致问题_为什么说第三方仓储企业再专业也会导致很多库存问题呢?
  3. # 定义四边形_数学教研——认识四边形
  4. spark uniq 本质上就是单词计数
  5. parquet文件格式——本质上是将多个rows作为一个chunk,同一个chunk里每一个单独的column使用列存储格式,这样获取某一row数据时候不需要跨机器获取...
  6. 图解Skip List——本质是空间换时间的数据结构,在lucene的倒排列表,bigtable,hbase,cassandra的memtable,redis中sorted set中均用到...
  7. 第三次作业——结对编程
  8. python入门:常用模块—random模块
  9. ios purelayout--基础使用--进阶使用--看这就够了
  10. 原生js给div添加类