目 录
第1章 基本概念 1
1.1 内存相关术语 2
1.1.1 静态分配 6
1.1.2 寄存器机 6
1.1.3 堆栈(Stack) 7
1.1.4 堆栈机 11
1.1.5 指针 12
1.1.6 堆(Heap) 14
1.2 手动内存管理 15
1.3 自动内存管理 19
1.4 引用计数 23
1.5 跟踪回收器(Tracking Collector ) 26
1.5.1 标记阶段 27
1.5.2 回收阶段 30
1.6 小历史 32
1.7 本章小结 34
规则1 – 自学 34
第2章 底层内存管理 37
2.1 硬件 37
2.1.1 内存 42
2.1.2 CPU 43
2.2 操作系统 55
2.2.1 虚拟内存 55
2.2.2 large page 59
2.2.3 虚拟内存碎片 59
2.2.4 通用内存布局 59
2.2.5 Windows内存管理 60
2.2.6 Windows内存布局 65
2.2.7 Linux内存管理 67
2.2.8 Linux内存布局 68
2.2.9 操作系统的影响 69
2.3 NUMA和CPU组 70
2.4 本章小结 71
规则2 – 避免随机访问,拥抱循序访问 71
规则3 – 提高空间和时间数据局部性 72
规则4 – 不要放弃使用更高级技巧的可能性 72
第3章 内存测量 73
3.1 尽早测量 74
3.1.1 开销和侵入性 74
3.1.2 采样与跟踪 75
3.1.3 调用树 75
3.1.4 对象图 76
3.1.5 统计 77
3.1.6 延迟与吞吐量 79
3.1.7 内存转储、跟踪、实时调试 80
3.2 Windows环境 81
3.2.1 概述 81
3.2.2 VMMap 81
3.2.3 性能计数器 82
3.2.4 Windows事件跟踪 87
3.2.5 Windows性能工具包 95
3.2.6 PerfView 104
3.2.7 ProcDump, DebugDiag 111
3.2.8 WinDbg 112
3.2.9 反汇编程序和反编译程序 114
3.2.10 BenchmarkDotNet 114
3.2.11 商业工具 115
3.3 Linux环境 123
3.3.1 概况 123
3.3.2 Perfcollect 124
3.3.3 Trace Compass 126
3.3.4 内存转储 134
3.4 本章小结 135
规则5 – 尽早测量GC 137
第4章 .NET基础知识 139
4.1 .NET版本 139
4.2 .NET内部原理 141
4.3 程序集和应用程序域 148
4.4 进程内存区域 150
4.4.1 场景4-1:我的程序占用了多大内存 153
4.4.2 场景4-2:我的程序的内存使用率持续攀升(1) 155
4.4.3 场景4-3:我的程序的内存使用率持续攀升(2) 157
4.4.4 场景4-4:我的程序的内存使用率持续攀升(3) 158
4.5 类型系统 161
4.5.1 类型的分类 161
4.5.2 类型的存储 162
4.5.3 值类型 163
4.5.4 引用类型 169
4.6 字符串 173
4.6.1 字符串暂存 178
4.6.2 场景4-5:我的程序的内存使用率太高 182
4.7 装箱与拆箱 185
4.8 按引用传递 188
4.8.1 按引用传递值类型实例 188
4.8.2 按引用传递引用类型实例 189
4.9 类型数据局部性 190
4.10 静态数据 193
4.10.1 静态字段 193
4.10.2 静态数据揭秘 194
4.11 本章小结 197
规则6 – 测量你的程序 197
规则7 – 不要假设内存泄漏不存在 198
规则8 – 考虑使用结构 198
规则9 – 考虑使用字符串暂存 198
规则10 – 避免装箱 198
第5章 内存分区 201
5.1 分区策略 201
5.2 按大小分区 202
5.2.1 小对象堆 203
5.2.2 大对象堆 203
5.3 按生存期分区 207
5.3.1 场景5-1:我的程序健康吗?
实时的代大小 210
5.3.2 记忆集 214
5.3.3 卡表(Card Tables) 218
5.3.4 卡包(Card Bundles) 222
5.4 按物理分区 224
5.4.1 场景5-2:nopCommerce可能发生内存泄漏 229
5.4.2 场景5-3:大对象堆浪费了 236
5.4.3 段和堆解析 237
5.4.4 段重用 239
5.5 本章小结 241
规则11 – 监视代大小 241
规则12 – 避免不必要的堆引用 241
规则13 – 监视段使用情况 242
第6章 内存分配 243
6.1 内存分配简介 243
6.2 bump pointer分配 244
6.3 空闲列表分配 250
6.4 创建新对象 253
6.4.1 小对象堆分配 255
6.4.2 大对象堆分配 257
6.5 堆再平衡 260
6.6 OutOfMemoryException异常 262
场景6-1:OutOfMemoryException异常 263
6.7 堆栈分配 265
6.8 避免分配 266
6.8.1 显式的引用类型分配 267
6.8.2 隐式的分配 286
6.8.3 类库中的各种隐式分配 293
6.8.4 场景6-2:调查程序中的分配情况 297
6.8.5 场景6-3:Azure Functions 299
6.9 本章小结 300
规则14 – 在性能攸关的地方,
避免堆分配 300
规则15 – 避免过多的LOH分配 301
规则16 – 如果可行,在堆栈上分配 301
第7章 垃圾回收——简介 303
7.1 高层视图 303
7.2 GC过程的示例 304
7.3 GC过程的步骤 309
场景7-1:分析GC的使用情况 309
7.4 分析GC 313
7.5 垃圾回收性能调优数据 314
7.5.1 静态数据 314
7.5.2 动态数据 315
7.5.3 场景7-2:了解分配预算 317
7.6 回收触发器 325
7.6.1 分配触发器 326
7.6.2 显式触发器 326
7.6.3 场景7-3:分析显式GC调用 328
7.6.4 低内存级别系统触发器 333
7.6.5 各种内部触发器 333
7.7 EE挂起 334
场景7-4:分析GC挂起时间 335
7.8 要判决的代 336
场景7-5:被判决的代的分析 338
7.9 本章小结 339
第8章 垃圾回收——标记阶段 341
8.1 对象的遍历与标记 341
8.2 局部变量根 342
8.2.1 局部变量存储 343
8.2.2 堆栈根 343
8.2.3 词法作用域 343
8.2.4 存活堆栈根与词法作用域 344
8.2.5 带有激进式根回收的存活堆栈根 345
8.2.6 GC信息 350
8.2.7 固定局部变量 354
8.2.8 堆栈根扫描 356
8.3 终结根 357
8.4 GC内部根 357
8.5 GC句柄根 358
8.6 处理内存泄漏 363
8.6.1 场景8-1:nopCommerce可能
发生内存泄漏 365
8.6.2 场景8-2:找出最常出现的根 367
8.7 本章小结 369
第9章 垃圾回收——计划阶段 371
9.1 小对象堆 371
9.1.1 插头和间隙 371
9.1.2 场景9-1:具有无效结构的
内存转储 375
9.1.3 砖表 376
9.1.4 固定 377
9.1.5 场景9-2:调查固定 381
9.1.6 代边界 385
9.1.7 降级 385
9.2 大对象堆 389
9.3 压缩的决策 390
9.4 本章小结 391
第10章 垃圾回收——清除和压缩 393
10.1 清除阶段 393
10.1.1 小对象堆 393
10.1.2 大对象堆 394
10.2 压缩阶段 394
10.2.1 小对象堆 394
10.2.2 大对象堆 397
10.2.3 场景10-1:大对象堆的碎片化 398
10.3 本章小结 404
规则17 – 观察运行时挂起 405
规则18 – 避免“中年危机” 406
规则19 – 避免老的代和LOH碎片化 406
规则20 – 避免显式GC 407
规则21 – 避免内存泄漏 407
规则22 – 避免固定 407
第11章 GC风格 409
11.1 模式概述 409
11.1.1 工作站模式与服务器模式 409
11.1.2 非并发模式与并发模式 411
11.2 模式配置 411
11.2.1 .NET Framework 412
11.2.2 .NET Core 412
11.3 GC停顿和开销 413
11.4 模式描述 414
11.4.1 非并发工作站模式 415
11.4.2 并发工作站模式(4.0版本之前) 416
11.4.3 后台工作站模式 417
11.4.4 非并发服务器模式 423
11.4.5 后台服务器模式 425
11.5 延迟模式 426
11.5.1 批处理模式 426
11.5.2 交互式模式 426
11.5.3 低延迟模式 427
11.5.4 持续低延迟模式 427
11.5.5 无GC区域模式 429
11.5.6 延迟优化目标 430
11.6 选择GC风格 431
11.6.1 场景11-1:检查GC设置 432
11.6.2 场景11-2:对不同GC模式进行基准测试 433
11.7 本章小结 438
规则23 – 有意识地选择GC模式 439
规则24 – 记住延迟模式的相关知识 439
第12章 对象生存期 441
12.1 对象与资源的生命周期 441
12.2 终结 442
12.2.1 简介 443
12.2.2 激进式根回收问题 446
12.2.3 关键终结器 449
12.2.4 终结的内部实现 449
12.2.5 场景12-1:由于终结而导致的内存泄漏 455
12.2.6 复活(Resurrection) 460
12.3 Disposable对象 463
12.4 安全句柄 468
12.5 弱引用 473
12.5.1 缓存 477
12.5.2 弱事件模式 479
12.5.3 场景12-2:由于事件导致的内存泄漏 484
12.6 本章小结 486
规则25 – 避免终结器 486
规则26 – 首选显式清理 487
第13章 其他主题 489
13.1 依赖句柄 489
13.2 线程局部存储 494
13.2.1 线程静态字段 494
13.2.2 线程数据插槽 497
13.2.3 线程局部存储的内部 498
13.2.4 使用场景 503
13.3 托管指针 504
13.3.1 ref局部变量 505
13.3.2 ref返回值 505
13.3.3 只读ref变量和in参数 507
13.3.4 ref类型的内部 511
13.3.5 C#中的托管指针——ref变量 521
13.4 关于结构的更多知识 526
13.4.1 只读结构 526
13.4.2 ref结构(类似于托管指针的类型) 528
13.4.3 固定大小的缓冲区 529
13.5 对象/结构布局 533
13.6 非托管约束 541
13.7 本章小结 546
第14章 高级技巧 547
14.1 Span<T>和Memory<T> 547
14.1.1 Span<T> 547
14.1.2 Memory<T> 560
14.1.3 IMemoryOwner<T> 562
14.1.4 Memory<T>的内部实现 566
14.1.5 Span<T>和Memory<T>使用准则 568
14.2 Unsafe 568
14.3 面向数据设计 573
14.3.1 战术型设计 574
14.3.2 战略型设计 576
14.4 未来特性 585
14.4.1 可为空引用类型 585
14.4.2 Pipelines 590
14.5 本章小结 595
第15章 编程API 597
15.1 GC API 597
15.1.1 收集数据和统计 597
15.1.2 GC通知 604
15.1.3 控制非托管内存压力 606
15.1.4 显式回收 606
15.1.5 无GC区域 606
15.1.6 终结(Finalization)管理 606
15.1.7 内存使用率 607
15.1.8 GC类中的内部调用 608
15.2 CLR Hosting 609
15.3 ClrMD 616
15.4 TraceEvent库 621
15.5 自定义GC 623
15.6 本章小结 626