与GC相关的性能计数器
如果遇到了性能问题,在使用debug之前分析问题较为不错的一个工具就是perfmon.解决问题最好的方法是思考,这也是熊力大哥在其书中一直在强调的.
如果您的网站遇到下面的几种情形,那还是先看看perfmon里GC相关的东西吧:
- cpu占用高,内存占用不高.
- cpu和内存占用都比较高
- cpu和内存占用都不高,但是网站响应很慢
打开perfmon找到.NET CLR Memory后下面有好几个counter,从哪个开始看呢?
1) % Time in GC
这个值是说从上一次GC结束到当前这次GC的时间的百分比. 比如上次GC结束时经历了100w个循环,当前的GC消耗是50w个循环,这个计数器的值就是50%. 看perfmon的各个counter来推测究竟是什么问题,主要有两类情况,第一类需要看counter到变化趋势,第二类需要看到是counter到值.这里对待第2类情况引入一个"健康值"的概念.当然这些只是大方向上来说到,并不是100%到准确的适应大多数情况.
那么这个值为多少合适呢? 一般来说如果这个值>50%了我们应该去检查一下托管堆的问题.如果这个值不大,没有太大的必要去优化程序了.
2)Allocated Bytes/sec
如果认为在GC上花费的时间太多了,接下来应该看看Allocated Bytes/sec这个counter,它显示了分配速率.需要注意到是这个counter到值在分配速率很低的情况下其实是不准确的,这个值只有在每次GC开始的时候才会被更新,如果perfmon到取样频率(默认是1秒)大于GC的频率的时候,这个值就不太容易说明问题了。
当有分配请求不能被完成时,会触发GC:
- Gen0满了,不能满足最后一次的小对象的分配请求
- LOH满了,不能满足最后一次的大对象分配请求
所以当GC开始的时候会更新该计数器到值——将Gen0和LOH想加的和加到这个值上,然后与上一次的值相加再除以时间间隔。得到的就是这个分配速率。
举个例子:默认情况下perfmon1秒更新一次数据,在第1秒Gen0 GC因为需要分配100k而触发,所以再第1秒末这个值是(100k-0k)/1sec,是100k/sec。在第2秒没有GC发生,记录的值还是100k,那么第2秒末该值就是(100k-100k)/1sec,是0k/sec,第3秒Gen0 GC又被触发总共被分配了200k,所以在第3秒末的时候这个值是(200k-100k)/1sec,是100k/sec。
从上面到例子能看到如果说GC发生的不是非常频繁的话这个值应该是0k/sec的。
3)Large Object Heap Size
这个值只是记录在LOH里的bytes。
OK。到这一步为主,我们可以看出导致GC做大量工作的一个关键因素就是较高的分配速率。大家都知道GC是分Generation的,从Gen0到Gen2,如果一个对象在Gen0到时候就死了,我们况且成它为“夭折”(die young),另一类生命力看似顽强但是到了Gen2立刻挂掉的,我们称之为“中年危机”(die at Gen2)。
所以如果都在GC Gen0完成后就结束工作了,花在GC上的时间百分比是不会高的。毕竟Gen0到GC只会占用非常短暂的时间。
但是Gen2的GC就不这样了,它会从Gen0到Gen2,再加上LOH的。LOH的GC也是很消耗资源的工作,但是并没有只针对LO的回收,所以即使LOH还有空间可供分配,但是Gen2满了,也会导致LOH跟着一起遭殃。
通常来说这三个代的GC速率在100:10:1是不错的。
4)# Gen X Collections
X到值为0,1和2。需要注意的一点是Gen1会一次性的回收Gen0和Gen1。
如果有大量的Gen2上的GC,就意味着有大量的对象存活了太长时间,但是还没长到他们要一直在Gen2里生存。如果看到了GC消耗了很多时间但是分配速率却不高的话,最大的可能就是很多对象在不断的从Gen0到Gen2被不断的提升。
5)Promoted Memory from Gen 0/1和 Promoted Finalization - Memory from Gen 0
这三个值是看提升(promotion)情况的。被finalization引发的对象的提升要看后者,是不包括在前面两个counter里的。但是后者虽然说是from Gen 0的,但是其实同时包含了Gen0和Gen1的。
这时可能出现一种最坏的情况:一个对象存活了很久,最终被提升到Gen2,但是一进入Gen2立刻就死掉了,也就是上述的“中年危机”。当遇到这个情况Promoted Memory from Gen1到只是比较高的,而且有大量的Gen2 GC。
需要注意到是当一个finalizable的对象存活时,所以他引用的对象也都是存活的,Promoted Finalization-Memory from Gen0的计数器也包含了这些对象。
6)Gen 1/2 heap size
当看到这些提升相关的计数器的值比较高时,应该看看这两个counter。他们的意思从名字就能看出。
需要注意Gen 0 heap size到值是假的,它其实是一个预算,指示下一次什么时候进行GC的。
Gen0和Gen1都很小,从256k到几兆。
7)# Total committed Bytes和# Total reserved Bytes
# Total committed Bytes= Gen0 heap size + Gen 1 heap size + Gen 2 heap size + LOH size
后者到值要比前者的值大.
8)# Induce GC
如果看到这个值比较高那就比较惨了,检查代码中GC.Collect()是不是调用了太多了.这种使用和设置IIS检测到memory涨到一定程度自动回收一样,都不是真正解决问题的方法.
参考:
http://msdn.microsoft.com/en-us/library/0xy59wtx.aspx
http://msdn.microsoft.com/en-us/library/f144e03t(VS.80).aspx
http://blogs.msdn.com/maoni
http://msdn.microsoft.com/en-us/library/bb802825.aspx