C# 问答 04:CLR、GC、内存与性能
061 托管堆分代的依据是什么?
难度: 基础
查看参考答案
结论: 多数对象短命,因此年轻代更频繁回收
原因: 存活对象晋升,代数不是重要程度
边界: 长期缓存会增加第 2 代扫描和保留内存
062 LOH 为什么值得关注?
难度: 基础
查看参考答案
结论: 大对象分配和回收成本高且可能碎片化
原因: 阈值是运行时实现边界,不应硬编码业务逻辑
边界: 复用大缓冲区和流式处理可减少压力但需所有权管理
063 终结器为何昂贵?
难度: 基础
查看参考答案
结论: 对象需进入终结队列并延长至少一个回收阶段
原因: 终结器时机不确定且线程受限
边界: 优先 SafeHandle 和 IDisposable,Dispose 后 SuppressFinalize
064 IDisposable 的正确模式是什么?
难度: 基础
查看参考答案
结论: 确定释放自身拥有的资源,允许重复调用并阻止后续使用
原因: 不拥有的依赖不能随意 Dispose
边界: 异步释放仅在释放本身需要 await 时实现 IAsyncDisposable
065 对象池何时值得使用?
难度: 基础
查看参考答案
结论: 对象创建昂贵、频率高且能可靠重置时
原因: 池会延长对象存活并增加泄漏和污染风险
边界: 先分析分配热点,再设容量和清理策略
066 stackalloc 的风险是什么?
难度: 基础
查看参考答案
结论: 分配来自有限线程栈,过大或循环使用可导致栈溢出
原因: 长度应有严格上限,较大缓冲走池或堆
边界: Span 只改善访问安全,不会扩大栈容量
067 pinning 为什么会影响 GC?
难度: 进阶
查看参考答案
结论: 固定对象不能移动,会妨碍堆压缩并产生碎片
原因: 短期固定小范围通常可接受,长期固定考虑固定对象堆或原生内存
边界: fixed 结束后指针立即失效,不能逃逸
068 Span 的性能价值是什么?
难度: 进阶
查看参考答案
结论: 提供无复制切片和安全连续内存访问
原因: 它不自动让算法更快,边界检查和底层生命周期仍需理解
边界: 需要字符串或长期存储时仍会分配
069 Memory 与 Span 如何分工?
难度: 进阶
查看参考答案
结论: Memory 可跨异步和存入堆,Span 用于同步访问窗口
原因: Memory.Span 不能跨 await 保存
边界: 所有权通常由数组、内存池或 MemoryManager 另行表达
070 如何测托管分配?
难度: 进阶
查看参考答案
结论: 使用 BenchmarkDotNet 内存诊断、dotnet-counters 或分配分析器
原因: 预热、Release、输入规模和线程都影响数据
边界: 单次 GC.CollectionCount 不能定位具体分配来源
071 为什么先测量再优化?
难度: 进阶
查看参考答案
结论: 性能瓶颈常不在直觉位置,优化会增加复杂度
原因: 先定义吞吐、延迟和内存目标,再用分析器定位
边界: 微基准结果还需回到端到端负载验证
072 闭包如何产生分配?
难度: 进阶
查看参考答案
结论: 捕获变量提升到共享闭包对象,委托引用该对象
原因: 无捕获 static Lambda 可防止捕获,但委托本身仍有生命周期
边界: 循环捕获还要理解变量作用域,不能靠猜测值复制
073 反射的主要成本在哪里?
难度: 进阶
查看参考答案
结论: 成员查找、Invoke 参数处理、装箱和异常包装
原因: 缓存 MethodInfo 或委托可摊销查找
边界: AOT 和裁剪还需显式保留动态访问成员
074 异常为何不适合普通分支?
难度: 进阶
查看参考答案
结论: 抛出需要构造对象和采集栈,控制流展开成本高
原因: 不可恢复失败仍应使用异常表达
边界: 输入校验优先 Try 模式,不能因此吞掉真正异常
075 结构体越大越慢吗?
难度: 实战
查看参考答案
结论: 不是绝对,但复制成本随数据增加且防御性复制可能隐藏
原因: JIT、传参方式和寄存器布局都会影响
边界: 按语义建模后用 readonly、in 和基准优化
076 字符串拼接怎样选工具?
难度: 实战
查看参考答案
结论: 少量固定拼接用插值,循环大量拼接用 StringBuilder,格式化热点考虑插值处理器
原因: 预估容量可减少扩容
边界: 不要为两段文本机械创建 StringBuilder
077 ArrayPool 有何安全问题?
难度: 实战
查看参考答案
结论: 租借数组可能包含旧数据,归还后其他代码可复用
原因: 敏感数据应清零并确保异步操作完成后归还
边界: try/finally 防止异常泄漏租借缓冲
078 GC.Collect 为什么通常不应手工调用?
难度: 实战
查看参考答案
结论: 运行时更了解堆和分配速率,强制完整回收会造成暂停
原因: 极少数可证明阶段边界可测量使用
边界: 不能用 Collect 修复未释放句柄或无限缓存
079 NativeAOT 对代码设计有什么影响?
难度: 实战
查看参考答案
结论: 启动和体积受益,但动态代码生成与未注解反射受限制
原因: 源生成序列化和静态注册更友好
边界: 必须在 AOT 发布配置实际测试,普通 JIT 成功不够
080 性能评审应输出什么?
难度: 实战
查看参考答案
结论: 可复现基准、环境、输入、指标、剖析证据和复杂度取舍
原因: 只报百分比会隐藏基线和误差
边界: 优化后还需回归正确性、并发和内存上限