内存泄漏排查
问题
Rust 不是内存安全的吗?为什么还会有内存泄漏?
答案
Rust 保证内存安全(无悬垂指针、无 use-after-free),但不保证没有内存泄漏。mem::forget 是 safe 的,Arc 循环引用也是合法的。
常见泄漏场景
1. Arc 循环引用
use std::sync::Arc;
use std::sync::Mutex;
struct Node {
next: Option<Arc<Mutex<Node>>>,
}
// 循环引用:A → B → A,引用计数永远不会降到 0
let a = Arc::new(Mutex::new(Node { next: None }));
let b = Arc::new(Mutex::new(Node { next: Some(a.clone()) }));
a.lock().unwrap().next = Some(b.clone());
// a 和 b 的引用计数都是 2,drop 后变 1,永远不会释放
// ✅ 修复:使用 Weak 打破循环
use std::sync::Weak;
struct Node {
next: Option<Arc<Mutex<Node>>>,
parent: Option<Weak<Mutex<Node>>>, // Weak 不增加引用计数
}
2. 未关闭的 Channel
// 发送端未 drop,接收端永远在 recv → goroutine/task 泄漏
let (tx, mut rx) = tokio::sync::mpsc::channel::<i32>(100);
tokio::spawn(async move {
while let Some(val) = rx.recv().await {
// tx 不 drop,这个 task 永远不会结束
}
});
// ✅ 修复:确保 tx 在不需要时被 drop
3. 忘记释放大缓冲区
// Vec 只会缩小 len,不会自动缩小 capacity
let mut buf = Vec::with_capacity(1_000_000);
buf.extend_from_slice(&[1u8; 1_000_000]);
buf.clear(); // len=0, capacity=1_000_000,内存未释放
// ✅ 修复
buf.shrink_to_fit(); // 释放多余容量
// 或 drop(buf);
排查工具
| 工具 | 用途 |
|---|---|
valgrind | 检测内存泄漏 |
heaptrack | 内存分配追踪 |
DHAT (dhat crate) | Rust 友好的堆分析 |
tokio-console | 检测 task 泄漏 |
# Valgrind 检测
valgrind --leak-check=full ./target/debug/myapp
# DHAT 嵌入式分析
# 在代码中添加 dhat::Profiler,运行后生成报告
常见面试问题
Q1: Rust 的内存安全和内存泄漏是什么关系?
答案:
- 内存安全:不会访问无效内存(no UB) — Rust 保证
- 内存泄漏:分配的内存不再使用但未释放 — Rust 不保证
std::mem::forget 是 safe 的,因为泄漏不会造成未定义行为。这是 Rust 的设计选择:Rc/Arc 循环引用是安全的(不会崩溃),只是浪费内存。