高效使用GC - 第一部分
来源:互联网 发布:怎么在mac上卸载程序 编辑:程序博客网 时间:2024/06/06 00:43
原文出自:http://blogs.msdn.com/maoni/archive/2004/06/15/156626.aspx
版权归原文作者所有,转载请注明出处
高效使用GC-第一部分
本文的目的是讨论GC操作的成本,以便在使用托管内存时能作出正确决策- 注意不是讨论GC本身-而是如何使用GC。本文假定您对使用GC,而不是实现GC更有兴趣。同时假定对GC有基本的了解 。如果需要了解一些背景知识的话,推荐阅读Jeff Richter 发表在MSDN上的两篇文章: 1 和2.
首先我们关注Workstation GC,然后讨论其与server GC的不同之处(有时候你不需要选择使用哪个,稍后我讲解释为什么)。
代
拥有三个代的原因是:希望在优化过的应用程序中,大部分对象仅存活在0代。举个例子,在一个服务器应用程序中,为每一个请求分配的内存应该在请求结束后被释放。接着请求相关的对象被提升到1代然后死亡。本质上讲,1代充当了年轻对象和年长对象之间的缓冲区。当在性能监视器查看搜集次数时,2代应当比0代搜集频率低。1代堆的搜集次数相对不那么重要。并且搜集1代比0代的成本低。
GC段
首先看一下GC如何从操作系统获得内存。GC在段内保留内存,每个段16MB。当EE (执行引擎)开始,会保留一些初始段 - 一个用作小对象堆,另外一个用作大对象堆。
在需要时,内存被提交或者撤销提交。用完这些段之后将保留一个新段。每次完全搜集时,不被使用的段将被删除。
大对象对总是在自己段上 - 大对象被区别于小对象来对待,它们之间不共享段。
分配
在GC堆上分配时,实际的成本是多少?如果我们不需要进行垃圾搜集,那么分配操作将1)向前移动指针 并且2)为新对象清理内存。对于可终结的对象,还有一个额外的步骤:将其加入到一个被GC监视的列表。
注意,我说的是“如果不需要进行垃圾搜集”- 意味着分配成本与分配的内存大小是成比例的。分配的内存越少,GC需要做的就越少。如果需要15字节,就请求15字节而不会增加到32 字节或更大的块。分配有一个极限值,达到这个值就会触发垃圾搜集,应该尽可能的避免垃圾搜集。
GC堆区别与NT对的另一个特性就是:GC堆上的对象是一个紧挨一个的,因而可以保持当前的分配位置。
分配在GC堆上的每一个对象都需要8字节的额外开销(同步锁+方法表指针)。
如我前面提到的,大对象被区别对待,所以使用不同的分配模式。将在大对象这一节讨论此问题。
搜集
重要的事情优先 - 什么时候开始搜集(换言之,什么时候GC被触发)?GC在满足下面三个条件之一时触发:
1) 分配时达到0代极限值
2) System.GC.Collect被调用
3) 系统处在低内存情况下
1)是最常见的。分配只发生在0代。每次垃圾搜集,0代被清空。然后0代被充满,接着再次搜集,如此反复。
不调用GC.Collect可以避免2) – 写应用程序的时候,一般情况下,永远不要调用它。BCL是唯一应该调用它的地方。在应用程序中调用GC.Collect带来的问题是:GC的次数比预计的要频繁(非常容易发生),性能急剧下降,这是由于为获得最佳性能而调节的GC调度被打乱了。
3)受到系统其它进程的影响,所以无法精确掌控,只能保证自己的进程行为良好。
看看这意味着什么。首先,GC堆是工作集(working set)的一部分。并且消耗私有页(private pages)。理想情况下,分配在0代上的对象总是在0代上死亡(对象总是被0代GC搜集,永远不会发生完全搜集)所以GC堆讲永远不会超过0代的大小。现实中当然不是这样。所以应该保证GC堆大小是可控的。
其次,应该保证GC花费的时间是可控的。也就是说1)减少搜集次数 以及 2)减少对代龄高的代的搜集次数。对代龄高的代进行搜集比代龄低的代要昂贵的多,因为搜集高龄的代的同时也要搜集低龄的代。要么分配很快会死亡的临时对象(绝大多数在0代,搜集0代成本很低),要么分配存活很久的对象(呆在2代)。对后一种情况,常见的场景是在程序启动时就分配一些对象 – 比如,订单系统中,为整个目录分配内存,这些内存直到应用程序终止才会释放。
CLRProfiler 是一个很棒的工具,可以用来查看GC堆上有什么以及什么在持有存活的对象。
如何组织数据
1)值类型 vs 引用类型
如你所知,值类型分配在堆栈上,引用类型在GC堆上。有人会问:如何决定什么时候使用值类型什么时候使用引用类型。就性能而言,答案通常是“看情况”,这里也不例外。值类型不会触发垃圾搜集,但如果值类型经常被装箱,装箱操作比创建引用类型的实例要昂贵;值类型参数在传递时需要拷贝。对于小成员来说,使用引用类型会带来额外的指针开销。我们有一些内部代码将其内联(比如,作为值类型)以缩小工作集来提升性能。这依赖于使用类型的模式。
2)引用富对象
如果对象是引用富集的(包含比较多的引用),会同时对分配和搜集带来压力。每个内嵌对象带来8字节开销。并且由于分配成本与分配大小成比例,,所以现在分配成本更高了。搜集时,构建对象引用树也需要花费更多时间。
一般建议根据类的逻辑设计来组织类数据。对存活对象的引用,不需要时就不应该保持。举个例子,在可以避免的情况下,尽量不在老对象中储存对年轻对象的引用。
3)可终结对象(Finalizable objects)
我会在专门的章节详细讨论终结。但现在,最重要的事情是紧记可终结对象在被终结时,它持有所有对象都必须是存活的,这会增加搜集成本。所以要尽可能的将可终结对象与其他对象隔离开。
4)对象位置
在分配对象的子对象时,如果子对象的生存周期与父对象相同,那么它们应该被同时分配,它们将一起放在GC堆上。
大对象
在请求大于等于85000字节的对象时,将在大对象堆(LOH)进行分配。大对象堆永远不会压缩-仅仅清理(使用一个释放列表)。有一个不应该依赖的实现细节 – 如果分配大对象并且不希望被移动,必须确保钉住(pin)这个对象。
大对象仅在完全搜集时被搜集,所以搜集成本很高。有时会发现完全搜集之后2代对大小没有多大变化。这意味着搜集是针对大对象堆的 (可以在性能监视器中观察大对象堆的体积来判断)。
比较好的使用大对象的方式是:分配并重用对象。这样不会导致过多的完全搜集。如果对象大小在100k-120k之间,那么分配120K并重用它。分配大量临时大对象是个坏主意,因为这将导致总是进行完全搜集。
- 高效使用GC - 第一部分
- 【阅读】《如何高效学习》——第一部分1
- Jquery的使用第一部分
- Jakarta Struts编程:使用Tiles,第一部分
- 在运行时使用 UDDI,第一部分
- Jakarta Struts编程:使用Tiles,第一部分
- 开始使用Commons Chain (第一部分)
- Jakarta Struts编程:使用Tiles,第一部分
- 第一部分 DWR是什么,如何使用
- 第一部分 DWR是什么,如何使用
- inno setup使用详细 第一部分
- 如何使用NSOperations和NSOperationQueues 第一部分
- iOS中的storyboard使用 (第一部分)
- ViewPagerIndictor框架的使用(第一部分)
- RxJava 教程第一部分:为何使用RxJava
- D3.python第一部分 使用入门
- 第一部分
- 第五部分 高效并发
- 按照控制面板区域设置显示时间的格式函数
- 点滴积累1
- VS2005的语言设置可能会导致模板丢失和类设计器(Class Designer)不能创建类型
- AJAX框架资源汇总
- 请教大家有关delphi里面的输入法的控制问题
- 高效使用GC - 第一部分
- 跪求 高手教我怎么搞私服游戏
- .net学习站点大汇总
- PMBOK 学习笔记3-项目管理过程组
- Add thousand separator (SQL Server 2005)
- 备份与恢复windows 2003的AD数据库
- oracle 存储过程的基本语法
- 函数WSAStartup
- VB.NET环境下的Socket异步编程举例