
先讲一个几乎每个虚拟化老用户都会遇到的场面宿主机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、/tmpWindows里的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-size100M sudo rm -rf /tmp/* sudo rm -rf /var/tmp/*说明一下apt clean清的是下载缓存通常能释放几百MBjournalctl --vacuum-size把系统日志限制在100MB以内比全部清空更稳健/tmp和/var/tmp删掉的内容都是临时文件虚拟机重启后其实也用不着。清理完以后需要把剩余的空闲空间全部填充为0。关掉所有不必要的应用然后执行sudo dd if/dev/zero of/zero.fill bs1M 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可以安装并使用growpartsudo 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~40GWindows给50~60G所有的大文件和项目数据放到额外添加的独立虚拟磁盘里比如D盘、数据盘这样系统盘即使膨胀了也只影响一个盘清理或迁移时不需要动全部数据。另外如果虚拟机被频繁用来编译、测试、跑数据库建议把虚拟磁盘类型改成“预先分配”厚置备虽然创建时占用大但运行性能和稳定性更好而且不会出现动态盘越跑越碎的问题。两种方案没有绝对好坏关键是按使用场景去选。5.3 让“清理压缩”自动化的个人脚本参考我自己有一台常开Linux虚拟机宿主机是Linux通过SSH来做季度压缩。脚本思路大致如下#!/bin/bash # 在宿主机执行假设虚拟机名称为 dev-vm vmrun -T ws stop /path/to/dev-vm.vmx hard # 用ssh进入客户机执行清理和清零示例需要配置免密 ssh uservm-ip sudo apt clean; sudo journalctl --vacuum-size200M; sudo dd if/dev/zero of/zero.fill bs1M; 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-size200MWindows休眠文件不存在powercfg /h offWindows WinSxSDISM /AnalyzeComponentStore建议清理执行StartComponentCleanup宿主机可用空间大于最大vmdk大小压缩前先清理宿主机或迁移其他文件把这些字段填一遍每次维护都能知道虚拟机是不是又开始“胖”了。最后再分享一个我个人的小习惯做完一次完整清理后我会把vmdk文件大小和客户机已用空间的大小都截图记在笔记里等下次再发现某个文件涨得特别离谱时能立刻判断是正常数据增长还是历史垃圾累积。虚拟机磁盘空间管理本质上是“识别哪些数据是真的、哪些数据是墓碑”的过程越早处理代价越小。