返回目录

第二章:垃圾回收

垃圾回收是你开发工作中要了解的最重要的事情。它是造成性能问题里最显著的原因,但只要你保持持续的关注(代码审查,监控数据)就可以很快修复这些问题。我这里说的“显著的原因”,实际上是我们对垃圾回收的理解和期望不正确导致的。在.NET开发中,内存的性能问题和CPU的性能问题一样多,这就是单独开一章主要描述这个问题的原因。

当我们提及垃圾回收造成的开销时,就会不如自主的紧张起来,但一旦你理解它,就能很好的优化你的程序。在后面文章里,你可以看到GC可以在大多数情况下,在堆处理上提供很好的性能,同时也能很好解决内存分配与内存碎片问题。

Windows在非托管堆里用使用一个空闲列表来维护内存分配。尽管它会想尽办法来减少内存碎片,但很多长时间运行的非托管代码(本地代码)的程序还是会碰上内存碎片问题。它会花很多时间在空闲列表里找到合适可分配地址。随着内存使用持续增长,不可避免的需要不断重启来解问题。一些程序还会采用自定义内存分配的方案(自己的内存分配算法来接管malloc)函数来解决内存碎片问题。

.NET里的内存分配通常在一个内存段里进行,这样申请和回收的消耗会小很多。托管堆通过将最近申请的内存对象放在一起,可以减少对空闲列表的遍历,提升性能。

在默认的分配过程中,通过代码获得对象的大小,然后在剩余的缓冲区里分配它。因为没有竞争,只要有合适的空间就能很快的分配。一旦这要申请的空间这一段无法满足,GC分配器会创建一个新的内存段,在这上面开始分配,之后的新的分配也都会在这新创建的内存段里进行。

这个过程中系统代码(分配器)只会做一些简单的检查。

我们来看一个简单的栗子:

  1. private class MyObject
  2. {
  3. private int x;
  4. private int y;
  5. private int z;
  6. }
  7. private static void Main(string[] args)
  8. {
  9. var x = new MyObject();
  10. }

首先,我们在分配器前设置一个断点

  1. ; Copy method table pointer for the class into
  2. ; ecx as argument to new()
  3. ; You can use !dumpmt to examine this value.
  4. mov ecx,3F3838h
  5. ; Call new
  6. call 003e2100
  7. ; Copy return value (address of object) into a register
  8. mov edi,eax

目前分配的是:

  1. ; NOTE: Most code addresses removed for formatting reasons.
  2. ;
  3. ; Set eax to value 0x14, the size of the object to
  4. ; allocate, which comes from the method table
  5. mov eax,dword ptr [ecx+4] ds:002b:003f383c=00000014
  6. ; Put allocation buffer information into edx
  7. mov edx,dword ptr fs:[0E30h]
  8. ; edx+40 contains the address of the next available byte
  9. ; for allocation. Add that value to the desired size.
  10. add eax,dword ptr [edx+40h]
  11. ; Compare the intended allocation against the
  12. ; end of the allocation buffer.
  13. cmp eax,dword ptr [edx+44h]
  14. ; If we spill over the allocation buffer,
  15. ; jump to the slow path
  16. ja 003e211b
  17. ; update the pointer to the next free
  18. ; byte (0x14 bytes past old value)
  19. mov dword ptr [edx+40h],eax
  20. ; Subtract the object size from the pointer to
  21. ; get to the start of the new obj
  22. sub eax,dword ptr [ecx+4]
  23. ; Put the method table pointer into the
  24. ; first 4 bytes of the object.
  25. ; eax now points to new object
  26. mov dword ptr [eax],ecx
  27. ; Return to caller
  28. ret
  29. ; Slow Path call into CLR method
  30. 003e211b jmp clr!JIT_New (71763534)

总之,这些涉及到方法调用的指令只有9个,不是很难懂。

如果你使用了一些配置选项,例如工作站模式,它不会因为竞争而导致分配变慢,因为GC给每一个处理器(cpu内核)分配了一个堆(段)。.NET在这些内存分配地方做了一些复杂处理,但你不必深入了解它是如何工作,只需要知道如何优化它就可以了。

我在本书开头就涉及到垃圾回收是因为今后章节里很多东西都会涉及到它。正确的理解垃圾回收是帮助你实现好性能的基础。

下一节 >> 基本操作

[翻译]编写高性能 .NET 代码 第二章:垃圾回收的更多相关文章

  1. [翻译]编写高性能 .NET 代码 第二章:垃圾回收 基本操作

    返回目录 基本操作 垃圾回收的算法细节还在不断完善中,性能还会有进一步的提升.下文介绍的内容在不同的.NET版本里会略有不同,但大方向是不会有变动的. 在.net进程里会管理2个类型的内存堆:托管和非 ...

  2. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少分配率, 最重要的规则,缩短对象的生命周期,减少对象层次的深度,减少对象之间的引用,避免钉住对象(Pinning)

    减少分配率 这个几乎不用解释,减少了内存的使用量,自然就减少GC回收时的压力,同时降低了内存碎片与CPU的使用量.你可以用一些方法来达到这一目的,但它可能会与其它设计相冲突. 你需要在设计对象时仔细检 ...

  3. [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区

    避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...

  4. [翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化

    将长生命周期对象和大对象池化 请记住最开始说的原则:对象要么立即回收要么一直存在.它们要么在0代被回收,要么在2代里一直存在.有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束.其它对 ...

  5. [翻译] 编写高性能 .NET 代码--第二章 GC -- 配置选项

    配置选项 在基于"less rope to hang yourself with"思想下,.NET 框架没有给开发提供很多太多的配置选项.但在大多数情况下,GC会跟你的硬件配置,及 ...

  6. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少大对象堆的碎片,在某些情况下强制执行完整GC,按需压缩大对象堆,在GC前收到消息通知,使用弱引用缓存对象

    减少大对象堆的碎片 如果不能完全避免大对象堆的分配,则要尽量避免碎片化. 对于LOH不小心就会有无限增长,但LOH使用的空闲列表机制可以减轻增长的影响.利用这个空闲列表,我们可以在两块分配区域中间找到 ...

  7. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Performance Counters(性能计数器)

    <<返回目录 Performance Counters(性能计数器) 性能计数器是监视应用程序和系统性能的最简单的方法之一.它有几十个类别数百个计数器在,包括一些.net特有的计数器.要访 ...

  8. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Visual Studio

    <<返回目录 Visual Studio vs虽然不是全宇宙唯一的IDE,但它是.net开发人员最常用的开发工具.它自带一个性能分析工具,你可以使用它来做开发,不同的vs版本在工具上会略有 ...

  9. [翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 平均值 vs 百分比

    <<返回目录 平均值 vs 百分比 在考虑要性能测试的目标值时,我们需要考虑用什么统计口径.大多数人都会首选平均值,但在大多数情况下,这个正确的,但你也应该适当的考虑百分数.但你有可用性的 ...

随机推荐

  1. jsp里的逻辑语句c:if和c:choose

    1.c:if <c:if test=""></c:if> c:when的test里可以是变量或者是一个EL表达式,其结果应该是true或者false. EL ...

  2. Oracle内连接、外连接、右外连接、全外连接小总结

    数据库版本:Oracle 9i 表TESTA,TESTB,TESTC,各有A, B两列 A B 001 10A 002 20A A B 001 10B 003 30B A B 001 10C 004 ...

  3. 如何更改MyEclipse中XML文件的字体?

    windows>Preferences>General>Appearance>Colors and Fonts>Basic>Text Font

  4. cpp - 输入输出

    c语言面向过程 c++支持面向过程+支持面向对象 #include <iostream> using namespace std; int main() { int a; cout < ...

  5. 微信小程序左右滑动切换图片酷炫效果(附效果)

    开门见山,先上效果吧!感觉可以的用的上的再往下看. 心动吗?那就继续往下看! 先上页面结构吧,也就是wxml文件,其实可以理解成微信自己封装过的html,这个不多说了,不懂也没必要往下看了. < ...

  6. Switch-case 内定义变量的问题

    Switch-case 内定义变量的问题 这个问题需要分开讨论,C 语言和 C++ 的标准定义是不同的. C++ int Caset(int a) { switch (a) { case 1: int ...

  7. Linux实践篇--crontab定时任务

    原文出处:http://www.cnblogs.com/tracy/archive/2011/12/27/2303788.html.感谢作者的无私分享 一.  Crontab 介绍           ...

  8. JavaWeb项目架构之Redis分布式日志队列

    架构.分布式.日志队列,标题自己都看着唬人,其实就是一个日志收集的功能,只不过中间加了一个Redis做消息队列罢了. 前言 为什么需要消息队列? 当系统中出现"生产"和" ...

  9. CSS中设置border:none和border:0的区别

    在我们设置CSS的时候,对标签元素不设置边框属性或者取消边框属性一般设置为:border:none;或border:0;两种方法均可. border:none;与border:0;的区别体现有两点:一 ...

  10. 搭建内网的NTP时间服务器

    NTP时间服务器 标签: linux 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 1.简介 NTP(Network Time Protocol,网络时间协议) ...