C盘红了但WSL里明明天天删文件?虚拟磁盘"只增不减"的坑,一次清出150G

Windows C盘占用652G,但WSL里df一看明明只用了135G——多出来的500多G去哪了?排查发现是WSL2和Docker Desktop的vhdx虚拟磁盘只增不减:删文件只是内部标记,文件本身不缩。本文记录整个排查过程,以及用diskpart compact压缩vhdx、一次回收150G的完整做法。

一个对不上的账

前几天做例行磁盘体检,发现一个对不上的账:

  • Windows 这边看 C 盘:949G 的盘,已用 652G,红了。
  • 但在 WSL(Ubuntu)里跑 df -h /:只用了 135G

中间差着 500 多 G。按常识,我在 Linux 里删一个文件,Windows 上的占用就该跟着降——我前一晚刚删了 62G 的视频产物,理论上 C 盘应该松一大截。可它纹丝不动。

这账对不上,就值得查。

排查:钱到底进了哪个口袋

我没有上来就删东西,而是先一层层把空间分布摸清楚。C 盘太大(600多G),du 全盘扫太慢,改用 PowerShell 直接问 Windows,按目录大小排序:

1
2
3
4
5
Get-ChildItem 'C:\' -Directory -Force | ForEach-Object {
  $s = (Get-ChildItem $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
        Measure-Object Length -Sum).Sum
  [PSCustomObject]@{ GB=[math]::Round($s/1GB,2); Name=$_.Name }
} | Sort-Object GB -Descending

结果一目了然:

1
2
3
560.90 GB  Users          <- 几乎全在这
 35.45 GB  Program Files
 30.93 GB  Windows

往下钻 C:\Users\<我>\AppData\Local:

1
2
264.72 GB  wsl            <- WSL 的虚拟磁盘
108.40 GB  Docker         <- Docker Desktop 的虚拟磁盘

再看到具体文件:

1
2
C:\Users\li\AppData\Local\wsl\{...}\ext4.vhdx                  264.7 GB
C:\Users\li\AppData\Local\Docker\wsl\disk\docker_data.vhdx     108.3 GB

两个文件加起来 373G。元凶找到了。

根因:vhdx 是"只增不减"的

这两个 .vhdx 是 WSL2 和 Docker Desktop 的虚拟硬盘文件。你整个 Linux 系统、所有 Docker 镜像,最终都存在这两个文件里。

关键在于它的增长策略:只增不减

打个比方——它像一个不断往里塞东西的衣柜:

  • 你往 Linux 里写文件,Windows 就按需把 vhdx 文件撑大一点,腾出"格子"。
  • 你在 Linux 里 rm 删掉文件,只是在衣柜里把那个格子标记为"空",衣柜本身(vhdx 文件)一点都不缩。

所以会出现这种魔幻现象:我在 Linux 里越删越狠,C 盘越涨越凶。vhdx 内部其实已经有 130G 是"空洞"了,但 Windows 看到的还是那个 264G 的大家伙。

Docker 那个也一样——我刚 docker system prune 清了 20多G 镜像和构建缓存,docker_data.vhdx 还是 108G。

解决:手动 compact 把空洞挤掉

既然不会自动缩,就手动压缩。思路是把 vhdx 以只读方式挂载,再用 diskpartcompact vdisk 把内部的空洞物理挤掉。

全程在 Windows 端操作,因为得先 wsl --shutdown 关掉 WSL——总不能一边踩着刹车一边让车自己跑。

第一步:关掉 WSL 和 Docker

在 Windows 里右键开始菜单 → 终端(管理员) / PowerShell(管理员):

1
2
3
wsl --shutdown
# 确保 Docker Desktop 没在占用它的 vhdx
Get-Process "com.docker*" -ErrorAction SilentlyContinue | Stop-Process -Force

第二步:diskpart 压缩 vhdx

每个 vhdx 文件跑一遍(把路径换成你自己的):

1
diskpart

进入 DISKPART> 提示符后:

1
2
3
4
5
select vdisk file="C:\Users\li\AppData\Local\wsl\{你的GUID}\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit

compact vdisk 会跑几分钟(取决于文件多大、有多少空洞),进度条走完就收工。

Docker 那个同理:

1
2
3
4
5
select vdisk file="C:\Users\li\AppData\Local\Docker\wsl\disk\docker_data.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit

找不到自己的 vhdx 在哪?微软官方给了一条 PowerShell 命令,替换成你的发行版名(比如 Ubuntu)即可:

1
2
(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss |
  Where-Object { $_.GetValue("DistributionName") -eq 'Ubuntu' }).GetValue("BasePath") + "\ext4.vhdx"

实际效果

我这台机器:

文件压缩前压缩后释放
WSL ext4.vhdx264.7G~140G~124G
Docker docker_data.vhdx108.3G~80G~28G

C 盘可用空间从 298G 直接回到 440G 左右,一下多出 150G。整个过程不动任何 Linux 文件,只是把"空洞"物理挤掉,零风险。

顺手清掉的几类"真空垃圾"

排查途中还发现了几处真能删的,一并记录:

  • Docker 三件套:docker container prunedocker image prune -adocker builder prune —— 我这台清出 20多G(悬空镜像 + 构建缓存)。注意 image prune -a 会删掉没在跑的镜像,先确认没有要留的。
  • VS Code 插件更新残留:.vscode\extensions 下堆了 27 个 .xxxxxxxx-xxxx 这种 GUID 命名的隐藏目录,是 Codex / Claude Code 插件每次更新留下的旧版本尸体,每个塞一个 100-335M 的 codex.exe/claude.exe。清掉 6.9G,正在用的插件不受影响。
  • 各类包管理器缓存:~/.npm~/.bun/install/cache~/.cache、pip/pnpm store —— 删了会自动重建,放心清。

几点提醒

  1. vhdx 压缩是安全的,但操作前确认 WSL 里没有正在编辑未保存的内容(wsl --shutdown 会断开所有终端)。
  2. 如果压缩效果不理想,先在 WSL 里跑一次 sudo fstrim -av(把文件系统的空闲块标记出来),再回 Windows 执行 compact,回收会更彻底。
  3. 想一劳永逸:WSL 较新版本支持在 .wslconfig 里给 vhdx 设上限,以及开启稀疏 vhdx(sparseVhd=true)让文件按需增长、回落更积极。具体可参考微软官方的 WSL 磁盘管理文档
  4. 别用 Windows 的资源管理器/编辑器直接去改 AppData 里的 WSL 文件,容易把发行版搞坏。要从 Windows 访问 Linux 文件,走 \\wsl$\<发行版名> 这个共享路径。

结语

这次的账其实很简单:C 盘红不是因为我东西多,而是因为两个虚拟磁盘只吃不拉。删文件在 Linux 里是"逻辑删除",落到 Windows 的 vhdx 上就成了"只标记不回收"。

碰到"C盘满了但不知道被谁占了"的情况,别急着格式化重装——先把空间一层层排出来,八成是某个只增不减的容器文件在悄悄膨胀。找到它,compact 一下,空间就回来了。