异常处理与内存管理

本章聚焦 C# 服务端面试中最高频的两大底层能力:异常处理机制(try-catch-finally 执行语义、异常过滤、自定义异常)与内存管理(GC 代龄回收、Dispose 模式、弱引用)。掌握这些知识点能体现候选人对资源管控和系统稳定性的理解深度。

📌 学习目标:能清晰阐述 try-catch-finally 执行路径与 finally 必须性;能说明 GC 代龄策略与弱引用适用场景;能正确实现 Dispose 模式并对比弱引用使用边界。

知识结构总览

主题 核心机制 关键原则 面试常考点
异常处理 try → catch → finally 精确捕获、资源清理、异常过滤 finally 执行条件、when 子句、异常吞没
GC 回收 代龄机制(0/1/2) 自动回收、晋升策略、STW 停顿 FullGC 触发、LOH 碎片、弱引用
Dispose 模式 IDisposable 接口 确定性资源释放、using 语法 Dispose(bool) 设计、防止双重释放
弱引用 WeakReference 允许 GC 回收、缓存优化 与强引用对比、WeakEvent 模式
异常处理回答框架 try 块 执行代码 catch 块 捕获处理 finally 块 清理资源 异常过滤(when) 自定义异常 主路径:顺序执行 → 异常跳转 → 确保清理 | 加分点:精确过滤 + 语义封装 内存管理三大机制 GC 自动回收 Dispose 模式 弱引用 补充 利用 代龄机制 / LOH 非托管资源释放 缓存 / 避免泄漏 GC 是基础回收机制;Dispose 确定性释放非托管资源;弱引用利用 GC 特性优化缓存

面试问答卡片

📌 问题 1:请解释 try-catch-finally 的执行顺序及 finally 的必要性

面试官考察点是否理解异常处理的核心语义;是否知道 finally 在资源清理中的不可替代性;是否了解异常过滤的用法。
回答框架执行顺序 → finally 必要性(return / 异常时均执行) → 异常过滤(when 子句) → 最佳实践(避免空 catch / 自定义异常)
示例回答try 包含可能抛异常的代码,catch 按异常类型捕获并用 when 精确过滤,finally 无论是否发生异常均执行以确保资源清理。即使 try 中有 return,finally 也会在返回前执行。避免空 catch 或用异常控制流程,自定义异常应继承 Exception 保留 InnerException。
可能追问finally 块不执行的唯一场景是什么?(Environment.FailFast / 线程被暴力终止) | 异常过滤对性能有何影响?(额外评估开销) | 如何设计自定义异常结构?
避坑提醒⚠️ 不要在 finally 中抛出异常(会覆盖原始异常)
⚠️ 不要使用空 catch 块吞没异常

📌 问题 2:请说明 .NET GC 的工作原理,代龄机制如何优化回收性能?

面试官考察点是否理解代龄回收策略;是否知道 GC 触发条件与 STW 影响;是否了解 LOH 与弱引用的作用。
回答框架代龄划分(0/1/2) → 晋升策略 → GC 触发条件 → LOH 管理 → 弱引用协同
示例回答GC 将托管堆分为 0/1/2 代,新对象分配在 0 代,存活后逐代晋升。GC 优先回收 0 代减少全量停顿。大对象直接进入 LOH(第 2 代),需警惕碎片和 FullGC。弱引用不阻止 GC 回收,常用于大对象缓存。优化方向:降低分配率、减少 LOH 碎片。
可能追问FullGC 在哪些场景下触发?(LOH 分配 / 第 2 代压力 / GC.Collect 强制) | 如何监控 GC 性能?(GC.GetTotalMemory / ETW 事件) | GC 模式 (Workstation / Server) 的区别?
避坑提醒⚠️ 弱引用不保证对象一定存活(可能随时被回收)
⚠️ 避免频繁分配大对象导致 LOH 碎片

📌 问题 3:Dispose 模式与弱引用分别解决什么问题?如何选择?

面试官考察点是否理解确定性资源释放与自动回收的边界;是否掌握 IDisposable 正确实现;是否知道弱引用的典型场景。
回答框架Dispose 模式(非托管资源) → 弱引用(缓存优化) → 对比选择 → 实现细节
示例回答Dispose 模式通过 IDisposable 接口配合 using 语句确保非托管资源(文件句柄、连接池)及时释放。弱引用 WeakReference 允许引用对象但不阻止 GC 回收,适用于大对象缓存和 WeakEvent 模式避免泄漏。选择依据:需要确定性释放用 Dispose,可容忍重建用弱引用。
可能追问Dispose(bool) 中 true 和 false 分别处理什么?(托管资源 vs 非托管资源) | 弱引用是否线程安全?(WeakReference 本身线程安全,但目标对象不保证) | 如何防止 Dispose 被重复调用?(设置标志位)
避坑提醒⚠️ 忘记调用 Dispose 会导致资源泄漏(始终使用 using 包裹)
⚠️ 弱引用不适合高频访问的小对象(重建成本低,引用管理有开销)

项目案例实战

🔧 亿级 API 网关内存治理

项目名称高并发 API 网关内存优化与资源管控
业务背景公司核心网关日请求量过亿,FullGC 频繁导致 P99 延迟从 50ms 飙升到 500ms+,影响线上交易链路。
规模指标日请求量 1.2 亿|峰值 QPS 3.5 万|网关节点 16 台|内存配置 16GB/节点
技术栈.NET 6 + ASP.NET Core + Kestrel + Redis + Prometheus + Grafana
核心难点
  • 连接池对象未显式释放导致 GC 无法回收
  • 请求体反序列化频繁分配大对象造成 LOH 碎片
  • 缓存使用强引用引发内存泄漏,FullGC 无法缓解
面试高频追问
  • 你是如何定位到连接池泄漏的?(通过 dump 分析 + !GCHeapStat 查看代龄分布)
  • FullGC 触发条件在项目中具体是哪个?(LOH 分配触发第 2 代回收)
  • Dispose 模式在网关中如何落地的?(使用 using 包装 HttpClientFactory 和 RedisConnection)
  • 弱引用缓存如何避免缓存风暴?(结合 LRU + 弱引用双重保护)
可迁移回答话术
“我采用三层内存治理方案:第一层通过连接池复用 and Dispose 包装减少 GC 压力;第二层将大对象缓存改为弱引用,允许 GC 按需回收;第三层通过 Prometheus 监控 GC 频率和 LOH 大小,设置告警水位。最终 FullGC 频率降低 80%,P99 延迟从 500ms 降至 50ms。”

💡 迁移要点:任何涉及连接池、大对象缓存、资源释放的服务均可套用此三层框架,先复现问题 → 分层治理 → 指标验证。
📖 复习提示:异常处理与内存管理是 .NET 面试的“底层护城河”。建议结合实战项目(如网关、中间件)来讲述 try-catch-finally 和 Dispose 的应用,用 GC 代龄数据(代龄分配量、FullGC 频率)证明你的优化效果,比单纯背概念更有说服力。