跳到主要内容

内存泄漏排查

问题

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 循环引用是安全的(不会崩溃),只是浪费内存。

相关链接