【转】关于CLR内存管理一些深层次的讨论[上篇]

半年之前,PM让我在部门内部进行一次关于“内存泄露”的专题分享,我为此准备了一份PPT。今天无意中将其翻出来,觉得里面提到的关于CLR下关于内存管理部分的内存还有点意思。为此,今天按照PPT的内容写了一篇文章。本篇文章不会再讨论我们熟悉的话题,比如“值类型、引用类型具有怎样的区别?”、“垃圾回收分为几个步骤?”、“Finalizer和Dispose有何不同”、等等。整篇文章分上下两篇,上篇主要谈论的是“程序集(Assembly)和应用程序域(AppDomain)”。也许有的地方说的不是很正确,希望读者不吝赐教。

一、程序集与应用程序域

何谓程序集(Assembly)?它是一个托管应用的基本的部署单元。一个程序集是自描述的(通过元数据)、能够实施版本策略和部署策略。我倾向于这样的方式来定义程序集:“Assembly is a reusable, versionable, and self-describing building block of a CLR application.”从结构组成来看,一个程序集主要由三个部署组成:IL指令元数据资源。程序集的结构组成如下图所示。

image

那么什么是应用程序域呢?从功能上讲,通过应用程序域实现的隔离机制为托管代码的执行提供了一个安全的边界。从与程序集的关系来讲,我们可以将应用程序域看成是加载程序集的容器。只有相关的程序集被CLR加载到相应的应用程序域中,才谈得上代码的执行。

基于应用程序域的隔离,归根结底是内存的隔离。一个基本的反映就是:在一个应用程序域中创建的对象,不能直接在另一个应用程序域中使用。这中间需要有一个基本的跨应用程序域传递的机制,我们将这种机制称之为“封送(Marshaling)”。具体来讲,又具有两种不同的封送方式:按值封送(MBV:Marshaling By Value )和按引用封送(MBR:Marshaling By Reference)。MBV主要采用序列化的方式,而MBR最典型的就是.net Remoting。

二、系统程序域、共享程序域和默认程序域

image

当托管应用被启动后,在执行第一句代码之前,CLR会先后为我们创建三个应用程序域:系统程序域(System Domain)、共享程序域(Shared Domain)和默认程序域(Default Domain),它们分别具有不同的作用。

  • 系统程序域:系统程序域是第一个被创建的应用程序域,同时也是其他两个应用程序域的创建者。在该程序域初始化过程中,由它将msCorLib.dll这个程序集(这是一个很重要的程序集,.NET类型系统最基本的类型在该文件中定义)加载到共享程序域中。此外,驻留的字符串也被保存在此系统程序域中。系统程序域的一个主要的任务是追踪其他所有应用程序域的状态,并负责加载和卸载它们;
  • 共享程序域:共享程序域主要用于保存以“中立域(Domain-neutral Domain )”加载的程序集容器。所谓“中立域 ”方式加载的程序集,就是说程序集并不被加载到当前的程序域中并被该程序域专用,而是加载到一个公共的程序域中被所有程序域共享。
  • 默认程序域:我们的托管程序最终就运行在该程序域中,默认程序域可以通过System.AppDomain表示。

三、字符串的驻留

上面的文字描述实际上透露一些重要的信息,其中一个就是字符串的驻留(String Interning)。关于字符串的驻留,我想大家都不陌生,所以在这里我就不作重复的介绍了。在这里,我只想讨论一个问题:字符串的驻留是基于整个进程的,不是仅仅基于某个应用程序域

从上面的描述我们知道,字符串对象和一般的引用类型对象具有很大的不同:字符串对象直接被保存到系统程序域中,而一般的引用类型对象我们都是最终保存在GC堆中。从某种意义上讲,在字符串驻留机制下,字符串也是以“中立域”的方式被加载的,被驻留的字符串能够被同一个进程下所有应用程序域所共享。

那么,我们是否可以通过一些比较直观的方式来验证这一点。但是,我们不能直接编写程序来比较两个应用程序域中字符串是否是相同的引用,但是我们有一些间接的机制。我个人喜欢采用的方式是:加锁。我们在运行于不同的应用程序域的代码中对两个字符串变量进行加锁,如果程序运行的结果和对相同的对象加锁一样,那么就可以证明被加锁的两个对象实际上是同一个对象。

为了便于演示,我写一个如下一个AppDomainContext,表示某个AppDomain对应的执行上下文。AppDomainContext具有一个只读的类型为AppDomain的属性,该属性通过构造函数执行,最终在静态方法NewContext被创建。我们调用Invoke方法让指定的方法对应的应用程序域中执行。

   1: public class AppDomainContext
   2: {
   3:     public AppDomain AppDomain { get; private set; }
   4:     private AppDomainContext(AppDomain appDomain)
   5:     {
   6:         this.AppDomain = appDomain;
   7:     }
   8:     public static AppDomainContext NewContext(string friendlyName)
   9:     {
  10:         return new AppDomainContext(AppDomain.CreateDomain(friendlyName));
  11:     }
  12:  
  13:     public void Invoke<T>(Action<T> action) where T : MarshalByRefObject
  14:     {
  15:         T instance = (T)this.AppDomain.CreateInstanceAndUnwrap(typeof(T).Assembly.FullName, typeof(T).FullName);
  16:         action.Invoke(instance);
  17:     }
  18: }

我们接着在定义一个辅助类ObjectLock方便进行加锁,以及确认对象是否被所住。ObjectLock比如继承自MarshalByRefObject,因为我们需要该对象以MBR的方式进行传递。在Lock方法中对指定的对象进行加锁,并指定加锁的时间。在CheckLock中通过时间间隔判断指定的对象是否已经被锁住,相应的结果会在控制台中被输出。为了让大家能够确定相应的操作是在哪个应用程序域中执行的,在枷锁和检查锁定的时候将应用程序域的名称(AppDomain.FriendlyName属性)打印出来。

   1: public class ObjectLock : MarshalByRefObject
   2: {
   3:     public void Lock(object objectToLock, int millisecondsTimeout)
   4:     {
   5:         lock (objectToLock)
   6:         {
   7:             Console.WriteLine("[{0}] Successfully lock the object.", AppDomain.CurrentDomain.FriendlyName);
   8:             Thread.Sleep(millisecondsTimeout);
   9:         }
  10:     }
  11:     public void CheckLock(object objectToLock)
  12:     {
  13:         if (Monitor.TryEnter(objectToLock, 10))
  14:         {
  15:             Console.WriteLine("[{0}] The object is not  locked.", AppDomain.CurrentDomain.FriendlyName);
  16:         }
  17:         else
  18:         {
  19:             Console.WriteLine("[{0}] The object is locked .", AppDomain.CurrentDomain.FriendlyName);
  20:         }
  21:     }
  22: }

然后我再一个控制台应用中的Main方法中,编写了如下简单的代码。通过AppDomainContext在一个的应用程序域(Foo)中锁定一个值为“Hello World!”的字符串,并在另一个应用程序域(Bar)中确认同值得字符串是否已经被锁定。结果表示在应用程序域Bar中指定的字符串已经被锁定,从而证明了应用程序域Foo和Bar中两个值为“Hello World!”的字符串对象实际上是同一个。

   1: static void Main(string[] args)
   2: {
   3:     Action<ObjectLock> lockObj = objLock => objLock.Lock("Hello World!", 2000);
   4:     Action<ObjectLock> checkLock = objLock => objLock.CheckLock("Hello World!");
   5:  
   6:     Thread lockObjThread = new Thread(() => AppDomainContext.NewContext("Foo").Invoke<ObjectLock>(lockObj));
   7:     Thread checkLockThread = new Thread(() => AppDomainContext.NewContext("Bar").Invoke<ObjectLock>(checkLock));
   8:  
   9:     lockObjThread.Start();
  10:     Thread.Sleep(500);
  11:     checkLockThread.Start();           
  12: }

输出结果:

   1: 1: [Foo] Successfully lock the object.
   2: 2: [Bar] The object is locked.

上面的介绍同时说明一个问题:千万不要对一个字符串对象加锁

四、程序集加载的方式

imageimage

虽然我们说CLR在启动托管应用的时候,以中立域的方式加载msCorLib.dll这个程序集,但是这不是程序集默认采用的加载方式。在默认的情况下,程序集被加载到当前的程序域中,供该程序集独占使用。我个人将这两种不同的程序集加载方式称为:独占加载(Exclusive Loading )共享加载(Shared Loading)。如右图所示:如果某个类型被定义在程序集中Foo.Dll,当AppDomain1和AppDomain2需要使用该类型的时候,它们会分别以独占的方式加载程序集Foo.Dll。但是,如果它们使用一些基元类型,比如System.Object、System.Int32、System.DateTime等,则不会加载定义它们的msCorLib.dll程序集,而是直接使用已经被以中立域方式加载到共享程序域中的msCorLib.dll。

我们同样可以借助上面定义的AppDomainContext来证明这一点。在这之前我需要说明一点:程序集的加载包括对定义在程序集中类型系统的加载,我们可以通过类型对象的加锁情况来推断程序集的加载方式。为此我在上面创建的解决方案中添加了一个类库项目Lib,ConsoleApp引用Lib项目,并在Lib中定义了一个空的Foo类型。

   1: namespace Artech.MemAllocation
   2: {
   3:     public class Foo
   4:     {}
   5: }

然后我们修改之前的程序,将对字符串加锁替换在对Foo类型(typeof(Foo))加锁。从输出结果我们可以看出,在Bar程序域中使用的Foo类型并没有被锁住,从而证明两个程序域(Foo和Bar)使用的同一个类型并不是Type对象,因为对应的程序集是以独占的方式加载的。

   1: static void Main(string[] args)
   2: {
   3:     Action<ObjectLock> lockObj = objLock => objLock.Lock(typeof(Foo), 2000);
   4:     Action<ObjectLock> checkLock = objLock => objLock.CheckLock(typeof(Foo));
   5:  
   6:     Thread lockObjThread = new Thread(() => AppDomainContext.NewContext("Foo").Invoke<ObjectLock>(lockObj));
   7:     Thread checkLockThread = new Thread(() => AppDomainContext.NewContext("Bar").Invoke<ObjectLock>(checkLock));
   8:  
   9:     lockObjThread.Start();
  10:     Thread.Sleep(500);
  11:     checkLockThread.Start();
  12: }

输出结果:

   1: [Foo] Successfully lock the object.  
   2: [Bar] The object is not locked.

但是,如果我们将加锁和锁定检验的typeof(Foo)替换成typeof(int),结果就完全不一样了。不同的结果说明了msCorLib.dll采用了不同于上面的程序集加载方式,以中立域方法的加载方式决定在任何应用程序域中使用的类型都是同一个Type对象。

   1: static void Main(string[] args)
   2: {
   3:     Action<ObjectLock> lockObj = objLock => objLock.Lock(typeof(int), 2000);
   4:     Action<ObjectLock> checkLock = objLock => objLock.CheckLock(typeof(int));
   5:  
   6:     Thread lockObjThread = new Thread(() => AppDomainContext.NewContext("Foo").Invoke<ObjectLock>(lockObj));
   7:     Thread checkLockThread = new Thread(() => AppDomainContext.NewContext("Bar").Invoke<ObjectLock>(checkLock));
   8:  
   9:     lockObjThread.Start();
  10:     Thread.Sleep(500);
  11:     checkLockThread.Start();
  12: }

输出结果:

   1: [Foo] Successfully lock the object.
   2: [Bar] The object is locked.

五、我们自己的程序集也可以采用中立域的方式加载吗?

我想到这里有人会问一个问题:“我们自定义的程序集可以像msCorLib.dll一样以中立域的方式共享加载吗?”。对于控制台应用,你只需要在Main方法上应用LoaderOptimizationAttribute特性,并指定LoaderOptimization为MultiDomain即可。比如,还是采用对Foo类型Foo类型(typeof(Foo))对象加锁,这次我们在Main方法上应用了这样的特性:[LoaderOptimization(LoaderOptimization.MultiDomain)]。输出的结果就与对Int32类型对象加锁一样。

   1: [LoaderOptimization(LoaderOptimization.MultiDomain)]
   2: static void Main(string[] args)
   3: {
   4:     Action<ObjectLock> lockObj = objLock => objLock.Lock(typeof(Foo), 2000);
   5:     Action<ObjectLock> checkLock = objLock => objLock.CheckLock(typeof(Foo));
   6:  
   7:     Thread lockObjThread = new Thread(() => AppDomainContext.NewContext("Foo").Invoke<ObjectLock>(lockObj));
   8:     Thread checkLockThread = new Thread(() => AppDomainContext.NewContext("Bar").Invoke<ObjectLock>(checkLock));
   9:  
  10:     lockObjThread.Start();
  11:     Thread.Sleep(500);
  12:     checkLockThread.Start();
  13: }

输出结果:

   1: [Foo] Successfully lock the object.
   2: [Bar] The object is locked.

又一个关于加锁的注意:谨慎地对Type对象进行加锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436806.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql数据库连接地址utf8_在Python中连接到MySQL数据库时UTF8不工作

我正在努力使Python更好地使用UTF-8编码的MySQL数据库&#xff0c;例如&#xff0c;挪威字符。我找了好几个小时&#xff0c;但没能找到像预期的那样有效的东西。以下是从数据库中提取的示例表&#xff1a;mysql> select * from my_table;---------------------| id | shop_…

【转】.NET Remoting

.Net Remoting提供了一种允许一个应用域中的对象与另一个应用域中的对象进行交互的框架。是.NET框架中的一个重要技术改进,它用于减轻运行应用程序的系统开销. 中文名 .Net Remoting 作 用 减轻运行应用程序的系统开销 目录 1 介绍2 .NET Remoting的原理 ▪ 1.NET Rem…

python多重赋值技巧_python教程12课:多元赋值、多重赋值、运算符以及判断字符串类型...

# 多元赋值&#xff1a;# x,y,z 和 1,2,‘String是两个元组&#xff0c;只不过元组的 () 被省略掉了x, y ,z 1, 2, Stringprint(x, y, z)(x, y ,z) (3, 4, String)print(x,y,z)# 一般用在交换变量值#交换变量值常规思路x 10,y 20z 30x ,y, z y,z,xprint(x,y,z)#使用多元赋值…

【转】关于CLR内存管理一些深层次的讨论[下篇]

《上篇》中我们主要讨论的是程序集&#xff08;Assembly&#xff09;和应用程序域&#xff08;AppDomain&#xff09;的话题&#xff0c;着重介绍了两个不同的程序集加载方式——独占方式和共享方式&#xff08;中立域方式&#xff09;&#xff1b;以及基于进程范围内的字符串驻…

python正则表达式处理txt_Python文本处理服务(re正则表达式例子)

正则表达式例子检查对子在此示例中&#xff0c;我们将使用以下帮助函数来更优雅地显示匹配对象&#xff1a;def displaymatch(match): if match is None: return None return % (match.group(), match.groups())假设你在写一个扑克程序&#xff0c;一个玩家的一手…

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

理解堆与栈 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆&#xff0c;值类型与引用类型 深入浅出图…

bi 存储过程方案_BI 系统中容易被忽视的数据源功能

BI 系统中容易被忽视的数据源功能用户在选购 BI 解决方案的时候&#xff0c;常常会更关注界面环节的功能指标&#xff0c;比如美观性、操作的流畅性、移动端支持等等。毕竟&#xff0c;BI 是要给业务人员使用的&#xff0c;这些看得见的内容一般不容易被遗漏。然而&#xff0c;…

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理

栈基本工作原理 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆&#xff0c;值类型与引用类型 深入浅…

matlab将二值图像与原图重叠_图像处理matlab及图像融合图像镶嵌图像拼接

要把double的图像(范围是0到1)再次转化为256灰度值的&#xff0c;可以这样Igrey uint8(I2*255)图像类型转换函数&#xff1a;dither() 通过颜色抖动&#xff0c;把真彩图像转换成索引图像或灰度图象转换成二值图像gray2ind() 将灰度图像(或二值图像)转换成索引图像grayslice() …

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆,值类型与引用类型

前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC)&#xff0c;但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外&#xff0c;了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。 简介 本文将介绍值类型与引用类…

mysql udf sm4_SM4国密算法Java版

根据 国密SM4 文档 编写的一个Java 加密解密样例package javasm4;/**** author Jeen*/public class JavaSM4 {public static int[] key new int[4];//密钥public static int[] temp new int[4];//中间量 存储运算结果public static int[] rkey new int[32];//轮密钥public s…

【转】.net框架读书笔记---CLR内存管理\垃圾收集(一)

一、垃圾收集平台基本原理解析 在C#中程序访问一个资源需要以下步骤&#xff1a; 调用中间语言&#xff08;IL&#xff09;中的newobj指令&#xff0c;为表示某个特定资源的类型实例分配一定的内存空间。初始化上一步所得的内存&#xff0c;设置资源的初始状态&#xff0c;从而…

gperftools mysql_利用 gperftools 对nginx mysql 内存管理 性能优化

利用 gperftools 对nginx 与 mysql 进行 内存管理 性能优化 降低负载.Gperftools 是由谷歌开发。官方对gperftools 的介绍为&#xff1a;These tools are for use by developers so that they can create more robust applications. Especially of use to those developing m…

【转】.net框架读书笔记---CLR内存管理\垃圾收集(二)

前几天学习了CLR垃圾收集原理和基本算法&#xff0c;但是那些是仅仅相对于托管堆而言的&#xff0c;任何非托管资源的类型&#xff0c;例如文件、网络资源等&#xff0c;都必须支持一种称为终止化&#xff08;finalization&#xff09;的操作。 终止化 终止化操作允许一种资源…

python time 时间戳_Python的time.time()返回本地或UTC时间戳吗?

这是可以在文本文件中使用的时间戳记的文本形式。(问题的标题在过去是不同的&#xff0c;因此对此答案的介绍进行了更改&#xff0c;以阐明如何将其解释为时间。[2016年1月14日更新])您可以使用.now()或.utcnow()来将时间戳记作为字符串获取datetime.datetime&#xff1a;>&…

【转】.net框架读书笔记---CLR内存管理\垃圾收集(三)

接上一篇.net框架读书笔记---CLR内存管理\垃圾收集&#xff08;二&#xff09;&#xff0c;主要学习了终止化对象&#xff08;实现了Finalize方法的对象&#xff09;&#xff0c;了解了终止化对象的弊端&#xff0c;学习了通过实现IDisposable接口&#xff0c;通过Dispose方法来…

mysql实验三单表和多表查询_数据库实验三(单表查询)

实验三:select sno,snamefrom student;//(1)查询全体学生的学号和姓名select *from student;//(2)查询全体学生的详细记录select sname,sage,sdeptfrom student where sdeptMA;//(3)查询软件学院的学生姓名、年龄、系别select distinct snofrom sc;//(4)查询所有选修过课程的学…

【转】.net框架读书笔记---CLR内存管理\垃圾收集(四)

弱引用 当一个根指向一个对象时&#xff0c;该对象不可能被垃圾收集器收集&#xff0c;在这种情况下&#xff0c;通常说存在一个该对象的强引用&#xff08;strong reference&#xff09;。垃圾收集器还支持弱引用&#xff08;weak reference&#xff09;的概念。弱引用允许垃圾…

1756冗余_AB冗余模块1756-RM

AB冗余模块1756-RM100-C30UKJ01100-C30UKJ10100-C30UKL00100-C30UKL10AB冗余模块1756-RM100-C30UKP00100-C30UL00AB冗余模块1756-RM100-C30UL10100-C30UN00AB冗余模块1756-RM100-C30UN10100-C30UP001756-RM ControLogix冗余模块140U-H-RM12B 140U塑壳断路器外部附件1756-RMC1 C…

【转】.net框架读书笔记---CLR内存管理\垃圾收集(五)

对象复苏 当一个终止化对象被认为死亡时&#xff0c;垃圾收集器可以强制使该对象获得重生&#xff08;进入终止化可达队列&#xff09;&#xff0c;因为这样才能调用对象的Finalize方法。在Finalize方法被调用之后&#xff0c;它才算真正的死亡了&#xff0c;一个终止化对象会经…