news 2026/10/15 11:49:07

Java服务磁盘满排查:从jar启动失败到大文件定位的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java服务磁盘满排查:从jar启动失败到大文件定位的完整链路

很多开发者在排查服务器问题的时候,都撞见过这种组合拳:明明Java服务昨天还好好的,今天突然连不上,登录服务器一看,磁盘已经飙到95%。紧跟着就冒出一连串问题——jar文件到底在哪个目录?启动脚本为什么报错?日志文件为什么占了几十个G?这一整套动作,归根结底就是两件事:查jar启动,找大文件。

我在实际排查线上问题的过程中,几乎每隔一段时间就会碰到一次这类情况。今天把整个排查链路和踩过的坑整理出来,希望能帮你把这类问题从“玄学”变成“肌肉记忆”。

1. 为什么“jar启动”和“搜索大文件”总是一起出现

在很多人眼里,Java服务只要“能跑就行”,这台机器要等磁盘满了才知道出问题。而磁盘满了,往往又会被误判成程序Bug。其实这两件事的因果链很直接。

先讲一个最常见的链路:Java应用长期运行,日志文件不断增长,尤其是打印了异常堆栈或者重复打点的情况下,一个用户量不大的服务也能在短时间内写满几十G日志。一旦磁盘被塞满,任何依赖落盘的启动动作都会失败——Java进程可能起不来,或者起来之后马上又挂,健康检查迅速告警。这时候如果你只盯着启动报错,看不到背后的磁盘占用,那就会兜圈子。

再往深里说,jar包本身也是大文件的常客。很多团队把打成几十MB到几百MB的fat jar直接堆在部署目录里,一个版本一个目录,从不清理。一次升级留一个,几个月下来就是几十个G。所以“jar启动”和“搜索大文件”根本就是一枚硬币的两面,排查的时候必须放在一起看。

1.1 jar启动类问题的典型现象

我归纳了一下,最常见的jar启动失败或异常现象有这么几类:

  1. 端口冲突:明明要监听8080,结果有个旧进程占着,新jar报Address already in use。
  2. 磁盘满:启动时需要在临时目录解压内嵌依赖,或者要写日志、写pid文件,结果No space left on device直接打断。
  3. 内存不足:JVM申请不到足够的堆内存,进程起来就OOM被系统kill。
  4. 启动脚本路径写死:脚本里用的是相对路径,换目录之后classpath、配置文件找不到。
  5. 被系统OOM Killer或容器重启策略反复拉起,表现为日志里出现Killed。

这类现象的共同特点是:表面看是“应用起不来”,但根因几乎都指向文件系统状态、路径、权限和资源占用。这也是为什么排查的时候不能只盯着output.log开头那几行,而要先把整个系统状态摸一遍。

1.2 大文件与Java服务之间的因果链

如果一句话总结大文件与Java服务的关系,那就是:大文件要么吃掉磁盘,要么拖垮启动。

吃掉磁盘的典型是日志。日志文件的增长逻辑和应用行为强相关:异常重试、大对象序列化、业务循环调试、第三方SDK的debug输出,都会让单文件在几小时里冲到几个G。更麻烦的是,大多数日志框架默认是“无限写”的,没有配置滚动策略的话,日志文件就会一直涨到磁盘顶满为止。

拖垮启动的典型则是jar包和临时文件。Spring Boot这类可执行jar在启动时会把内部依赖解压到临时目录,磁盘满了解压必然失败;另外,某些部署目录里堆着大量历史jar包,启动脚本用通配符加载lib时如果把它们全带上,classpath会乱掉,还可能引发重复类、版本冲突。

还有一个很多人忽略的点:Linux下文件被进程打开后,即使你把它删了,磁盘空间也不会释放,要等句柄关闭。这个特性在“日志清理失败”的场景里经常坑人,后面我有专门章节讲。

1.3 谁最需要这套排查思路

说实话这套内容覆盖面挺广:

  • 维护单体Java服务的后端开发,遇到应用无故挂掉、磁盘告警,不想一个劲儿重启。
  • 负责中小型服务器运维的DevOps,经常被开发拉去“救火”,需要快速定位占空间的文件和进程。
  • 自己在云主机上部署学习项目的同学,对Linux命令不熟,看到“No space left”就慌。
  • 写程序打包部署脚本的人,想理解jar包部署的本质,避免踩到日志和文件系统相关的坑。

这套内容不依赖任何商用工具,只要一台能登录的Linux服务器,加上几只常用命令,就能把问题定位到具体文件、具体进程。

2. 从“服务没起来”到“jar包在哪儿”:启动排查五步走

不管报错信息是啥,我建议按“状态确认、进程定位、文件定位、启动方式识别、资源核查”这个顺序来查,避免被一根脚本报错带偏。

2.1 第一步:确认端口和服务状态

先看进程在不在,再看端口通不通:

ss -lntp | grep 8080

如果8080没有输出,说明进程可能没起来或者没监听。再确认一下Java进程:

ps -ef | grep java | grep -v grep

这里有个小经验:别只看进程数量,真正有用的是-ef全格式信息里的启动命令行。如果一行能直接看到java -jar /home/xxx/app/xxx.jar,后面两步可以省掉。但很多生产环境是用shell脚本包装的,命令行里只有脚本路径,真正的jar地址藏起来了,那就还得继续往下挖。

如果ss命令不存在,老一点的环境可以用netstat -tlnp | grep 8080。实在都没有,先安装iproute2或net-tools,但多数主流Linux发行版自带ss,这一步也就是几秒的事。

2.2 第二步:找到进程的真实身份

拿到PID之后,可以用:

jcmd PID VM.command_line

这是JDK自带的小工具,能直接看到JVM启动时的完整命令行参数,包括-D参数、jar包路径、JVM调优参数,比读脚本靠谱得多。

如果没有配JDK环境,或者JDK版本太老,就退而求其次读/proc:

cat /proc/PID/cmdline | tr '\0' ' '

/proc/PID/cmdline里保存的就是进程启动时的参数,每个参数之间用\0分隔,所以要用tr替换成空格才方便看。这条路通用性最强,不需要安装任何额外工具,几乎所有的Linux发行版都能用。

2.3 第三步:利用 /proc 把进程与 jar 文件绑定

知道PID之后,真正干活的是这几个命令:

readlink /proc/PID/cwd ls -l /proc/PID/exe lsof -p PID | grep -E '\.jar|\.log|deleted'

cwd是进程当前工作目录,很多相对路径配置都以此为基准;exe指向的是Java主程序的可执行文件路径;lsof -p列出进程打开的文件列表,从中能看到运行中的jar文件、日志文件,甚至包括那些“已删除但仍被占用”的文件。

这里有个特别实用的小技巧:如果jar文件被杀毒、日志清理脚本误删,但进程还活着,lsof -p PID | grep deleted会给你报出来。很多人以为“文件删了就释放空间”,在Linux上并不成立,这种deleted文件就是要重点排查的“隐形大文件”。看到deleted,别再纠结文件名,直接用ls -l /proc/PID/fd/文件描述符号把它找出来。

2.4 第四步:判断启动方式是内嵌还是外置

Java服务的部署方式,基本决定后面的排查方向。

  • 如果是java -jar app.jar,说明是Spring Boot内嵌Tomcat或Undertow启动。它自带依赖,但这个可执行jar会很大,启动时会解压到临时目录/tmp下面的spring-boot-libs之类的位置,这时候要重点检查临时目录是否清得动、磁盘空间是否够。
  • 如果是java -cp加了一串lib/*.jar去启动,说明是外置容器或者传统war部署思路。这时候jar包数量多,排查重点要从“单个文件大小”转向“lib目录整体占用”。
  • 如果是通过systemd或容器平台拉起,还要看是否配置了自动重启,防止你前脚手动停掉,后脚又被拉起,导致排查过程中“找不到进程”。

识别这一点不是纯粹的理论,它直接决定了你是要清理一个巨型jar,还是要瘦身几十个小jar,以及临时目录够不够。

2.5 第五步:把启动失败原因归结到文件系统

如果服务确实没起来,我建议顺手检查三件事:

df -h free -h ulimit -a

df -h看磁盘使用率,free -h看内存余量,ulimit -a看打开文件数、进程数限制。很多时候应用不是“逻辑坏了”,而是资源被卡住。比如磁盘满了,Spring Boot在解压临时文件时就崩;文件描述符到了上限,连接和日志都写不进去;内存不够,JVM直接OOM。

这里有个判断技巧:如果启动报错文件里包含No space left on device或java.io.IOException: No space left,基本可以确认就是磁盘问题,赶紧去搜大文件,别在代码里找原因。

3. 搜索大文件:命令、思路与日常巡检

3.1 直接按大小找:find 的容量级检索

找大文件第一反应应该是find,它对系统资源占用相对可控。比如我想找出所有大于1G的文件:

find / -xdev -type f -size +1G -exec ls -lh {} \; 2>/dev/null

拆开讲:

  • /表示从根开始。
  • -xdev很关键,它让find停留在当前文件系统内,不跨挂载点,避免去扫/proc、/sys这些虚拟目录和外部挂载盘,既省时间又避免一堆Permission denied噪音。
  • -type f只找普通文件,排除目录。
  • -size +1G匹配大于1G的文件,也可以写+200M、+500M。
  • -exec ls -lh {} \;把结果用可读单位打印出来。
  • 2>/dev/null把权限报错丢进黑洞,不然输出全是Permission denied,根本看不完。

如果你只要前几个最大的,可以配合sort:

find / -xdev -type f -size +100M -exec du -sh {} \; 2>/dev/null | sort -hr | head -20

这一步能把所有“大鱼”一次性列出来。但光知道哪些文件大还不够,还得知道它们待在哪层目录、属于哪个应用,这才是定位到jar进程的钥匙。

3.2 按目录统计:du 帮助定位占空间的“区块”

find适合找单个大文件,du适合找目录级别的“黑洞”。我常用这个命令列出一个目录下第一层各子目录的占用:

du -xhd1 / 2>/dev/null | sort -hr | head -20

参数解释:

  • -x同上,不跨文件系统。
  • -h输出可读大小。
  • -d1只统计当前目录下第一层子目录。
  • | sort -hr | head -20按从大到小列出前20个。

如果要换到具体目录,比如怀疑是应用部署目录,就写:

du -sh /home/某用户/app/* 2>/dev/null | sort -hr | head -20

用du -sh可以拿到每个子目录的整体占用,一眼看出“日志目录40G”“lib目录12G”“历史jar包目录8G”。这一步再结合前面的find,基本就把大文件的位置锁死了。

还有个小命令很实用:

du -sh /tmp/*

Spring Boot解压临时文件、Java生成的临时大文件经常藏在/tmp,/tmp在没有系统定时清理旧文件的机器上是个重灾区,值得单独看。

3.3 日志与jar包分开算:两类大文件的不同处置方式

找到大文件之后,必须分下类,因为处置方式完全不同。

日志文件:体积大的日志通常意味着没配滚动策略,或者滚动策略只在重启后才生效。我见过很多“写满磁盘”的真实案例,真正天天涨的其实是app.log这种单文件。处置可以先安全清零,再配置滚动。

安全清零用重定向,不要用删除:

> /home/某用户/app/logs/app.log

这个操作很关键:进程继续往这个文件描述符写,但你把它内容截断为0了,磁盘立刻释放。如果你改成rm,文件虽然没了,但进程还握着句柄,空间不会释放,直到进程重启。这就是“明明删了文件,磁盘还是满”的真相。

然后立刻补上日志滚动配置。以logback为例,一个比较稳妥的滚动配置大概是:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/app.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>7</maxHistory> <totalSizeCap>5GB</totalSizeCap> </rollingPolicy> </appender>

maxFileSize控制在100MB,maxHistory保留7天,totalSizeCap是总容量上限。这套配置能从根本上避免“单个大文件无限涨”的问题。log4j2也有对应的SizeBasedTriggeringPolicy和DefaultRolloverStrategy,思路一样。

jar包:历史版本的jar包一旦确定不用,就同步到备份区或者直接清理。如果担心误删,可以先mv到一个/home/某用户/backup-jars目录,观察一个周期再清理。如果发现某个jar本身特别大,超过200M甚至500M,重点看是不是把依赖都塞进了fat jar,可以用unzip -l 文件名.jar | less看一眼,通常你会发现里面躺着大量没用的依赖,应排除或更精细打包。

3.4 定时任务与巡检脚本建议

排查完一次不难,难的是让类似问题不再反复。我的做法是写一个简单的巡检脚本,每天检查一次“大文件Top 10”和“磁盘使用率”,有异常直接推通知。脚本不需要复杂,核心逻辑就是两条find或du命令的封装。省事一点就直接加到crontab里:

0 8 * * * find /home -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | mail -s "big files report" 你的内部邮箱

当然,大多数团队没有内部mail服务,那就改成把结果写到日志文件,或者直接用监控平台去接。重点是:把“人工排查”变成“按点巡检”,哪怕只是每天跑一次顶500M的命令,也比等磁盘满了才开始找强十倍。

4. 一次真实环境的磁盘告警排查实录

写理论太干,我拿一次现实中的排查过程做个完整复盘。项目代号就叫“某数据同步服务”吧,一套内部的数据交换系统,平时跑在一台测试服务器上,参与排查的主要就我和两个同事。

4.1 现象与第一反应

那天下午,监控平台弹了一条磁盘告警,使用率标红到了95%。紧接着测试环境反馈:某个Java接口超时,健康检查一直失败。

我的第一反应不是去看代码,而是先登录服务器,确认那两个基础数字:

df -h

结果输出里/分区直接红了,使用率95%。free -h内存还有富余。再查端口:

ss -lntp | grep 目标端口号

端口毫无输出,Java服务根本没有监听,说明进程要么停了要么没起来。

4.2 定位过程:先找大文件,再约束 jar 进程

我按日常习惯,先用du扫目录层级,看空间到底被谁吃掉了:

du -xhd1 / 2>/dev/null | sort -hr | head -20

扫出来/home占了大头,接着说:

du -sh /home/某用户/* 2>/dev/null | sort -hr | head -10

马上发现问题集中在某个应用目录,里面有logs目录和lib目录两个大块头。再用find单独查:

find /home/某用户 -xdev -type f -size +500M -exec ls -lh {} \;

结果出来了:logs/app.log已经有将近30G,另外还有两个历史jar包,一个是上个月版本的,一个是大半年前的,加起来占了6G多。

回头看最开始的进程问题:服务进程确实已经死了,从日志文件尾部能看到不断打印的OutOfMemory之外,还能看到大量No space left on device报错——它启动时试图往磁盘写临时文件,但磁盘已经满了,Spring Boot解压jar包的过程直接失败。

4.3 根因与处置过程

根因不难归纳:

  1. 日志没有配置滚动策略,app.log单文件无限增长,占掉了绝大多数空间。
  2. 部署目录里堆了两版历史jar包,没有清理习惯。
  3. Java应用启动时正好需要落盘解压,撞上磁盘满,服务自然起不来。

处置动作按顺序做:

  • 先把日志安全截断:
> /home/某用户/app/logs/app.log
  • 把历史jar包移动到备份目录,确认服务正常后再决定删除:
mkdir -p /home/某用户/backup-jars mv /home/某用户/app/old-*.jar /home/某用户/backup-jars/
  • 清理Spring Boot临时解压目录/tmp下残留的旧目录。
  • 修改logback配置,加上滚动策略。
  • 最后再重新启动服务,重启后端口监听恢复正常,磁盘使用率掉到20%以下。

这次排查全程大概40分钟,真正有效的动作就那几条命令。如果一开始就去翻日志报错,可能一晚上都定位不到“磁盘”这个根上。

4.4 复盘:这套流程的真正价值

这次案例让我意识到,排查顺序决定了效率。如果先把“启动失败”当代码问题,你会走进死胡同;先把“大文件”和“进程状态”确认清楚,问题往往不攻自破。后来我也把同样的流程复用了很多次,几乎每次都能快速收敛。

另一方面,处置完现场只是开始。漏配滚动策略、不清理旧jar包,这种问题下次还会来。真正长期有效的动作是改配置、加巡检,而不是等告警了再救火。

5. 常见问题速查表与几条避坑建议

5.1 高频问题速查表

现象大概率原因先做的事
启动报No space left on device磁盘被日志或临时文件占满先df确认,再find找大文件
文件删了磁盘空间不变文件被进程打开,句柄未释放lsof看deleted文件,重启或截断
服务没起来但找不到进程启动脚本被OOM或OOM-Killer干掉看dmesg/系统日志
端口提示Address already in use旧进程未退出ss -lntp查PID,杀旧进程
jar包太大,启动慢fat jar内嵌依赖过多unzip -l分析,排除无用依赖
/tmp目录占满解压残留+系统没清理du和find定位,重启前手动清理
多份应用互相干扰多个jar监听同一端口或共用资源用jcmd + lsof逐个确认路径

这张表不替代排查,但能帮你先有个方向感。

5.2 几条从业者才会提醒你的避坑建议

第一,别在没确认句柄的情况下直接rm日志。Linux下文件被进程open后,rm只是删文件名,空间不还给你,正确做法是先lsof | grep deleted确认,再用>截断或重启进程。

第二,搜索范围一定要加限制。直接对整个根分区疯狂find,在有大量小文件或者跑着数据库的机器上会拖垮IO,生产环境下风险更大。建议先用du把范围缩小到目录,再进那个目录用find,或者加上-xdev和-type f,别用裸find /扫一天。

第三,改滚动策略前,先想清楚当前日志要不要留。如果把“无限大日志”直接切成“10天滚动”,历史证据可能瞬间被清掉,排查告警会失去上下文。我一般先备份重要日志再套新策略,等运行稳定一周后确认没问题,再清理备份。

第四,定期做一次“瘦身”。不只是日志,历史jar包和工作目录都很容易积累。哪怕每季度花半小时跑一遍du排序,把不用的版本目录移到备份区,半年后你回头看会发现机器清爽很多,启动异常的概率也一起降下来。

第五,命令输出加上2>/dev/null不是逃避问题,是筛选有效信息。服务器上总有大量无权限目录,不加这个,几百行Permission denied会淹没真正的结果。真正要做的不是追求每一条输出,而是把结果排序、截断,让人一眼看到关键行。

最后再分享一个小技巧:排查这类问题的时候,把下面这串命令存在你的笔记里,每次直接改路径就能用:

du -xhd1 / 2>/dev/null | sort -hr | head -20 find /home -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null ss -lntp | grep 8080 jcmd <pid> VM.command_line lsof -p <pid> | grep -E '\.jar|deleted'

真的,我踩过几次坑之后,发现九成的“jar起不来”问题,最后都落在“磁盘满、日志没滚、文件句柄没释放”这三件事上。把这几个命令和背后的排查顺序吃透,你在服务器上救火的效率能提升一个量级。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 11:48:37

Matlab调用REFPROP完全指南:从路径配置到混合制冷剂批量计算

简介&#xff1a;Refprop 是美国国家标准与技术研究所开发的流体热力学与输运性质数据库。这份资料将 Refprop 程序、Matlab 调用包与配套使用说明打包成套&#xff0c;面向需要快速查询物性参数、并在 Matlab 中开展仿真计算的科研与工程人员&#xff0c;解决跨软件调用不便、…

作者头像 李华
网站建设 2026/10/15 11:48:23

CodeGraph实操指南:用静态分析构建代码依赖图谱

1. CodeGraph到底是什么&#xff0c;我为什么关注它package mainimport ("github.com/user/codegraph" )func main() {graph : codegraph.Load("./src")graph.Render("architecture.svg") }我接触CodeGraph的契机很偶然。团队里有个后端仓库积攒…

作者头像 李华
网站建设 2026/10/15 11:48:07

Python+Excel实现桑基图:零成本数据可视化方案

1. 项目背景与核心价值桑基图&#xff08;Sankey Diagram&#xff09;是一种特殊类型的流程图&#xff0c;主要用于展示能量、物质或成本在不同节点间的流动情况。这种图表最早由爱尔兰工程师Matthew Henry Phineas Riall Sankey在1898年用于描述蒸汽机的能量效率&#xff0c;如…

作者头像 李华
网站建设 2026/10/15 11:47:36

蓝牙芯片驱动开发-第2章第5题-基带处理器的时钟管理对信号同步有何影响

蓝牙面试题解析:基带处理器的时钟管理对信号同步有何影响? 难度:⭐⭐⭐⭐ 较难 | 场景:社招二面/三面、蓝牙芯片设计 | 高频:🔥🔥🔥🔥 标准答案 基带处理器的时钟管理直接影响 数据采样精度、频率同步和误码率,是蓝牙通信可靠性的基础: ① 时钟在收发链路中的…

作者头像 李华
网站建设 2026/10/15 11:47:20

从impeccable到查漏清单:打造无可挑剔的创作与产品

1. 从“impeccable”这个词说起&#xff1a;它到底在夸什么我第一次留意到“impeccable”这个词&#xff0c;是几年前在一份英文产品文案里。那款产品其实很普通&#xff0c;但文案用了“impeccable quality”来形容&#xff0c;我当时的第一反应是&#xff1a;这词是不是用大了…

作者头像 李华