Java后端必会:Jar包启动与日志大文件搜索全攻略
做后端开发和服务器运维的人应该都有过这种瞬间刚接手一个服务第一件事就是找到那个 jar 包敲上java -jar把它拉起来系统一报警第一反应就是冲到服务器上对着几个 GB 的日志文件漫无目的地翻找。这两个操作看起来一个比一个简单但我这几年实际帮人排查过的故障里有相当一部分就出在这两件“简单事”上——启动参数没配好导致线上内存爆炸搜索姿势不对让人在几十 GB 日志里白耗一晚上。这篇文章想把这两件事彻底讲透。前半部分聊 jar 包的启动原理、JVM 参数该怎么理解怎么配、生产环境启动脚本怎么写才够稳后半部分聊大文件搜索和“找大文件”的完整思路包括 grep 的高阶玩法、更快的新工具以及我第一次面对 3GB 日志时的完整排查过程。内容面向刚入行的 Java 开发也适合需要自己上服务器配服务的运维同学——你会发现这两项技能其实是同一件事的两个面让服务活着以及当它出问题时用最短的时间找到真相。1. jar 启动从一行命令到稳定运行1.1 先把 jar 包从外到里看明白jar 包本质上就是一个 ZIP 压缩包只不过里面多了一个META-INF/MANIFEST.MF清单文件。这个文件里最关键的一行是Main-Class: com.example.Main它告诉 JVM“这个可执行 jar 的入口类是谁”。java -jar app.jar执行时JVM 先读 MANIFEST.MF找到 Main-Class再通过类加载器加载那个类调用它的main方法。这里藏着一个新手很常见的坑在 IDE 里直接运行 main 方法没问题打成 jar 包后却报“找不到主类”或ClassNotFoundException。绝大多数情况就是 MANIFEST.MF 里没写 Main-Class或者打包方式不对用的是普通maven-jar-plugin而不是spring-boot-maven-plugin这类能生成可执行 fat-jar 的工具。所以我拿到一个陌生 jar 包从来不会直接java -jar盲跑先看两眼包内容unzip -l app.jar | head -30再直接看清单文件unzip -p app.jar META-INF/MANIFEST.MF这两条命令能帮你提前确认三件事包是胖包还是瘦包、入口类写没写对、依赖是不是都打进去了。省掉后面一大串莫名其妙的报错排查时间。1.2 JVM 参数是稳定运行的基础把服务跑起来只需要一条java -jar但要稳定跑一天、跑一周、跑一个月JVM 参数必须提前想清楚。我见过太多线上故障根因就是启动时没设内存上限。默认情况下JVM 堆内存上限只占物理内存的 1/4你要一台 16G 的机器它只敢用 4G高并发一上来几十秒内就 OOM。反过来你设了-Xmx8G但机器总共只有 6G 可用内存那从一开始就在死亡边缘试探。一个比较通用的基准配置长这样java -Xms4G -Xmx4G -XX:MetaspaceSize512m -XX:MaxMetaspaceSize512m \ -XX:UseG1GC -XX:MaxGCPauseMillis200 \ -jar app.jar解释一下我为什么这么配-Xms和-Xmx都设为 4G是为了避免堆内存频繁扩容和收缩。JVM 运行过程中如果初始堆太小会在达到临界点时做一次 Full GC 来扩容高并发下这个暂停非常致命。固定初始值和上限让堆一直维持在 4GGC 行为更可预测。-XX:MaxMetaspaceSize很多人会忽略但元空间一旦无限制增长对于动态生成类比较多的应用照样会 OOM。给个明确上限至少能在出问题时快速暴露。-XX:UseG1GC对多核大内存机器是更稳的选择。JDK 8 的默认 GC 是 Parallel吞吐不错但停顿不可控G1 可以设置目标停顿时间更适合大多数 Web 服务对响应性的要求。这是个“给新人抄作业”的基础款不是万能配置。批处理任务、实时计算、低延迟交易的参数方向都会不同但掌握了这几个关键参数的语义后面自己调优就有据可依。1.3 不要让服务裸奔生产环境里禁止把服务跑在前台。你手上开着一个窗口java -jar app.jar一旦 SSH 断开服务就跟着没了。正规一点的做法是配合nohup和重定向nohup java -Xms4G -Xmx4G -XX:MetaspaceSize512m -XX:MaxMetaspaceSize512m \ -XX:UseG1GC -XX:MaxGCPauseMillis200 \ -jar app.jar --spring.profiles.activeprod \ logs/app.log 21 这里有个细节21必须跟在重定向之后漏掉或者顺序不对错误信息就丢了。日志是后面排查问题唯一的线索必须保留。同时为了后续能优雅地停掉、平滑地重启我习惯把 PID 记录下来echo $! app.pid后面要停服务的时候kill -TERM $(cat app.pid)而不是直接kill -9。先给 JVM 一个执行优雅停机触发 ShutdownHook的机会等它自己收拾完再退出否则未写完的日志、未提交的事务全都会变成事故现场。日志切割是另一个绕不开的话题特别是后面我们要聊大文件搜索。你想想看一个 jar 启动后连续跑两三个月不重启日志单文件能涨到几个 GB等到想搜索的时候连打开都成问题更别说精准定位了。日志切割做得越早后面搜文件的成本就越低。基于常见实践我补充一点外部配置优先用--spring.config.location/绝对路径/application.yml或环境变量SPRING_PROFILES_ACTIVE外置。因为 Spring Boot 默认以当前工作目录为基准加载config/你从别的目录敲启动命令很可能导致外部配置没生效服务用默认配置跑起来连数据库地址都是错的。这类问题极其隐蔽但排查起来其实只需一条命令ps -ef | grep java看它的启动目录在哪。2. 大文件搜索先找对文件再找对内容2.1 为什么大文件对普通工具不友好大文件最让人头疼的场景一般有两个一是 1GB 以上的日志、数据导出文件用编辑器打开二是想在几十 GB 的文件里定位某一段关键字。先说说为什么“打开就卡死”。像很多图形化编辑器打开文件时会尝试把整个文件读入内存并且做语法高亮、行号计算这些操作对超大文件来说是灾难级的性能负担。200MB 起步就能明显感觉到卡顿到 1GB 基本等于死机就算侥幸打开滚动一次可能都要等几秒。文本编辑器处理大文件的原理是“全量加载 全量渲染”而命令行工具的思路完全不同逐行读取、流式处理、匹配即输出。这也是为什么几千行日志用编辑器没问题、几十 GB 的数据也敢直接用命令行搜的核心原因。所以真到了大文件场景第一原则就是别跟它比“全量”只做“流式扫描”。2.2 grep 的高阶用法大多数同学都会用grep做基础搜索但真正高效的大文件搜索命令的姿势有必要调整。最基础的用法grep ERROR app.log看似简单在超大文件上会有两个问题没有行号、正文完全淹没在输出中。稍微调整一下grep -n ERROR app.log | head -50-n显示行号定位到具体行之后马上可以用sed精确截取上下文。只想知道某个错误出现了多少次而不是刷一堆日志出来grep -c ERROR app.log如果目录下有一堆日志文件你需要知道哪个文件命中最多可以这样grep -c ERROR /var/log/app/*.log | sort -t: -k2 -nr | head -10这段命令的思路是先统计每个文件里 ERROR 出现次数再按次数倒序排只取前十个。排查大面积故障时这一条能直接告诉你“问题正在哪里集中爆发”。对日增几百 MB 的应用来说搜索长时间跨度的关键字时最好加上时间范围。比如想搜最近一小时的数据与其搜整个大文件不如先按时间过滤我用的很顺的一个组合是grep 2025-06-20 14: app.log | grep ERROR第一个 grep 先把时间窗内的行筛出来第二个 grep 再在结果里搜报错关键字。逻辑清晰中间那层结果随时可以抽出来单独看。2.3 查找大文件本身find du sort“搜索大文件”这个标题还有一层含义是“在磁盘里找到哪些文件占用空间最大”。服务器磁盘突然告警往往就是某条日志或者某个临时数据文件在偷偷膨胀。这时候不可能图形化查看纯命令行操作思路应该是du -ah /var/log 2/dev/null | sort -rh | head -20这条命令会列出/var/log下所有文件及目录的大小并从大到小排序只显示前 20 条。2/dev/null是为了把没权限读的目录错误信息屏蔽掉不然输出里会混进一堆Permission denied干扰看结果。如果你想按某个特定条件筛选比如找出所有大于 500MB 的文件find /data -type f -size 500M -exec ls -lh {} \; 2/dev/null | awk {print $5, $9} | sort -rh | head -20这里用 find 找到所有大于 500MB 的文件用ls -lh拿到可读的大小和路径再用 awk 提取前两列最后按大小排序。为什么要走这一套组合而不是直接ls -lhR因为ls -lhR会把整个目录树全部打出来子目录里的隐藏文件、符号链接全混在一起噪声太大。用 find size 条件等于先过滤、再展示效率和可读性完全不是一个级别。如果磁盘告警发生在特定分区应该先确认哪个目录占用最多再一层一层深入df -h du -sh /var/* 2/dev/null | sort -rh | head先看磁盘整体情况再看一级目录的占用大头。这种自顶向下的定位思路比随手进一个目录乱翻要靠谱得多。2.4 比 grep 更快的工具ripgreprggrep 在几百 MB、几个 GB 的文件上完全够用但如果面对的是几十 GB 乃至上百 GB 的日志或者是需要经常反复搜索的场景我强烈建议试试 ripgrep。rg 和 grep 的核心区别是rg 默认会并行扫描系统调用更少对超大文件的搜索速度通常比 grep 快 5-10 倍在多核服务器上差距更明显。用起来几乎零学习成本rg -n ERROR app.log rg 2025-06-20 14:.*ERROR /var/log/app/最实用的一点rg 默认会自动忽略.gitignore里的文件、二进制文件在大型项目目录里搜代码时不会有一堆编译产物和依赖目录混进结果。不过 rg 不是万能的。目标环境是很精简的服务器上面没有 rg那还是得靠系统自带的 grep 保底。掌握 grep 能活命学会 rg 能打得快。3. 实操记录一次完整的启动与排查过程3.1 新的 jar 服务从 0 到 1假设现在有一个打好的 jar 包 app.jar要在某台 Linux 服务器上跑起来。完整流程应该是什么样第一步看包unzip -l app.jar | head -30确认里面有BOOT-INF或者lib/目录说明是一个自带依赖的可执行包。如果是 Spring Boot 项目大概率是 BOOT-INF 结构。要是你看到的是只有几个 class 文件的瘦包那依赖没打进去直接java -jar一定会报 ClassNotFound。第二步看清单unzip -p app.jar META-INF/MANIFEST.MF重点确认 Main-Class 是否存在。Spring Boot 项目还会有一个 Start-Class这是 Spring Boot 自己的启动器机制。有些同学读清单时看到 Main-Class 是org.springframework.boot.loader.JarLauncher就以为包有问题其实那是正常的 Spring Boot fat-jar 启动器它负责加载嵌套依赖然后调用真正的业务入口。第三步确认配置。检查外部配置文件config/application.yml是否存在数据库、Redis 地址是否正确必要的话先测试连通性nc -vz db-host 3306第四步预估内存。先看服务器可用内存free -h如果总内存 16G给应用留 8G 堆空间比较合理但前提是已经确认没有其他大进程在跑。我以前踩过一个坑某次给服务设了-Xmx8G结果没注意同一台机器还跑着另一个数据库服务两边一加上来就超了物理内存直接吃到 swap整机响应变慢。所以不管堆设多少一定要先算清楚整个机器的内存预算。第五步启动nohup java -Xms4G -Xmx4G -XX:MetaspaceSize512m -XX:MaxMetaspaceSize512m \ -XX:UseG1GC -XX:MaxGCPauseMillis200 \ -jar app.jar --spring.profiles.activeprod \ logs/app.log 21 echo $! app.pid启动后不要急着干别的先看日志tail -50 logs/app.log重点观察有没有启动耗时明显的阶段、有没有 warn 级别的资源告警。如果日志快速滚动到Started ... in X seconds恭喜服务起来了。最后验证端口和健康状态ss -lntp | grep 8080 curl -s http://127.0.0.1:8080/actuator/health到这里一个服务才算真正“启动成功”而不是进程在、业务不可用那种伪成功。3.2 从 3GB 日志中定位一次 OOM 的全过程某天线上服务突然响应变慢随后有节点报警。按经验先查 GC 和内存情况但手头最快的证据其实是日志。日志目录里今天的文件已经 3.2GB这时候不可能打开看直接用搜索来定位。先看今天总共产生了多少日志ls -lh /var/log/app/app.log.* | tail -20可以看到app.log.20250620和app.log当前正在写。我们按时间窗先把 OOM 可能发生的时间段确认一下比如凌晨 2 点到 3 点rg -n 2025-06-20 0[23]: /var/log/app/app.log.* /tmp/ooms.txt这条命令把凌晨 0-2 点窗口内的所有日志行都导出到一个临时文件方便反复查。然后精确搜 OOM 相关关键字rg -n OutOfMemoryError|java.lang.OutOfMemoryError|GC overhead /tmp/ooms.txt如果某一段日志出现大量Exception in thread并且前后紧跟着OutOfMemoryError基本就能确定问题点。最后用 sed 精确截取报错前后 20 行还原现场sed -n 101200,101240p /tmp/ooms.txt这里 101200 是 rg 输出的行号直接传给 sed。全过程不需要打开那个 3GB 的原文件只处理了匹配后的临时文件操作非常轻快。4. 常见问题与排查技巧实录4.1 jar 启动高频问题速查下面这些是我在帮别人排查启动问题时出现频率最高的现象常见原因处理方式找不到主类清单里没有 Main-Class或包不是可执行 fat-jar换用 spring-boot-maven-plugin 重新打包端口被占用同机已有进程占住启动端口ss -lntp查看占用进程调整端口或停掉旧进程启动后秒退启动参数或配置错误异常没打印到日志先前台运行java -jar观察控制台输出不要直接 nohup内存持续上涨最终 OOM-Xmx 没设或堆内存在 GC 后仍无法释放显式设置 -Xmx必要时 dump 堆分析CPU 飙升死循环、GC 频繁、或同一服务被误启动多份先jstack抓线程栈再看 GC 日志确认停顿频率第一条“找不到主类”在瘦 jar 和 fat-jar 混用场景里很容易翻车。之前教一个小白 A 同学他在某个框架里把maven-jar-plugin和spring-boot-maven-plugin都配上了结果打出来的包里面是真 class 文件但缺依赖一跑就是NoClassDefFoundError。这种问题用unzip -l看一眼包结构就能提前发现。还有一条经验启动脚本要放在项目目录以外或者至少用绝对路径引用 jar 包和配置文件。因为 Spring Boot 默认以当前工作目录为基准加载config/你从/tmp或者别的目录敲启动命令很可能导致外部配置没生效服务用默认配置跑起来连数据库地址都是错的。这类问题极其隐蔽但排查起来其实只需一条ps -ef | grep java看它的启动目录在哪。4.2 大文件搜索的若干避坑细节第一个是文件编码问题。grep 默认按字节处理如果你的日志是 GBK 编码直接用 UTF-8 环境的终端去搜中文关键字结果是搜不到的。处理办法是先确认编码file -bi app.log如果是 GBK要么用 iconv 转码再搜要么把关键字也用 GBK 编码写。更推荐的做法是从一开始就在应用日志输出上统一 UTF-8从源头消灭这类问题。第二个是二进制文件判断。grep 遇到二进制文件默认会返回Binary file matches然后并不输出具体内容。排查日志时如果莫名搜不到信息看看是不是有大量 NUL 字节的文件混在里面比如从 Windows 迁移过来的日志文件。可以用grep -a强制把文件当文本处理但更合理的做法是先避免扫描到它们rg -n ERROR --glob *.log /var/log/app/第三个是搜索范围太宽。grep -r搜索的目录下如果有几十 GB 的压缩包、数据库 binlog、图片等非目标文件性能会大打折扣。我见过有人搜日志目录时把二进制备份文件也带上了导致本来几秒的事变成几分钟。所以搜索范围精确到文件名模式或类型是非常必要的。再补充一个实操心得tail -f会阻塞终端但它和 grep 组合起来监控实时日志非常顺手tail -f app.log | grep --line-buffered ERROR--line-buffered很关键不加这个参数grep 会等到缓冲区满了才输出实时性大打折扣你可能隔很久才看到想要的报错。4.3 磁盘告警的真实案例有一次某台机器磁盘使用率到了 96%我登上去先df -h看分区确认是/data分区爆了。然后du -sh /data/* 2/dev/null | sort -rh | head -10发现app目录占了将近 300G进去再看du -sh /data/app/* 2/dev/null | sort -rh | head -10定位到logs下面有个trace子目录里面一天的链路追踪日志就写了 180G。根因是当天某个异常分支让日志框架疯狂输出完全没考虑滚动切割。处理方式先停掉服务清理历史日志给日志框架配上按天按大小滚动再启动。整个过程从登录到解决不到 15 分钟靠的就是du sort这套组合拳逐层缩小范围。如果你遇到的是“某个文件被进程占用但删不掉”的情况可以配合 lsoflsof | grep deleted这种文件通常还有空间占着直到持有它的进程重启才会真正释放。遇到磁盘紧张但du又找不出大文件的时候记得查这一条。5. 写在最后的经验小结我自己在实际操作中体会最深的其实是“启动”和“搜索”这两件事在方法论上是完全相通的都不要盲目操作先确认现状再缩小范围最后精准处理。启动 jar 前先看包结构、看清单、看配置、看内存搜索大文件前先确认时间窗、确认目标文件类型、确认编码。每一步都有明确的目的看起来多花了几分钟但节省的往往是后面几个小时的返工时间。最后再分享一个小技巧把你的常用启动脚本和搜索命令沉淀成一套固定的操作习惯甚至写成自己的命令别名。比如我用rg搜日志时会把时间窗变量和关键字变量固定成同一种风格这样每换一个环境、每接一个新项目脑子里的肌肉记忆还能直接用。工具会变、项目会换但“先看结构、再定参数、最后精确搜索”这套思路什么时候都好用。