应用程序集和CLR 垃圾回收(二)

本篇是整理蒋金楠对CLR 内存管理的博文,蒋大神的博文中将CLR 的内存分管理分为三个逻辑部分,博文中详细讲述了哪些程序集要加载到系统程序域,哪些要加载到共享程序域,以及我们写的代码会被加载到默认程序域。下面是我整理后的思路,目的是加强一下对CLR 内存管理的概念。

程序集与应用程序域

程序集是一个托管应用的基本的部署单元。一个程序集是子描述的(通过元数据),能够实施版本策略和部署策略。从结构组成来看,一个程序集主要由三个部署组成:IL指令、元数据和资源。

应用程序域从功能上看是,通过应用程序域实现的隔离机制为托管代码的执行提供了一个安全的边界。

从与程序集的关系来讲,我们可以将应用程序域看成是加载程序集的容器。只有相关的程序集被CLR加载到相应的应用程序域中,才谈得上代码的执行。

应用程序域的隔离,归根结底是内存的隔离。一个应用程序域中创建的对象,不能在另一个应用程序域中使用。这中间需要有一个跨应用程序域传递的机制,我们称之为”封送“。具体来讲分为两种:

  • 按值封送(MBV),主要采用序列化的方式
  • 按引用封送(MBR),典型方式是 .Net Remoting

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

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

  • 系统程序域:第一个被创建,是其他两个应用程序域的创建者。在该程序域初始化过程中,由它将msCorLib.dll 这个程序集加载到共享程序域中,驻留字符串也被保存在系统程序域中。系统程序域的一个主要任务就是追踪其他所有应用程序域的状态
  • 共享程序域:保存以”中立域“加载的程序集容器。中立域方式加载的程序集可以被其他程序域使用。
  • 默认程序域:我们的托管程序最终就运行在该程序域上。通过AppDomain表示。

字符串的驻留

字符串的驻留是基于整个进程的,而不是仅仅基于某个应用程序域。字符串对象直接被保存到系统程序域中。从某种意义上讲,在字符串驻留机制下,字符串也是以“中立域”的方式被加载的,被驻留的字符串能够被同一个进程下所有应用程序域所共享。

程序集的加载方式

CLR 在启动托管应用的时候,以中立域的方式加载msCorLib.dll这个程序集,但是这不是程序集默认采用的加载方式。默认情况下,程序集被加载到当前的应用程序域中,该程序集独占使用。

可以看下面的例子,自己定义的Foo类不能被其他程序域访问,但是换成int 类型就不同了。

自己的程序采用中立域的方式加载

对于控制台应用,你只需要在Main方法上应用LoaderOptimizationAttribute 特性,并指定LoaderOptimization 为MultiDomain即可。

类型和实例:

对类型和实例的内存分配时如何进行的呢?对象是状态和行为的组合体,所以从.net framework 的角度来看类型,它只是具有两种类型的成员—— 字段和方法(实际还有嵌套类型),前者表示状态,后者表示行为。

类型是对元数据的描述,而实例则是符合该元数据描述的单个个体。

同一类型下的所有实例具有相同的行为,它们通过状态值的不同得以区分。

所以内存中的实例(这里所说的实例是引用类型的实例)表示的是字段值,而内存中的类型表示的则是类型成员结构的元数据。

当我们创建一个对象的时候,CLR会在GC堆(heap)中开辟一块连续的内存空间保存字段值。那么类型信息又是保存在哪块内存上的呢?

类型信息保存在“另一堆“上,我们称之为加载器堆(loader heap)。每一个应用程序域都具有各自的加载器堆,即包括我们创建的普通应用程序域,也包括三个特殊应用程序域:系统程序域、共享程序域或默认程序域。

如果说GC堆是实例的容器,那么基于应用程序域的加载器堆就是类型的容器。CLR采用”按需加载(这里指的是类型,不是程序集)、及时编译“的运行机制。

当某个类型被第一次使用的时候,CLR视图加载该类型。

如果该类型对应的程序没有独自地加载到本应用程序域中,或者没有通过中立域的形式加载到共享程序域中,它会按照相应的方式加载程序集(假设会按独占的方式加载)。然后,将使用到的这个类型加载到本应用程序域的加载器中。

加载器堆维护着自应用程序域创建以来使用过的所有类型记录,它们对应着一个特殊的对象——方法表(method table)。当程序第一次执行到某个方法的时候,CLR会定位到方法表中该条目,获取相关信息进行JIT编译。所以如果某个类型在加载器堆中的方法表的某个条目至少被执行一次,它就会指向一段JIT 编译后的机器指令。

实例内存分配不仅限于GC堆

CLR的GC堆用于盛放实例,加载器堆用于盛放类型。但CLR还不止这两个堆,它还有两个堆,一个是存放JIT编译后机器指令的JIT堆(JITheap),另一个则是专门用于”大对象“的大对象堆(LOH:large object Heap)。

当我们实例化一个对象的时候,如果该对象大于或者等于85000字节,CLR认为他是大对象,并被放到LOH中,否则放到GC堆中。这里有一点要注意,GC不仅限于堆GC堆中对象的回收,LOH中的对象也受GC管理。

实例对类型的引用

实例是类型的实例,实例和它所对应的类型需要维持一种联系。反应在内存中,就是GC堆和LOH中的对象有一个引用了 加载器堆中该类型的方法表。

实例对类型的引用通过一个特殊的对象来维系——TypeHandle。

举个例子,在如下一段简单对象实例化代码中,我先后实例化了四个对象:字符串”ABC“,System.Object 对象,自定义Bar对象和具有85000个元素的字节数组。

string strInstance         = "ABC";object objectInstance      = new object();Bar barInstance            = new Bar()byte[] largeObjInstance    = new byte[85000];

上面的程序执行后,围绕着实例化的四个对象和类型信息,在内存中将会具有如下一个关系。

LOH中的对象如何被回收

CLR采用基于”代龄(Generation)“的垃圾回收机制。代龄,这个词充分体现了设计者用于表现”不同对象具有不同生命周期“的意思。所有对象分为三代,代表三个不同的连续内存块,”辈分“越高,时间越久;”辈分“越低,被扫荡(GC回收)的频率越高。

对于LOH和GC堆中的对象,除了大小还有其他的什么不同之处吗?

将大对象放在LOH中,目的在于对其实施特殊的回收机制。关于垃圾回收,应该有这样的认识:回收的成本是和对象的大小基本成”正向“关系,对象越大,回收成本就越大。所以我们不能对大对象频繁的实施垃圾回收,实际上CLR是将LOH对象当成最高代龄的对象。

也就是说,针对LOH的回收工作是和GC堆中G2一并进行的。换句话说,当G2或者LOH的剩余空间低于某个限度,针对它们的垃圾回收便被触发。

原文地址:https://www.cnblogs.com/mingjie-c/p/11699934.html

时间: 2024-10-13 11:57:31

应用程序集和CLR 垃圾回收(二)的相关文章

CLR垃圾回收的设计

CLR垃圾回收的设计 作者: Maoni Stephens (@maoni0) - 2015 附: 关于垃圾回收的信息,可以参照本文末尾资源章节里引用的垃圾回收手册一书. 组件架构 GC包含的两个组件分别是内存分配器和垃圾收集器.内存分配器负责获取更多的内存并在适当的时候触发垃圾收集.垃圾收集器回收程序中不再使用的对象的内存. 有多种方法调用垃圾回收器,例如人工调用GC.Collect或者当终结线程在接收到表示低内存的异步通知时(调用). 内存分配器的设计 内存分配器由执行引擎(EE)的内存分配

CLR 垃圾回收算法

c#相较于c,c++而言,在内存管理上为程序员提供了极大的方便,解放了程序员与内存地址打交道,提高了程序员的工作效率.比如c中分配的malloc堆空间没有释放导致的内存泄露,数组越界导致的踩内存错误,使用了已释放的内存空间错误等等.这些在C#中统统的都不存在,主要是由于clr提供的安全检查机制以及垃圾回收机制.本篇文章主要来介绍常用的垃圾回收算法以及CLR中使用的垃圾回收算法. 在通常的情况下当分配对象时发现内存堆空间不足时,此时GC会执行垃圾回收算法.默认情况下,进程启动,会被分配相应的堆空间

Java垃圾回收(二) 堆内存的分代回收

堆内存的分代回收 Java针对堆的垃圾回收,将堆分为了三个较小的部分:新生代.老年代.持久代.新生代主要使用复制和标记-清除垃圾回收算法,年老代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器. 1. 分代回收的依据: 对象生存时间长短:大部分对象在Young期间就被回收. 不同代采用不同的垃圾回收策略:对存活时间不同的对象分类,用不同的垃圾回收算法进行高效的有针对回收. 2. 堆内存的分代: Young代: 回收机制:因为对象数量少,所以采用复

python垃圾回收二

由于循环引用的存在,我们在删除了a跟b之后,引用计数是1,这样,现有的垃圾回收机制是永远不可能把她们删除了.他们将永远存在于内存中. 我们当然不能对这种情况置之不理,于是,我们又添加了两种新的回收机制:标记清理,分代回收.这两种机制组合起来的效果笼统的说,我们就是设定了一个周期性任务,周期一到,任务启动,该任务是:对所有的可变对象都分析一遍,找出像ab这样的已经不用的,但是普通的垃圾回收机制又回收不了的垃圾,并清理掉,因为不可变对象不存在循环引用,所以该任务不需要对他们分析,只需要分析可变对象即

Java GC 垃圾回收(二)之 判断那些可回收,怎么回收

1.哪些对象可回收? 可行性分析算法 通过一系列GC Roots(?1)作为起始点,其到对象之间的引用(?2)称为引用链,当对象到GC Roots之间不存在引用链相连, 则此对象是不可用的.如下,Object5,Object6,Object7被判定为可回收对象. 2.怎么回收? 3.方法区回收: a.废弃常量: 没有任何String对象引用常量池的常量,也没有其他地方引用这个常量. b.无用的类: 下列条件全部满足: 1.该类所有的实例都已经被回收. 2.加载改类的ClassLoader已经被回

.NET垃圾回收机制(二)

一.GC的必要性 1.应用程序对资源操作,通常简单分为以下几个步骤:为对应的资源分配内存 → 初始化内存 → 使用资源 → 清理资源 → 释放内存. 2.应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: [1] 手动管理:C,C++ [2] 计数管理:COM [3] 自动管理:.NET,Java,PHP,GO- 3.但是,手动管理和计数管理的复杂性很容易产生以下典型问题: [1] 程序员忘记去释放内存 [2] 应用程序访问已经释放的内存 产生的后果很严重,常见的如内存泄露.数据内容乱

.Net程序的内存管理和垃圾回收机制

.NET 内存管理和垃圾回收 C/C++ 程序需要开发者手动分配和释放内存,.Net程序则使用垃圾回收技术自动收集不再使用的内存.垃圾回收器(GC)使用引用 跟踪占用内存的对象,如果对象被设置为null或已不在使用范围,GC就会标志该对象为可回收,这样GC就可以回收被这些对象占用的内存. 垃圾回收器(GC)使用Win32? VirtualAlloc() 接口为自己的堆分配内存,.Net托管堆是一个巨大连续的虚拟内存.GC先预留虚拟内存,当托管堆增长时则提交内存.GC跟踪托管堆末尾可用的地址并把下

垃圾回收机制GC知识再总结兼谈如何用好GC(其他信息: 内存不足)

来源 一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: 1.手动管理:C,C++ 2.计数管理:COM 3.自动管理:.NET,Java,PHP,GO- 但是,手动管理和计数管理的复杂性很容易产生以下典型问题: 1.程序员忘记去释放内存 2.应用程序访问已经释放的内存 产生的后果很严重,常见的如内存泄露.数据内容乱码,而且大部分时候,

垃圾回收机制GC知识再总结兼谈如何用好GC

一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: 1.手动管理:C,C++ 2.计数管理:COM 3.自动管理:.NET,Java,PHP,GO… 但是,手动管理和计数管理的复杂性很容易产生以下典型问题: 1.程序员忘记去释放内存 2.应用程序访问已经释放的内存 产生的后果很严重,常见的如内存泄露.数据内容乱码,而且大部分时候,程序的