热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

.NetDiscovery系列文章阅读索引--带你探索未知的.Net世界

.NetDiscovery系列文章是讲述.Net平台机制的文章,目前已有12篇,分别讲述了.Net垃圾收集、实时编译、字符串等部件的机制,现在推出1周年之际总结文章阅读索引,希望对大家有所帮助。

  .Net Discovery系列文章是讲述.Net平台机制的文章,目前已有12篇,分别讲述了.Net垃圾收集、实时编译、字符串等部件的机制,现在推出1周年之际总结文章阅读索引,希望对大家有所帮助。

image 
未标题-2

  string--.Net平台永恒的话题。这是一种比较特殊的数据类型,它是基元类型,也是引用类型,在编译以及运行时,.Net平台都对它做了一些优化工作,正是这些优化工作有时会迷惑编程人员,使string看起来难以琢磨,这篇文章分上下两章,共四节,来讲讲关于string的陌生一面。

  重点回顾: 
  在C#中,如果用new关键字实例化一个类,对应是由IL指令newobj来完成的;而创建一个字符串,则由ldstr指令完成,看到ldstr指令,我们即可认为,IL希望创建一个新的字符串 。
  从某些方面讲,正是字符串的恒定性,才造就了字符串的驻留机制,也为字符串的线程同步工作大开方便之门(同一个字符串对象可以在不同的应用程序域中被访问,所以驻留的字符串是进程级的,垃圾回收不能释放这些字符串对象,只有进程结束这些对象才被释放)。
  在.Net中处理字符串时,有一个很重要的机制,叫做字符串驻留机制。由于string是编程中用到的频率较高的一种类型,CLR对相同的字符串,只分配一次内存。CLR内部维护着一块特殊的数据结构,我们叫它字符串池,可以把它理解成是一个HashTable,这个HashTable维护着程序中用到的一部分字符串,HashTable的Key是字符串的值,而Value则是字符串的内存地址。一般情况下,程序中如果创建一个string类型的变量,CLR会首先在HashTable遍历具有相同Hash Code的字符串,如果找到,则直接把该字符串的地址返回给相应的变量,如果没有才会在内存中新建一个字符串对象。

image 

未标题-2

  通过上一篇文章,大家会了解字符串驻留机制、恒定性、常量池等特性,这篇文章通过10个例子,来为大家讲解string,同时如果你自己对string的了解程度,有足够的信心,那么就来读一下这篇文章,试试做一下10个例子,检测一下自己有多“牛”。

  重点回顾:

  代码九:

 

 

 

    string a = "a";

    string b = new string('a', 1);

    Response.Write(a.Equals(string.Intern(b)));

    Response.Write(ReferenceEquals(a, string.Intern(b)));

  输出:True (Equals比较值,无论是否Intern都会相同)

  True (ReferenceEquals比较字符串对象的引用,Intern已经将b驻留至字符串池内)

  代码十:

    string a = "str";

    string b = "str_2".Substring(0,3);

    Response.Write(a.Equals(b));

    Response.Write(ReferenceEquals(a, b));

  输出:True (Equals比较值,a与c的值相同)

  False (ReferenceEquals比较字符串对象的引用,Substring操作产生了新的字符串对象)

  此段代码产生了3个string对象,是哪3个呢?如果你不明白,还是从头再看一遍吧!

image 

未标题-2

  这篇文章将全面的为大家介绍.Net 垃圾收集的运行方式、算法,以及与垃圾收集相关的关键方法。 说到垃圾收集机制,很少有人知道,垃圾收集并不是伴随Java出现的,早在1958年,图林奖得主John发明的Lisp语言就已经提供了GC的功能,这是GC的第一次出现,是思想的一次闪光!而后,1984年Dave Ungar发明的Small talk语言第一次正式采用了GC机制。

  这篇文章将重点为大家介绍.Net垃圾收集器、代龄、策略引擎,并结合Windbg为大家展现一个有趣的机制平台。

  重点回顾:

  .Net中采用了一种叫做“标记与清除(Mark Sweep)”算法来完成上述任务。

“标记与清除”算法,顾名思义,这种算法有两个本领:

  “标记”本领——垃圾的识别:从应用程序的root出发,利用相互引用关系,遍历其在Heap上动态分配的所有对象,没有被引用的对象不被标记,即成为垃圾;存活的对象被标记,即维护成了一张“根-对象可达图”。

  其实,CLR会把对象关系看做“树图”,无疑,了解数据结构的同学都知道,有了“树图”的概念,会加快遍历对象的速度。

  检测、标记对象引用,是一件很有意思的事情,有很多方法可以做到,但是只有一种是效率最优的,.Net中是利用栈来完成的,在不断的入栈与出栈中完成检测:先在树图中选择一个需要检测的对象,将该对象的所有引用压栈,如此反复直到栈变空为止。栈变空意味着已经遍历了这个局部根(或者说是树图中的节点)能够到达的所有对象。树图节点范围包括局部变量(实际上局部变量会很快被回收,因为它的作用域很明显、很好控制)、寄存器、静态变量,这些元素都要重复这个操作。一旦完成,便逐个对象地检查内存,没有标记的对象变成了垃圾。

  “清除”本领——回收内存:启用Compact算法,对内存中存活的对象进行移动,修改它们的指针,使之在内存中连续,这样空闲的内存也就连续了,这就解决了内存碎片问题,当再次为新对象分配内存时,CLR不必在充满碎片的内存中寻找适合新对象的内存空间,所以分配速度会大大提高。但是大对象(large object heap)除外,GC不会移动一个内存中巨无霸,因为它知道现在的CPU不便宜。通常,大对象具有很长的生存期,当一个大对象在.NET托管堆中产生时,它被分配在堆的一个特殊部分中,移动大对象所带来的开销超过了整理这部分堆所能提高的性能。
  代龄就是对Heap中的对象按照存在时间长短进行分代,最短的分在第0代,最长的分在第2代,第2代中的对象往往是比较大的。Generation的层级与FrameWork版本有关,可以通过调用GC.MaxGeneration得知。

image

未标题-2

  这一篇主要讲了GC相关的重要方法。主要包括终止队列(Finalization Queue)与可达队列(Freachable Queue)、复生(Resurrection)、弱引用(WeakReference)、策略引擎、Dispose()、GC.Collect()、析构函数(Finalize()等知识点。

  重点回顾:

  首先要了解与Finalize相关的两个队列:终止队列(Finalization Queue)与可达队列(Freachable Queue),这两个队列存储了一组指向对象的指针。

  当程序中在托管堆上分配空间时(new),如果该类含有析构函数,GC将在Finalization Queue中添加一个指向该对象的指针。

  在GC首次运行时,会在已经被确认为垃圾的对象中遍历,如果某个垃圾对象的指针被Finalization Queue包含,GC将这个对象从垃圾中分离出来,将它的指针储存到Freachable Queue中,并在Finalization Queue删除这个对象的指针记录,这时该对象就不是垃圾了——这个过程被称为是对象的复生(Resurrection)。当Freachable Queue一旦被添加了指针之后,它就会去执行对象的Finalize()方法,清除对象占用的资源。

  当GC再次运行时,便会再次发现这个含有Finalize()方法的垃圾对象,但此时它在Finalization Queue中已经没有记录了(GC首次运行时删掉了它的Finalization Queue记录),那么这个对象就会被回收了。
  至此,通过GC两次运行,终于回收了带有析构函数的对象。

image

未标题-2

  JIT--实时编译机制是.Net平台的又一亮点,这个文章将分为上下两节,从运行原理、机制等方面,结合WinDbg为大家详细的讲解JIT方面的知识。关键字:JIT MSIL 元数据 方法表 托管模块 本地映像。

  重点回顾:

  JIT是运行时的一个重要职责模块,它将IL转换为本地CPU指令,从上图可以看出,也许你不敢相信,即时编译这个过程是在运行时发生的,这会不会对性能产生影响呢?事实上答案是虽然是肯定的,但这种开销物有所值:

  1. JIT所造成的性能开销并不显著。

  2. JIT遵循计算机体系理论中两个经典理论:局部性原理与8020原则。局部性原理指出,程序总是趋向于使用最近使用过的数据和指令,这包括空间的和时间的,将局部性原理引申可以得出,程序总是趋向于使用最近使用过的数据和指令,以及这些正在使用的数据和指令临近的数据和指令(凭印象写的,但不曲解原意);而8020原则指出,系统大多数时间总是花费80%的时间去执行那20%的代码。

  根据这两个原则,JIT在运行时会实时的向前、后优化代码,这样的工作只有在运行时才可以做到。

  3. JIT只编译需要的那一段代码,而不是全部,这样节约了不必要的内存开销。

  4. JIT会根据运行时环境,即时的优化IL代码,即同样的IL代码运行在不同CPU上,JIT编译出的本地代码是不同的,这些不同代码面向自己的CPU做出了优化。

  5. JIT会对代码的运行情况进行检测,并对那些特殊的代码经行重新编译,在运行过程中不断优化。

image

未标题-2

  这一篇文章主要讲了JIT一些实例,结合Windbg,对代码进行运行时监控,并通过Windbg的反馈向大家展示运行时编译的过程。

  重点回顾:

  回车后注意高亮区域的信息:

clip_image002

  图8 JIT前A类型的信息

  高亮区域显示的是“”,这说明虽然运行和程序,但未点击按钮时,A类型未被JIT,因为它还没有入口地址。这一点体现了即时、按需编译的思想。

  同样,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令会得到同样的结果。

  好,现在做第4步操作,Detach Debuggee进程,并回到程序中点击“GO”按钮

clip_image004

图9 点击按钮

  第五步 重新附加进程(参考第一步),这时程序已经调用了new A().a1()方法,并重新执行命令!name2ee *!JITTester.A ,注意高亮部分

clip_image006

图10 JIT后A类型的信息

  和图8中的信息比较,图10中的方法表地址已经变为JIT后的内存地址,这时图4中的Stub槽将被一条强制跳转语句替换,跳转目标与该地址有关。这一点说明JIT在大多情况下,只编译一次代码。

image

未标题-2

    新年伊始,该文章是博客园2010年的第一篇文章,感兴趣的同学可以注意一下该文章的发布日期,是2010年1月1日1秒。

    本文分三节为大家深入介绍.Net GC的完整收集(Full GC)机制 、GC工作模式以及.Net 4.0中GC的特性方法。

  重点回顾:
  GC的工作模式分3种,Workstation GC with Concurrent GC off、 Workstation GC with Concurrent GC on、Server GC ,在.Net 2.0以上版本可以通过修改Config文件来改变GC工作模式。

  Workstation GC without Concurrent: 用于单CPU的服务器,策略引擎会调节GC工作频率,使用挂起->查找与标记->压缩->恢复的流程进行GC工作。

  Workstation GC with Concurrent: Concurrent GC与Non Concurrent GC模式相比,有着更敏捷的反应速度,Winform应用程序和Windows services   服务程序默认采用这种模式,单CPU机器上只能使用workstation GC方式,默认为 Workstation GC with Concurrent。

在这种模式下,第0、1代的收集仍然是要暂时挂起应用程序的,只有在收集第2代时,才会并行处理,这种并行收集是利用多CPU

对Full GC进行并行处理,具体原理是将Full GC过程切分成多个短暂子过程对线程进行冻结,在线程冻结时间之外,应用程序仍然可

以正常运行。这主要通过将0代空间设置的很大,使Full GC时,CLR仍然能够在0代中进行内存分配,如果Full GC时0代内存也已用尽,那么应用程序将被挂起,等待Full GC的完成。

  Server GC: 用于多CPU的服务器,这种GC模式有着很高的性能和效率。这种模式下,CLR为每个CPU创建一个专用的GC线程,每个CPU可以独立的为相应的  heap执行GC操作,这些GC线程是以非并发的形式工作的,收集工作与线程正常工作不能同时进行,这就是说第0、1、2代的收集都会挂起应用线程。

  在.Net 4.0中,有一种新的垃圾收集机制,叫做后台收集。这种机制以concurrent GC为基础的,如上文所讲,Workstation GC with Concurrent模式中,在Full GC过程时,CLR仍然能够在0代中进行内存分配,如果Full GC时0代内存也已用尽,那么应用程序将被挂起,等待Full GC的完成。

这个过程在后台收集机制中是这样工作的,在进行Full GC时可以同时进行第0、1代收集,并且后台收集是一个独立线程完成的,这个进程任务优先级低于第0、1代收集,如果在后台收集中需要对第0、1代收集,后台收集将会等待第0、1代收集完成后再进行工

作,当然第0、1代收集是需要短暂挂起应用的。

  后台收集还会根据策略引擎的指示,动态调节第0、1代的容量,减少前台收集(第0、1代收集)次数。

image

未标题-2

  本文是《.Net Discovery》系列文章(一)的勘误版。

重点回顾:

  所以,第三行C#代码(a = "str_2";)的样子看起来是在修改变量a的旧值"str_1",但实际上是创建了一个新的字符串"str_2",然后将变量a的指针指向了"str_2"的内存地址,而"str_1"依然在内存中没有受到任何影响,所以变量b的值没有任何改变---这就是string的恒定性,同学们,一定要牢记这一点,在.Net中,string类型的对象一旦创建即不可修改!包括ToUpper、SubString、Trim等操作都会在内存中产生新的字符串。

image

未标题-2

  本文是《.Net Discovery》系列文章(二)的勘误版。

重点回顾:

  代码二:

    string a = "str_1str_2";

    string b = "str_1";

    string c = "str_2";

    string d = b + c;

    Response.Write(a.Equals(d));

    Response.Write(ReferenceEquals(a, d));

  输出:True(Equals比较字符串对象的值)

  False(ReferenceEquals比较字符串对象的引用,由于变量d的值为变量连接的结果,字符串驻留机制无效)

  代码三:
    string a = "str_1str_2";

    string b = "str_1" + "str_2";

    Response.Write(a.Equals(b));

    Response.Write(ReferenceEquals(a, b));

  输出:True(Equals比较字符串对象的值)

  True (ReferenceEquals比较字符串对象的引用,由于变量b的值为常量连接的结果,字符串驻留机制有效。如果变量b的值由“常量+变量”的方式得出,则字符串驻留无效)

image

未标题-2

  转眼间《.Net Discovery》系列文章已经推出1年了,本文为该系列的第10-13篇文章,在本文中将对以前所讲的.Net平台知识做一个小小的总结与机制分析,引出并重点介绍这些机制对程序性能的影响与改进建议。 本文将分为四部分,分别讲述了:垃圾回收机制、即时编译机制、异常处理机制、字符串驻驻留机制的原理与性能改进建议。

  本文主要介绍垃圾回收机制对系统性能的影响分析。

重点回顾:

  垃圾收集器一般将托管堆中的对象分为3代,这可以通过调用GC.MaxGeneration得知,对象按照存在时间长短进行分代,最短的分在第0代,最长的分在第2代,第2代中的对象往往是比较大的,第二代空间被称作Large Object Heap,对于2代对象的回收,与第0、1代回收方式相比最大的不同在于,没有了指针移动的压缩过程。

  如下图,第一次GC时,左边第一列A-F表示内存中的对象,位于浅蓝色 区域,经过Mark后,ACDF标记为可用,Sweep过程清除了BE,Compact过程移动了ACDF,使之位于连续存储区域中;第二次使用绿色做标记;第三次GC使用蓝色表示标记;可以看出第三次GC过程没有了指针移动的压缩过程。

clip_image002[6]

图1 对象的回收

image

未标题-2

  转眼间《.Net Discovery》系列文章已经推出1年了,本文为该系列的第10-13篇文章,在本文中将对以前所讲的.Net平台知识做一个小小的总结与机制分析,引出并重点介绍这些机制对程序性能的影响与改进建议。 本文将分为四部分,分别讲述了:垃圾回收机制、即时编译机制、异常处理机制、字符串驻驻留机制的原理与性能改进建议

  本文主要介绍即时编译机制对系统性能的影响分析。

重点回顾:

  运行时,操作系统会根据托管模块中各种头信息,装载相应的运行时框架,Load()被加载,由于是第一次加载,这会触发对Load()的即时编译,JIT会检测Load()中引用的所有类型,并结合元数据遍历这些类型中定义的所有方法实现,并用一个特殊的HashTable(仅用于理解)储存这些类型方法与其对应的入口地址(在未被JIT前,这个入口地址为一个预编译代理(PreJitStub),这个代理负责触发JIT编译),根据这些地址,就可以找到对应的方法实现。

  在初始化时,HashTable中各个方法指向的并不是对应的内存入口地址,而是一个JIT预编译代理,这个函数负责将方法编译为本地代码。注意,这里JIT还没有进行编译,只是建立了方法表

clip_image001

图2方法表、方法描述、预编译代理关系

  图2中所示的MS核心引擎指的是一个叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一个桥接DLL,连同mscorwks.dll主要完成以下工作:

  1. 查找程序集中包含的对应类型清单,并调用元数据遍历出包含的方法。

  2. 结合元数据获得这个方法的IL。

  3. 分配内存。

  4. 编译IL为本地代码,并保存在第3步所分配的内存中。

  5. 将类型表(就是指上文中提到的HashTable)中方法地址修改为第3步所分配的内存地址。

  6. 跳转至本地代码中执行。

  所以随着程序的运行时间增加,越来越多的方法的IL被编译为本地代码,JIT的调用次数也会不断减少。

image

未标题-2

  转眼间《.Net Discovery》系列文章已经推出1年了,本文为该系列的第10-13篇文章,在本文中将对以前所讲的.Net平台知识做一个小小的总结与机制分析,引出并重点介绍这些机制对程序性能的影响与改进建议。 本文将分为四部分,分别讲述了:垃圾回收机制、即时编译机制、异常处理机制、字符串驻驻留机制的原理与性能改进建议

 

  本文主要介绍异常处理机制、字符串驻驻留机制对系统性能的影响分析。

重点回顾:

  .Net 中基本的异常捕获与处理机制是由try…catch…finally块来完成的,它们分别完成了异常的监测、捕获与处理工作。一个try块可以对应零个或多个catch块,可以对应零个或一个finally块。不过没有catch的try似乎没有什么意义,如果try对应了多个catch,那么监测到异常后,CLR会自上而下搜索catch块的代码,并通过异常过滤器筛选对应的异常,如果没有找到,那么CLR将沿着调用堆栈,向更高层搜索匹配的异常,如果已到堆栈顶部依然没有找到对应的异常,就会抛出未处理的异常了,这时catch块中的代码并不会被执行。所以距离try最近的catch块将最先被遍历到。

  最后三行的每一个Item被称作Exception Handing Clause,EHC组成Exception Handing Table,EHT与正常代码之间由ret返回指令隔开。

  可以看出,FormatException排列在EHT的第一位。

  当代码成功执行或反之而返回后,CLR会遍历EHT:

  1. 如果抛出异常, CLR会根据抛出异常的代码的“地址”找到对应的EHC(IL_0001 to IL_0010为检测代码的范围),这个例子中CLR将找到2条EHC, FormatException会最先被遍历到,且为适合的EHC。

  2. 如果返回的代码地址在IL_0001 to IL_0029内,那么还会执行finally handler IL_0029 to IL_0033中的代码,不管是否因成功执行代码而返回

  事实上,catch与finally的遍历工作是分开进行的,如上文所言,CLR首先做的是遍历catch,当找到合适的catch块后,再遍历与之对应finally;而且这个过程会递归进行至少两次,因为编译器将C#的try…catch…finally翻译成IL中的两层嵌套。

  当然如果没有找到对应的catch块,那么CLR会直接执行finally,然后立即中断所有线程。Finally块中的代码肯定会被执行,无论try是否检测到了异常。


推荐阅读
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 本文详细介绍了如何使用Python的多进程技术来高效地分块读取超大文件,并将其输出为多个文件。通过这种方式,可以显著提高读取速度和处理效率。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 深入解析 Synchronized 锁的升级机制及其在并发编程中的应用
    深入解析 Synchronized 锁的升级机制及其在并发编程中的应用 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节
    揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节 ... [详细]
  • 本文介绍了如何将包含复杂对象的字典保存到文件,并从文件中读取这些字典。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 双指针法在链表问题中应用广泛,能够高效解决多种经典问题,如合并两个有序链表、合并多个有序链表、查找倒数第k个节点等。本文将详细介绍这些应用场景及其解决方案。 ... [详细]
  • 性能测试中的关键监控指标与深入分析
    在软件性能测试中,关键监控指标的选取至关重要。主要目的包括:1. 评估系统的当前性能,确保其符合预期的性能标准;2. 发现软件性能瓶颈,定位潜在问题;3. 优化系统性能,提高用户体验。通过综合分析这些指标,可以全面了解系统的运行状态,为后续的性能改进提供科学依据。 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
author-avatar
哭泣的玫瑰花丶_443
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有