先讲一个几乎每个虚拟化老用户都会遇到的场面:宿主机C盘从“还剩60G”变成“只剩2G”,翻遍软件安装记录也没装几个大东西。最后顺着磁盘空间一轮轮找下去,在虚拟机目录里发现了一个Win10的vmdk文件,竟然占了78G,而进虚拟机里查一下,C盘实际已用空间只有32G。多出来的40多G去哪了?删掉的电影、安装包、卸载的软件,为什么没有把空间还给宿主机?这就是虚拟磁盘“只吃不吐”的问题。
这篇就把这件事彻底掰开揉碎地讲清楚。我会从虚拟磁盘的底层逻辑、空间被谁吃了、怎么安全地把空间“挤”回来,一直到需要扩容、甚至想彻底让虚拟机瘦身时该怎么操作,全都过一遍。适合个人电脑上用VMware Workstation、VirtualBox跑Windows/Linux虚拟机,经常被“虚拟机目录越来越大”逼疯的朋友。不需要你是运维大牛,只要跟着步骤操作,大概率能把丢失的几十G找回来。
1. 虚拟磁盘为什么总在膨胀?三类空间黑洞先搞清楚
1.1 动态分配不是“自动回收”,它更像只进不出的海绵
大多数人创建虚拟机时,默认选择的是“动态分配磁盘空间”,术语叫Thin Provision(精简置备)。这种模式的好处是创建时虚拟磁盘文件非常小,只有几十MB,随着虚拟机内部写入数据,文件才逐步变大。但是它的回收逻辑和很多人的直觉完全相反:虚拟机里删掉文件,宿主机上的那个vmdk/vdi文件并不会自动缩小。
这里可以把它想象成一块海绵。你往海绵里倒水,它会扩张;但你把海绵里的水挤掉一部分,它的体积不会立刻自动缩回原样,除非你主动去“拧干”。虚拟磁盘也一样,客户机(虚拟机里的系统)删除文件时,虚拟磁盘控制器看到的是“这段区域可以被覆盖”,但没有得到指令去把这段空间“缩回去”。所以文件体积只会单方向增长,直到你显式执行压缩。
1.2 已删除文件所占用的块没有被清零,是空间无法回收的根因
这是很多人理解不了的一点:明明在虚拟机里用diskgenius或者优化软件反复清理了“垃圾文件”,宿主机上的vmdk文件大小纹丝不动。原因很简单,虚拟磁盘由大量固定大小的数据块组成,客户机删除文件时,只修改了文件系统中的“目录项”和“位图”,标记这些块为可用,但块里面仍然保存着旧数据。
对虚拟机监控程序来说,它只看到这些块还有内容,不能被当成“空白”处理。所以你必须在客户机内做一次“空白空间清零”,也就是把空闲区域用零填充,虚拟磁盘才知道这块地方可以腾出来了。这一步是整个压缩流程里最关键的铺垫,后面实操部分我会具体演示。
1.3 快照链、日志缓存、系统还原点:三个你看不见的怪物
除了文件系统本身的碎片化问题,还有三个典型的“空间怪兽”。
第一是快照。VMware和VirtualBox里一旦创建快照,原始虚拟磁盘会变成只读基线,之后的所有新数据都写入差值文件(VMware里是类似Windows 7-s001.vmdk这样的子盘)。如果你在快照前删过大量文件,快照里仍然保留着被删文件的历史数据,等于白占空间。更麻烦的是,多个快照之间相互关联,删掉其中一层时其他层的数据会被合并,合并过程中临时文件可能比实物盘还大,一不小心就会把宿主机硬盘挤爆。
第二是日志和临时文件。Linux里的/var/log/journal、/tmp,Windows里的C:\Windows\Temp、浏览器缓存,长期不清理会以GB级别增长。特别是Windows的WinSxS组件库,系统更新后旧的组件版本不会自动清理,动不动就是10~20G,在虚拟机里尤其夸张。
第三是休眠文件和系统还原点。Windows虚拟机一旦启用了休眠,C:\hiberfil.sys会占物理内存大小的一半;系统保护里的还原点、卷影副本,也会吃掉大量空间。这些文件不会因为“磁盘清理”被全部释放,需要针对性处理。
2. 先查再动手,三步定位空间被谁吃了
在动手清理之前,必须把问题定位清楚。如果一上来就盲目执行压缩命令,大概率会遇到报错或者白等几个小时。我的习惯是分三步走:先看宿主机侧的虚拟磁盘文件,再看客户机内部的真实占用,最后检查快照链。
2.1 宿主机侧:分清“配置大小”和“实际大小”
在VMware里右键虚拟机 -> 设置 -> 硬盘,能看到“最大大小”,那只是虚拟硬盘的上限,不代表宿主机的实际占用。要看真实占用,最简单的方法是直接到虚拟机目录里看文件大小。
Linux宿主机用:
du -sh "虚拟机目录/" ls -lah "虚拟机目录/" | grep vmdkWindows宿主机就在虚拟机文件夹里右键查看大小,或者用WizTree这类工具扫一下。如果看到-s001.vmdk、-s002.vmdk之类的多个文件,说明虚拟磁盘被分割成了多个2GB的块,要汇总计算,不能只看其中一个。
这里有个很常见的误区:vmdk文件大小远大于客户机已用空间是正常的(因为没有清零回收),但如果你发现vmdk已经达到“最大大小”,而且客户机内部明明用了不到一半,那就要重点关心快照或者长期没有压缩。
2.2 客户机侧:用系统自带命令找出“内部胖子”
Linux虚拟机里,最常用的是:
df -h sudo du -sh /var /usr /tmp /home /root 2>/dev/null | sort -rh journalctl --disk-usagedf -h看整体使用率,du进一步定位大目录,journalctl --disk-usage检查日志占了多少。很多Ubuntu虚拟机日志都预留着好几百MB,加上apt缓存,轻松超过1G。
Windows虚拟机里,可以打开“设置 -> 系统 -> 存储”看整体占用,再用“磁盘清理”里“清理系统文件”查看Windows更新清理能释放多少。命令行角度,可以用PowerShell跑一下各目录大小,但简单起见,更推荐直接在资源管理器的“此电脑”里看C盘,然后逐个盘符看占用。
2.3 快照管理器:最容易被忽略的“隐形磁盘”
这一步一定不能跳过。VMware Workstation里点击“虚拟机 -> 快照 -> 快照管理器”,VirtualBox里是“备份”界面。如果看到有多个快照,而且时间跨度还很长时间,那你磁盘空间的很大一部分其实在快照里。
以下情况都说明快照在吞空间:
- 创建快照之后,你在客户机里删过很多文件;
- 快照数量多于1个,并且每个都保留了很久;
- 快照管理器里显示的子盘大小比实际原始盘还大。
处理顺序必须是:先做客户机清理(把垃圾清掉并清零空闲块),再删除快照。因为删除快照时各个快照的数据会向前合并,如果快照里全是垃圾数据,合并后的原始盘还是会保留这些垃圾。先清理再删快照,才能让合并结果干净。
3. 客户机清垃圾 + 空白块归零 + 宿主机压缩,三位一体的标准流程
现在进入正题。不论你用的是VMware还是VirtualBox,完整流程都是三板斧:客户机内部清理 -> 客户机将空白区域清零 -> 宿主机执行压缩。缺少任何一步,压缩效果都会大打折扣,甚至无效。我分别按Linux和Windows客户机来写。
3.1 Linux客户机瘦身:不只是删文件,还要让零块标记出来
以Ubuntu/Debian系列为例,先做常规清理:
sudo apt clean sudo apt autoremove sudo journalctl --vacuum-size=100M sudo rm -rf /tmp/* sudo rm -rf /var/tmp/*说明一下:
apt clean清的是下载缓存,通常能释放几百MB;journalctl --vacuum-size把系统日志限制在100MB以内,比全部清空更稳健;/tmp和/var/tmp删掉的内容都是临时文件,虚拟机重启后其实也用不着。
清理完以后,需要把剩余的空闲空间全部填充为0。关掉所有不必要的应用,然后执行:
sudo dd if=/dev/zero of=/zero.fill bs=1M sudo sync sudo rm -rf /zero.fill这条命令会向磁盘连续写入全零数据,直到磁盘写满为止。之后delete掉这个填充文件,文件系统会把这些块标记为空闲,但底层数据已经被零覆盖,虚拟磁盘就能“看见”哪些块可以压缩了。这一步会短暂占满客户机磁盘,务必确认剩余空间足够,而且不要中断。如果虚拟机有swap分区,建议执行前先swapoff -a,否则填充过程可能不会走到所有空间。
CentOS/RHEL一类系统也差不多,只是包管理器命令换成yum clean all或dnf clean all。填充零的方式完全一样。
3.2 Windows客户机瘦身:休眠文件、WinSxS、sdelete一个都别漏
Windows客户机的思路类似,但比Linux多几个特定项目。
首先打开“磁盘清理”,点“清理系统文件”,勾选Windows更新清理、临时文件、缩略图、DirectX着色器缓存等,执行清理。然后管理员身份打开CMD或PowerShell,依次执行:
powercfg /h off这一步关闭休眠并删除hiberfil.sys,如果你的虚拟机没打算用“快速启动”功能,可以安全执行。如果长期不用系统还原,还可以右键“此电脑 -> 属性 -> 系统保护 -> 配置 -> 禁用保护并删除还原点”,能释放几个GB到十几GB的卷影副本空间。
接下来压缩WinSxS组件库:
DISM /Online /Cleanup-Image /StartComponentCleanup这条命令会清理Windows更新留下的旧版本组件,执行时间较长,耐心等它完成。
最后,用微软Sysinternals套件里的sdelete把空闲空间清零:
sdelete -z C:需要管理员权限。它会遍历C盘所有空闲块并写入零值,效果和Linux的dd填充一样。如果不想下载sdelete,也可以直接在计算机管理里把休眠关掉,再用碎片整理工具,但sdelete -z是公认最稳的做法。
3.3 VMware宿主机压缩:图形界面与命令行双保险
客户机填充完零并关机后,就可以在宿主机上压缩了。VMware Workstation最朴素的方式:
- 先在本机关闭虚拟机(不是挂起);
- 菜单栏点“虚拟机 -> 设置 -> 硬件 -> 硬盘 -> 实用工具 -> 压缩”;
- 等待进度条走完。
但这个图形操作有时候会失败,尤其是虚拟机存在多块硬盘或磁盘路径有中文时。更通用的是命令行方式。找到VMware的安装目录,Windows一般是C:\Program Files (x86)\VMware\VMware Workstation\vmware-vdiskmanager.exe(或者VMware Workstation Pro的路径),然后:
"C:\Program Files (x86)\VMware\VMware Workstation\vmware-vdiskmanager.exe" -k "D:\VMs\Win10\Windows 10.vmdk"-k就是压缩。必须保证该虚拟机没有快照,否则会报错或只压缩当前子盘。如果你有快照,请回到客户机清理+清零,再删除快照,最后再执行这条命令。
有一点需要提醒:压缩过程非常消耗IO和CPU,宿主机磁盘剩余空间最好大于虚拟磁盘实际大小的一倍以上。因为压缩时会生成临时文件,空间不够会卡在一半,异常中断还可能损坏vmdk。我一般会在压缩之前先看一下宿主机剩余空间,不够就先清理宿主机垃圾或移动其他文件。
3.4 VirtualBox用户对应的压缩命令
VirtualBox的压缩概念类似,但命令语法不同。清理客户机并关机后,先找到虚拟磁盘文件路径,然后执行:
VBoxManage modifyhd "/path/to/disk.vdi" --compact如果你用的是VirtualBox 7.x新版本,这个命令依然有效。它同样要求客户机空闲区域已经清零,否则不会有效果。如果虚拟磁盘是VMDK格式但没有被VirtualBox锁定,也是同理。
实际踩坑中发现,VirtualBox压缩对快照的处理更“暴力”,有快照时modifyhd也会报错“cannot be compacted because there are snapshots”。所以VirtualBox用户更要重视先删除快照再压缩。
4. 压缩完还是不够?扩容、瘦身与“重装派”的三种破局方案
有时候压缩只能挤回一部分空间,但虚拟磁盘的“最大大小”已经规划得很保守,或者客户机系统本身已经臃肿到清理不动了。这时候要考虑另外三条路。
4.1 扩容:先扩VMDK,再进客户机扩展分区
如果你的虚拟机磁盘最大大小就是40G,而且已经用完,那压缩解决不了问题,只有扩容才是正解。VMware Workstation里操作很简单:关闭虚拟机 -> 设置 -> 硬件 -> 硬盘 -> 实用工具 -> 扩展 -> 输入新的最大大小。也可以命令行:
vmware-vdiskmanager.exe -x 80G "D:\VMs\Win10\Windows 10.vmdk"-x参数是扩展。注意:这只是在宿主机侧扩大了虚拟磁盘的容量上限,客户机内部的分区表还是原来的大小,需要再进系统去扩容分区。
Linux客户机,假设是单分区/dev/sda1,使用了ext4,可以安装并使用growpart:
sudo growpart /dev/sda 1 sudo resize2fs /dev/sda1执行完df -h就能看到根分区变大了。如果是LVM,那要复杂一些:
sudo pvresize /dev/sda2 sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lvWindows客户机更简单,打开“磁盘管理”(diskmgmt.msc),找到刚才扩大的磁盘,右键后面的未分配空间,选择“扩展卷”,一路下一步就行。
4.2 换格式不吃亏:用“复制转换”让虚拟磁盘彻底减脂
如果你需要的是“缩水”而不是“扩大”,还有一种很干净的办法:把虚拟磁盘转换复制一份,新副本只保留当前数据块。VMware里可以用:
vmware-vdiskmanager.exe -r "old.vmdk" -t 0 "new.vmdk"-r是转换复制,-t 0表示生成“动态增长的单一文件VMDK”。转换完成后,新vmdk会重新分配,只包含实际有数据内容的块。很多情况下,你会惊奇地发现80G的旧盘转换后变成了35G,效果比-k压缩还要狠。然后把旧的vmdk文件备份或改名,再用新的vmdk替换到虚拟机配置里。
VirtualBox中类似的命令是:
VBoxManage clonemedium "old.vdi" "new.vdi" --variant Standard克隆出来的新盘一般比原盘紧凑得多。在VMware里,旧vmdk如果还在虚拟机目录里,会占据很大空间,建议先备份到其他地方,确认新盘能正常启动后再删除旧盘。
4.3 终极“迂回战术”:新虚拟机挂载旧盘,只搬数据
如果你觉得客户机系统已经积累了几年垃圾,即使压缩/扩容也没意义,那可以考虑“重建虚拟机”。流程是这样的:
- 新建一个全新的虚拟机,先不要安装系统,只建一个足够大的空白虚拟磁盘;
- 把原来那个臃肿的虚拟磁盘当作第二块硬盘挂载到新虚拟机上;
- 用系统安装盘或救援工具启动新虚拟机,把旧盘里的需要数据拷到新盘;
- 拷贝完成后,卸载旧盘,把新盘设为主启动盘,完成系统安装。
Linux下可以直接用rsync排除系统伪目录来拷贝;Windows下用robocopy拷贝用户目录和你需要的数据,然后从新安装的系统启动。这种方式表面上麻烦,但能彻底甩掉所有日志垃圾、缓存、无效注册表条目和快照残留,适合一台虚拟机已经“病入膏肓”的情况。如果你有备份习惯,这其实是性价比最高的选择。
5. 别等问题发生再救火,把这些预防动作写进日常
处理完一次之后,最怕的是三个月后又来一遍。虚拟磁盘膨胀是个持续性问题,我的经验是把它变成周期性的例行维护,而不是每次等到C盘爆红再四处救火。
5.1 快照不是保险箱,清理快照要形成肌肉记忆
快照适合在重大升级或安装软件前临时使用,操作完成后确认无误就要立刻删除。别让快照成为长期备份介质,它既不包含完整的系统状态(取决于位置),又会在你删除时产生很大的合并开销。我的建议是:除非你在做实验需要频繁回滚,否则虚拟机里保持“零快照”状态。如果你确实习惯用快照,那请记住一个原则:任何超过一周仍然存在的快照,都应该主动审视它存在的必要性。
5.2 创建虚拟机时的容量规划,决定未来痛苦程度
个人虚拟机建议优先使用“动态分配”,但不要因此无脑把磁盘最大大小设成200G。你设得越大,vmdk最大上限越大,文件系统出现空洞的概率也越大。更合理的做法是:系统盘只留刚需容量,比如Linux给30~40G,Windows给50~60G,所有的大文件和项目数据放到额外添加的独立虚拟磁盘里(比如D盘、数据盘),这样系统盘即使膨胀了,也只影响一个盘,清理或迁移时不需要动全部数据。
另外,如果虚拟机被频繁用来编译、测试、跑数据库,建议把虚拟磁盘类型改成“预先分配”(厚置备),虽然创建时占用大,但运行性能和稳定性更好,而且不会出现动态盘越跑越碎的问题。两种方案没有绝对好坏,关键是按使用场景去选。
5.3 让“清理+压缩”自动化的个人脚本参考
我自己有一台常开Linux虚拟机,宿主机是Linux,通过SSH来做季度压缩。脚本思路大致如下:
#!/bin/bash # 在宿主机执行,假设虚拟机名称为 dev-vm vmrun -T ws stop "/path/to/dev-vm.vmx" hard # 用ssh进入客户机执行清理和清零(示例,需要配置免密) ssh user@vm-ip 'sudo apt clean; sudo journalctl --vacuum-size=200M; sudo dd if=/dev/zero of=/zero.fill bs=1M; sudo rm /zero.fill' vmrun -T ws start "/path/to/dev-vm.vmx" nogui但这种自动化脚本简单可以,前提是你对客户机有很强的控制力。更稳妥的方式是直接用vmrun执行客户机内部的guest命令,或者干脆手动执行。压缩前的清零确实比较费时间,如果虚拟机文件很大,建议放到夜深人静时跑。
5.4 季度维护自查清单
我一般按季度做一次,顺便把各项数值记录下来。这里整理成一张可以直接复制的表格:
| 检查项 | 正常标准 | 异常处理 |
|---|---|---|
| 快照数量 | 0个 | 先清理客户机并清零,再删除快照 |
| vmdk实际大小与客户机已用空间比值 | 小于1.5:1 | 做客户机清理+清零+宿主机压缩 |
| Linux日志占用 | journalctl --disk-usage< 500M | 执行journalctl --vacuum-size=200M |
| Windows休眠文件 | 不存在 | powercfg /h off |
| Windows WinSxS | DISM /AnalyzeComponentStore建议清理 | 执行StartComponentCleanup |
| 宿主机可用空间 | 大于最大vmdk大小 | 压缩前先清理宿主机或迁移其他文件 |
把这些字段填一遍,每次维护都能知道虚拟机是不是又开始“胖”了。
最后再分享一个我个人的小习惯:做完一次完整清理后,我会把vmdk文件大小和客户机已用空间的大小都截图记在笔记里,等下次再发现某个文件涨得特别离谱时,能立刻判断是正常数据增长还是历史垃圾累积。虚拟机磁盘空间管理本质上是“识别哪些数据是真的、哪些数据是墓碑”的过程,越早处理,代价越小。