网站开发和推广的不同,外贸电商平台排行榜,一级a做受片免费网站,如何做企业套模网站基本操作
垃圾回收的算法细节还在不断完善中#xff0c;性能还会有进一步的提升。下文介绍的内容在不同的.NET版本里会略有不同#xff0c;但大方向是不会有变动的。
在.net进程里会管理2个类型的内存堆#xff1a;托管和非托管。本地代码申请的#xff0c;以及由CLR申请…基本操作
垃圾回收的算法细节还在不断完善中性能还会有进一步的提升。下文介绍的内容在不同的.NET版本里会略有不同但大方向是不会有变动的。
在.net进程里会管理2个类型的内存堆托管和非托管。本地代码申请的以及由CLR申请的都是非托管内存使用Windows API 的 VirtualAlloc 方法进行申请。CLR里分配的托管对象则分配在托管堆里这些对象可以被垃圾回收处理。
在托管堆里有还进一步分为小对象对和大对象堆LOH。每个对象类型都有自己的一段堆内存段。每段的大小根据你的配置或者硬件配置有关对于一个大型的应用一个内存段可到几百M。
小对象还可以进一步分为3个世代。0代和1代总是在一个内存段里2代则可以跨越多个内存段。包含0代和1代的内存段称为暂存段。下图是堆的图形分别是A段和B段
A段是小对象堆B段是大对象堆。2代和1代开始只有几个字节大小因为他们到目前为止是空的。
在小对象堆里分配对象存在一个3世代的生命周期CLR在小对象堆里分配小于85000字节的对象8.5k)。0代内存通常在段内存的结尾开始分配。这就是为什么前面看到的.NET内存分配得很快。如果快速分配失败则在0代边界范围里找一个合适的分配地址。如果也没有合适的位置则分配器会扩大0代的在内存段里边界范围。如果扩展范围时超过了内存段的范围则会触发垃圾回收。
对象创建后都是0代。只要对象还存在在GC时会将它们标记到下一代。0代和1代称为临时集合。
当触发GC时可能会同时触发压缩在这种情况下GC会将对象移动到另外一个内存地址里释放当前段的内存空间。如果没有触发压缩也仅仅只是重新划分边界。在一些没有触发过压缩的GC后结果如下图 虽然对象没有移动但边界有重新划分。
压缩可以发生在任何一代的GC里这是一个相当耗时的过程因为它需要重新更新对象的引用关系这需要暂停所有的线程操作。正因为代价高压缩操作只会在必要的时候才会进行。
一旦对象到达2代在剩下的生命周期里会一直保持。这并不意味着2代对象会一直存在如果所有的2代对象被回收并且整段内存里都没有对象则这段内存可以被操作系统回收或者作为其他内存段的附属段。这通常出现在全回收阶段。
那么对象的活着是什么概念呢在GC时可以通过任何已知的root节点到达对象能找到对象的引用关系就说明这个对象还活着。root节点可以是程序里的某个静态变量线程里正在执行的方法指局部对象GC句柄被pinned的句柄以及在finalizer queue里的对象。请注意如果你的对象在2代并且可以被回收但你在做0代GC时它也不会被回收。他们需要等到一个完整回收时才会被干掉。
如果0代对象已经填充满一个内存段再做压缩也不能获得足够空间时gc会重新分配一个新的内存段。新的段将用于放置新分配的1代和0代对象。而之前段的对象都将转为2代对象。所有的0代对象转为1代1代对象同样降为2代对象因为不需要复制所以很容易实现。这个新的内存段看起来如下 如果2代内存不断增加那么它可以存放在多个内存段里。LOH也一样可以跨越多个内存段存放。但不管有多少个内存段0代和1代对象始终在一个内存段里。了解到这些知识对后面的学习会很有帮助。大对象堆使用另外一种规则。通常来说一些字符串或者数组大小在8500直接以上会自动分配到LOH堆里它不会走上面的代纪模型。出于性能考虑LOH分配的对象不会在回收的时候做压缩过程但从.net4.5.1开始你可以按需做压缩了。就行2代对象那样当对象不再LOH里需要时你还是可以回收它使用的内存空间但稍后我们会说到在垃圾回收里最理想的状态是不要将对象创建到大对象堆上。在LOH里分配器都是使用空闲列表的方式来寻找最合适的位置来分配在本章的后面将探索一些技术来减少在大数据堆上减少内存碎片。
垃圾回收特定代时会顺带回收它下面的代。例如回收1代时会把0代的也回收。如果是2代那么就是把所有的都回收了包括LOH里的。如果是0代或者1代回收时程序会暂停执行到GC过程结束。如果是回收2代这一部分操作可以可能会在另外一个后台线程里执行这取决于系统配置。
垃圾回收分为4个阶段
暂停--所有托管线程需要在回收开始前暂停。标记--从每个root节点开始回收器会对每个对象的引用做标记压缩--移动内存对象并重新修改引用路径以便释放内存碎片。它通常发生在小对象堆上并在系统认为需要的时候进行你不能手动控制。在大对象堆上压缩不会自动发生但你可以配置垃圾回收器按需压缩他。恢复--托管线程回复执行
标记阶段实际上不需要遍历堆上的每个对象它只会处理需要回收的堆。举个栗子做0代收集时只会考虑0代的对象做1代收集时则会标记0代和1代的对象。在做2代或者一次完整回收时才需要遍历每一个活着的对象当然这个开销就比较高了。另外需要考虑的是一个高代的对象可能是低代对象的root节点这会导致在做遍历时也会遍历相关的高代对象当然这个开销会比全回收阶段时小一些。
上面描述的过程会导致以下问题。首先垃圾回收所消耗的时间取决于当前还活着的对象数量而不是已经分配出去的数量。这就意味着如果分配了100w个对象在一个root节点上只要下次GC前你把它与root切断引用关系这100w个对象对你的回收耗时不会造成太大影响。其次垃圾回收的频率取决于特定的一代里分配了多少内存。一旦超过内部的一个阈值GC将回收这一代的对象。这个过程GC会根据你的程序做动态调整。如果在某代的回收卓有成效回收了很多对象则在这一代的回收会频繁触发反之则减少。另外一个触发因素就是你的程序在电脑里的可用内存。如果可用内存低于某个阈值GC也会频繁发生用来减少堆的总体体积。
从上面的描述里你可能觉得GC已经超过了你的控制范围。但这其实已经离真相不远了。最简单的优化方式就是你可以通过控制内存的分配模式来达到。你在了解GC是如何工作后你可以根据分配速率对象的生命周期来选择合适的配置。
相关文章
[翻译]编写高性能 .NET 代码 第一章性能测试与工具 -- 选择什么来衡量[翻译]编写高性能 .NET 代码 第一章性能测试与工具 -- 平均值 vs 百分比[翻译]编写高性能 .NET 代码 第一章工具介绍 -- Visual Studio编写高性能 .NET 代码 第一章工具介绍 -- Performance Counters性能计数器编写高性能 .NET 代码 第二章垃圾回收
原文地址http://www.cnblogs.com/yahle/p/6552457.html.NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注