异常处理与内存管理
本章聚焦 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 模式 |
面试问答卡片
📌 问题 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 |
| 核心难点 |
|
| 面试高频追问 |
|
| 可迁移回答话术 | “我采用三层内存治理方案:第一层通过连接池复用 and Dispose 包装减少 GC 压力;第二层将大对象缓存改为弱引用,允许 GC 按需回收;第三层通过 Prometheus 监控 GC 频率和 LOH 大小,设置告警水位。最终 FullGC 频率降低 80%,P99 延迟从 500ms 降至 50ms。” 💡 迁移要点:任何涉及连接池、大对象缓存、资源释放的服务均可套用此三层框架,先复现问题 → 分层治理 → 指标验证。 |
📖 复习提示:异常处理与内存管理是 .NET 面试的“底层护城河”。建议结合实战项目(如网关、中间件)来讲述 try-catch-finally 和 Dispose 的应用,用 GC 代龄数据(代龄分配量、FullGC 频率)证明你的优化效果,比单纯背概念更有说服力。