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

一、垃圾收集平台基本原理解析

  在C#中程序访问一个资源需要以下步骤:

  • 调用中间语言(IL)中的newobj指令,为表示某个特定资源的类型实例分配一定的内存空间。
  • 初始化上一步所得的内存,设置资源的初始状态,从而使其可以为程序所用。一个类型的实例构造器负责这样的初始化工作。
  • 通过访问类型成员来使用资源。
  • 销毁资源状态,执行清理工作。
  • 释放托管堆上面的内存,该步骤由垃圾收集器全权负责,值类型实例所占的内存位于当前运行线程的堆栈上,垃圾收集器并不负责这些资源的回收,当值类型实例变量所在的方法执行结束时,他们的内存将随着堆栈空间的消亡而自动消亡,无所谓回收。对于一些表示非托管的类型,在其对象被销毁时,就必须执行一些清理代码。

  当应用程序完成初始化后,CLR将保留(reserve)一块连续的地址空间,这段空间最初并不对应任何的物理内存(backing storage)(该地址是一段虚拟地址空间,所以要真正使用它,它必须为其“提交”物理内存),该地址空间即为托管堆。托管堆上维护着一个指针,暂且称之为NextObjPtr。该指针标识着下一个新建对象分配时在托管堆中所处的位置。刚开始的时候,NextObjPtr被设为CLR保留地址空间的基地址。

  中间语言指令newObj负责创建新的对象。在代码运行时,newobj指令将导致CLR执行以下操作:

  • 计算类型的所有实例字段(以及其基类型所有的字段)所需要的字节总数。
  • 在前面所的字节总数的基础上面再加上对象额为的附加成员所需的字节数:一个方法指针和一个SyncBlockIndex。
  • CLR检查保留区域中的空间是否满足分配新对象所需的字节数-----如果需要则提交物理内存。如果满足,对象将被分配在NextObjPtr指针所指的地方。接着类型的实例构造器被调用(NextObjPtr会被传递给this参数),IL指令newobj(或者说new操作符)返回其分配内存地址。就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区域,并指示出下一个新建对象在托管堆中的地址。

下图演示了包含A,B,C三个对象的托管堆,如果再分配对象将会被放在NextObjPtr指针所演示的位置(紧跟C之后)

2010050916425858.jpg

  在C语言中堆分配内存时,首先需要遍历一个链表数据结构,一旦找到一个足够大的内存块,该内存块就会被拆开来,同时链表相应节点上的指针会得到适当的调整。但是对于托管堆来说,分配内存仅仅意味着在指针上增加一个数值---显然要比操作链表的做法快许多,C语言都是在找到自由空间为其对象分配内存,因此连续创建几个对象,他们将很有可能被分散在地址空间的各个角落。但是在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。

  就目前来看托管堆在实现的简单性和速度方面要远优于C语言的运行时中的堆。之所以这样是因为CLR做了大胆的假设---那就是应用程序的地址空间和存储空间是无限的,显然这是不可能的。托管堆必须应用某种机制来允许这种假设。这种机制就是垃圾回收器。

  当应用程序调用new创建对象时,托管堆可能没有足够的地址空间来分配该对象。托管堆通过将对象所需要的字节总数添加到NextObjPtr指针表示的地址上来检测这种情况。如果得到的结果超出了托管堆的地址空间范围,那么托管堆将被认为已满,这时就需要垃圾收集器。,其实这种描述是过于简单的,垃圾回收与对象的代龄有着密切的关系,还需继续学习垃圾收集。

二、垃圾收集算法

  垃圾收集器通过检查托管堆中是否有应用程序不再使用的对象来回收内存。如果有这样的对象,它们的内存将被回收。那么垃圾收集器是这样知道应用程序是否正在使用一个对象呢??还得继续学习。

  每一个应用程序都有一组根(root),一个根是一个存储位置,其中包含着一个指向引用类型的内存指针。该指针或者指向一个托管堆中的对象,或者被设置为null。例如所有的全局引用类型变量或静态引用类型都被认为是根。另外,一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个根。最后,在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。

  当垃圾收集器开始执行时,它首先假设托管堆中的所有对象都是可收集的垃圾。换句话,垃圾收集器假设应用程序中没有一个根引用着托管堆中的对象。然后垃圾收集器便利所有的根,构造出一个包含所有可达对象的图。例如,垃圾收集器可能会定位出一个引用托管对象的全局变量。下图展示了分配有几个对象的托管堆,其中对象A,C,D,F为应用程序的根所直接引用。所有这些对象都是可达对象图的一部分。当对象D被添加到该图中时,垃圾收集器注意到它还引用着对象H,于是对象H被添加到该图,垃圾回收器就这样子以递归的方式来遍历应用程序中所有的可达对象。

2010050917454647.jpg

  一旦该部分的可达对象完成后,垃圾回收器将检查下一个根,并遍历其引用的对象。当垃圾回收器在对象之间进行遍历时,如果发现某对象已经添加到可达对象图中时(比如上图中的H,在检查D的时候已经将其添加到了可达对象图),它会停止沿着该对象标识的路径方向上遍历的活动。两个目的:

  • 可以避免垃圾收集器对一些对象的多次遍历,可高性能。
  • 如果两个对象之间出现了循环引用,可以避免遍历陷入无限循环(比如上图中D引用着H,而H又引用着D)。

  垃圾收集器一旦检查完所有的根,其得到的可达对象将包含所有从应用程序的根可以访问的对象。任何不在该图中的对象将是应用程序不可访问的对象,不可达的对象,因此也是可以被执行垃圾收集器的对象。垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区域。

  PS:CLR的垃圾收集机制对我来说有点非主流,在此之前,我一直认为是垃圾收集器直接去寻找不可达的对象,现在看来垃圾收集器使用了逆向思维,通过找到可达对象来找到不可达的对象(这个原因还得继续思考)。

  如果找到了较大的连续区域,垃圾收集器将会把内存中的一些非垃圾对象搬移到这些连续区块中以压缩堆栈,显然搬移内存中的对象将使所有这些指向对象的指针变的无效。所以垃圾收集器必须修改应用程序的根以使它们指向这些对象更新后的位置。另外,如果任何对象包含有指向这些对象的指针,那么垃圾收集器也会负责矫正它们。托管堆被压缩以后,NextObjPtr指针将被设为指向最后一个非垃圾对象之后。下图展示了对于上面图执行垃圾收集器后的托管堆。

2010050918093015.jpg

  可见垃圾回收器对于应用程序的性能有不小的影响,CLR采用了代龄等措施来优化了性能(以后学习)。

  因为任何不从应用程序的根中访问的对象都会在某个时刻被收集,所以应用程序将不可能发生内存泄漏,另外应用程序也不可能再访问已经被释放的对象。因为如果对象可达,它将不可能被释放;而如果对象不可达,应用程序必将无法访问到它。

  下面代码演示了垃圾收集器是这样分配管理对象的:

class Program
{
static void Main( string [] args)
{
// 在托管堆上ArrayList对象,a现在就是一个根
ArrayList a = new ArrayList();

// 在托管堆上创建10000个对象
for ( int i = 0 ; i < 10000 ; i ++ )
{
a.Add( new Object()); // 对象被创建在托管堆上
}
// 现在a是一个根(位于线程堆栈上)。所以a是一个可达对象
// ,a引用的10000个对象也是可达对象
Console.WriteLine(a.Count);

// 在a.Count返回后,a便不再被Main中的代码所引用,
// 因此也就不再是一个根。如果另外一个线程在a.Count的结果被
// 传递给WirteLine之前启动了垃圾收集,那么a以及它所引用的10000个对象将会被回收。
// 上面for里面的变量i虽然在后面的代码中不再被引用,但由于它是一个值类型,并不存在于
// 托管堆中,所以它不受垃圾收集器的管理,它在Main方法执行完毕后会随着堆栈的消失而自动
// 被系统回收
Console.WriteLine( " End of method " );
}
}

 

  CLR之所以能够使用垃圾回收机制,有一个原因是因为托管堆总是能知道一个对象的实际类型,从而使用其元数据信息来判断一个对象的那些成员引用着其他对象。

 

------------------------------------------------------------------------------------------------------------

关于根的问题,好多朋友都讨论了,其实书上已有更详细的东西呢,只是那些000的东西我比较反感,非常抱歉,现在我贴出来,一起再学习一下:

  当JIT编译器编译一个方法的IL代码时,除了产生本地CPU代码外,JIT编译器还会创建一个内部逻辑表。从逻辑上来看,该表中的每一个条目都标识着一个方法的本地CPU指令的字节偏移范围,以及该范围中一组包含根的内存地址(或者CPU寄存器),下表描述了该内存表:

起始字节偏移                                              结尾字节偏移                               根                                                              
0x000000000x00000020this,arg1,arg2,ECX,EDX
0x000000210x00000122this,arg2,fs,EBX
0x000001230x00000145fs

  如果在0x00000021和0x00000122之间的代码执行时开始执行垃圾收集,那么垃圾收集器将知道参数this参数arg2,本地变量fs以及寄存器EBX都是根,他们引用的托管堆中的对象将不会被认为是可收集的垃圾对象。除此之外,垃圾收集器还可以遍历线程的调用堆栈,通过检测其中每一个方法内部表来确定所有调用方法中的根,最后,垃圾收集器使用其他一些手段获得存储在全局引用类型变量和静态引用类型变量中保持的根。

  在上表中方法的arg1参数在偏移为0x00000020处的指令执行完毕后就不再被引用了,这意味着arg1引用的对象在该指令执行后的任何时刻都可以被垃圾收集器收集(假设应用程序中没有其他的根再引用该对象),换句话说,只要一个对象不再可达,它就是垃圾收集器的候选对象,CLR并不保证对象在一个方法的整个生存期内都一直存活。

  另外请大家关注11楼的答复。

一本书不要指望一次就能看懂啊

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

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

相关文章

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

编程控制垃圾收集器 System.GC类型为应用程序提供了直接控制垃圾收集器的一些方法&#xff0c;可以通过GC.MaxGeneration来查询托管堆支持的最大代龄&#xff0c;目前为2。 通过下面方法执行垃圾收集器 GC.Collect(int);传递代龄&#xff0c;传递0&#xff0c;收集0代&#xff…

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 1

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

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 2

前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC)&#xff0c;但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外&#xff0c;了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。 简介 继续上篇未完成的“参数传…

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复

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

linux virt java_Linux下Java环境安装

本节主要讲解Linux(Centos 6.5)下Java环境的安装1. 卸载机器上默认安装的JDK在Linux环境下一般会默认安装jdk&#xff0c;为了自己项目的开发部署&#xff0c;一般情况要重新装jdk&#xff0c;而且自己装的Jdk相对来说易控制版本&#xff0c;稳定性更高。所以以下是我卸载预装J…

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能****

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

【转】分布式事务的常见解决方案

一、事务起步 1. 什么是事务 事务这种东西大家都耳熟能详了&#xff0c;通常指由一组操作组成的一个工作单元&#xff0c;这一整个组合要么全部成功&#xff0c;要么全部失败。 2. 本地事务 在计算机系统中&#xff0c;更多的是通过关系型数据库来控制事务&#xff0c;这是…

java s.charat_Java中s.charAt(index)用于提取字符串s中的特定字符操作

charAt(int index)方法是一个能够用来检索特定索引下的字符的String实例的方法.charAt()方法返回指定索引位置的char值。索引范围为0~length()-1.如: str.charAt(0)检索str中的第一个字符,str.charAt(str.length()-1)检索最后一个字符.警告&#xff1a;在字符串s中越界访问字符…

【转】.NET框架简介

.NET 框架是由微软开发的软件开发平台&#xff0c;其最主要的两个组成部分是公共语言运行时 (CLR) 和框架类库 (FCL)&#xff0c;基础类库 (BCL)是框架类库的一个子集。 .NET 框架简介 下图展示了 .NET 框架的主要结构。 其中&#xff0c;最下层的无疑就是操作系统了。 在 …

java比赛题目_【蓝桥杯2016第七届比赛题目】JAVA A组

1 煤球数目有一堆煤球&#xff0c;堆成三角棱锥形。具体&#xff1a;第一层放1个&#xff0c;第二层3个(排列成三角形)&#xff0c;第三层6个(排列成三角形)&#xff0c;第四层10个(排列成三角形)&#xff0c;....如果一共有100层&#xff0c;共有多少个煤球&#xff1f;请填表…

【转】C#技术漫谈之垃圾回收机制(GC)

摘要&#xff1a;今天我们漫谈C#中的垃圾回收机制&#xff0c;本文将从垃圾回收机制的原理讲起&#xff0c;希望对大家有所帮助。 GC的前世与今生 虽然本文是以.NET作为目标来讲述GC&#xff0c;但是GC的概念并非才诞生不久。早在1958年&#xff0c;由鼎鼎大名的图林奖得主John…

【转】git hub 使用小结

【转自&#xff1a;https://blog.csdn.net/yj310873325/article/details/79255134】 1.创建账号: https://github.com/ 2.下载客户端&#xff1a;https://git-scm.com/download 这是命令行模式&#xff0c;用着比较舒服&#xff0c;不是github的客户端&#xff0c;一路默认安…

【转】细说.NET 中的多线程 (一 概念)

为什么使用多线程 1.使用户界面能够及时响应用户的输入 当某个应用程序在进行大量运算时候&#xff0c;为了保证应用程序能够随时响应客户的输入&#xff0c;这个时候我们往往需要让大量运算和响应用户输入这两个行为在不同的线程中进行。 2.效率原因 应用程序经常需要等待一…

【转】细说.NET中的多线程 (二 线程池)

上一章我们了解到&#xff0c;由于线程的创建&#xff0c;销毁都是需要耗费大量资源和时间的&#xff0c;开发者应该非常节约的使用线程资源。最好的办法是使用线程池&#xff0c;线程池能够避免当前进程中大量的线程导致操作系统不停的进行线程切换&#xff0c;当线程数量到达…

java第二章_零基础学Java第二章

一、第一个代码案例1.1. HelloWorld案例1.1.1 代码执行流程我们写的代码都将以.java开头的文件保存&#xff0c;经过类编译器编译成.class的字节码文件&#xff0c;然后通过解释器翻译与机器交流1.1.1 代码执行流程1. 编写代码步骤首先定义一个类&#xff1a;public class 类名…

java volidate_volidate 学习

一&#xff1a;Volatile 变量具有synchronized的可见性&#xff0c;有序性 特性&#xff0c;但是不具备原子特性二&#xff1a;java memory model(jmm) java 内存模型形象理解见下图Java Memory Modela&#xff1a;java 线程读取共享内存变量流程&#xff1a;线程2 --> JMM …

【转】ABP源码分析一:整体项目结构及目录

ABP是一套非常优秀的web应用程序架构&#xff0c;适合用来搭建集中式架构的web应用程序。 整个Abp的Infrastructure是以Abp这个package为核心模块(core)15个模块(module).其中13个依赖于Abp这个核心包。另外两个包&#xff08;FluentMigration,Web.Resources&#xff09;相对独…

【转】ABP源码分析二:ABP中配置的注册和初始化

一般来说&#xff0c;ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法。执行这个方法前HttpApplication 实例必须存在&#xff0c;也就是说其构造函数必然已完成了执行。 ABP开始的地方就是HttpApplication的构造函数。 如下图一&#xff0c;Abp定义了一…

【转】ABP源码分析三:ABP Module

Abp是基于模块化设计思想进行构建的。开发人员可以将自定义的功能以模块&#xff08;module&#xff09;的形式集成到ABP中。具体的功能都可以设计成一个单独的Module。Abp底层框架提供便捷的方法集成每个Module.下图是所有Abp自带的module.AbpModule是所有Module的基类&#x…

java weka 聚类_简单开源数据挖掘工具weka进行文本聚类

目前非代码的数据挖掘工具很多&#xff0c;但非开源&#xff0c;weka是一款开源软件。只要安装jdk环境就可使用(具体安装jdk可以百度)本文将论述如何不用代码&#xff0c;使用weka操作&#xff0c;通过与文档频数与单词权的特征选择方法进行文本聚类(数据为附件)第一步&#xf…