☰
Java程序员必备的Linux命令:从日志查看到线上排障
2026/10/13 3:43:01 网站建设 项目流程

搞Java开发的,早晚都得跟Linux打交道。哪怕你在Windows上写代码写得飞起,一到测试环境、生产环境,面对的十有八九就是一个黑乎乎的终端窗口,满屏的Linux命令等着你。我刚工作的前半年其实也没把这当回事,代码能跑就觉得万事大吉,直到有一次线上服务半夜报警,运维同事远程让我看日志,我连tail -f都没记住,脸都红了。后来花了几个礼拜系统过了一遍命令,才发现这些看起来零散的工具,其实是一套完整的排障语言。

这篇博客不打算把Linux命令大全搬过来,只讲Java程序员日常开发、部署、排查问题的时候真正用得上的那一部分——从怎么看日志、查进程、看端口,到定位CPU飙升和内存溢出,再到把自己的部署流程用脚本固化下来。适合两类人看:一是刚入行不久、还在Windows环境里写代码的Java开发;二是会一些命令但每次出了问题都手忙脚乱的Java后端。就算你完全零基础,只要跟着命令敲一遍,也能很快上手。

1. 为什么Java程序员的日常离不开Linux命令

1.1 从开发到部署,绕不开的终端

你可能会想:我写的是Java,大部分时间都在IDE里点来点去,Linux命令和我有什么关系?这个问题我以前也问过。后来我发现,Java程序的宿命决定了你绕不开它。代码写完要打包,打包完要上传到服务器,服务器十有八九是Linux;部署的时候要创建目录、移动文件、解压包,这些操作没有鼠标,只能靠命令;启动之后要确认进程有没有起来、日志有没有报错,又得靠命令。哪怕你只用容器技术,底层也是Linux内核,进入容器之后同样是一个终端。

一条最典型的链路就是:本地写好代码,用构建工具打成Jar包;用scp或者sftp传到服务器某个应用目录下;ssh远程登录服务器;mkdir建目录、mv移动文件、chmod赋予执行权限;用合适的方式把Jar包启动起来;最后tail -f看一下日志确认没有异常。整个链路里至少有七八个Linux命令。如果你不懂,每一步都得求人,等于把命运交给别人。更麻烦的是,生产环境出问题的时候,运维不一定有空,也不一定对你的Java应用内部逻辑有多熟。你作为开发,才是那个最了解应用逻辑的人。命令就是你和系统对话的工具,会用命令,才能亲自去看、去试、去定位问题。

1.2 不懂命令的代价:能跑就行和能救命的差距

我印象特别深的一次,某次周末线上应用突然响应变慢,CPU飙得老高,我在群里只会发一句“是不是数据库问题?”运维同事让我先自己用top看一下是什么进程在吃CPU,我磨蹭了半天才把top敲出来。那种感觉特别无力。后来我花了些时间把top、ps、jstack这些命令搭配起来用,再遇到类似情况,基本能在十几分钟内定位到是哪个线程卡在哪个业务方法里。

懂命令和不懂命令的差距,平时看不出来,一上线就很明显。不懂的人只会重启服务,懂的人会先抓现场:日志有没有报错、GC是不是太频繁、线程是不是在锁上等待、端口是不是被别的服务占了。这些现场信息一旦错过,可能就再也找不回来了,因为你重启之后日志被覆盖、线程栈被清空、端口被释放。所以我的建议是,哪怕你不在运维岗,也一定要把常用命令练到条件反射的程度。这不是为了抢运维的活,而是为了在关键时刻自己有能力救场。

1.3 我接下来要讲的这些内容,适合谁看

我接下来要讲的,集中在命令本身,不涉及太深的内核原理,也不会让你去做系统级调优。但我会把Java开发里真正会碰到的场景串起来讲:怎么看日志、怎么部署Jar包、怎么排查CPU和内存、怎么处理端口冲突、怎么写第一个部署脚本。每个命令我都会讲它到底解决什么问题,以及我踩过的坑。如果你是Linux老手,可能会觉得某些部分基础,但里面的一些小技巧和排障思路,还是值得扫一眼的。

另外,我不建议你拿着一本几百页的书从头背命令,那太容易放弃了。更好的方式是先记住最核心的二三十个命令,然后在使用中不断扩展。就像学游泳,先在浅水区扑腾,比上来就练标准动作有用得多。下面的内容,我尽量按真实工作流来组织,你也可以把它当成一份随身手册,用到哪查到哪。

2. 先搭好环境:选一个顺手的学习工作台

2.1 虚拟化软件、云主机还是本机环境?

学习Linux命令,环境选不好很容易劝退。我见过不少同学在Windows电脑上折腾双系统,结果显卡驱动、网络配置、引导修复折腾好几天,还没开始学命令就想放弃了。其实最省心的方式是装虚拟化软件,里面跑一个主流的Linux发行版,不需要图形界面,只要一个终端就够了。虚拟化环境的好处是可以随时做快照,命令敲坏了、服务起不来了,恢复一下就好,不用重装系统。网上资料最丰富的那两个发行版选一个就行,不要纠结哪个更好,先把自己手里的环境跑起来。

如果你手头有云主机,哪怕是最低配的按量计费实例,也值得拿来做练习。云主机的优势是网络和真实服务器一致,你能完整体验从上传Jar包到部署启动的过程,而且不会影响本地环境。macOS用户比较幸运,自带的终端就是类Unix环境,大部分Linux命令可以直接用,不过文件路径、包管理器有些差异,真遇到问题还是可以再准备一个虚拟化环境来对齐。选择哪个不重要,重要的是有一个能让你放心折腾的环境。

2.2 终端工具和Shell的取舍

很多人问在Windows上是不是就不方便学Linux?其实现在不是问题。装好虚拟化环境之后,用系统自带终端、或者独立的SSH客户端工具登录你的Linux环境都行。我个人的建议是,终端工具不用太花哨,能把内容显示清楚、能多标签、能保存登录会话就够了。真正影响你效率的不是工具,而是你对命令的熟悉程度。有些终端工具还能配置字体和配色,让中文日志显示更舒服,这个倒是值得花几分钟调一下。

Shell方面,生产环境的Linux服务器默认基本都是bash,所以我建议你的学习环境也用bash。其他Shell确实在某些提示和补全上比bash强,但如果你在登录生产服务器时依然用的是bash,切换来切换去会让你分心。先把bash练熟练,再折腾自己的日常终端不迟。此外,一定要习惯Tab补全。很多新手敲命令时还在一个字符一个字符地删改,其实你敲到一半按Tab就能补全,参数不确定时快速按两下Tab可以看到候选列表。这个习惯越早养成越受益。

2.3 第一件事:学会看帮助和补全

在开始学具体命令之前,先花十分钟学会看帮助文档,之后能省下大量搜索时间。每个命令几乎都支持--help,系统也自带文档可以用man查看。比如你想知道ls都有哪些参数,直接敲:

ls --help man ls

新手最怕的是脸皮薄,命令参数记不住也不敢查,凭感觉乱试。这是学习效率的大敌。我后来的习惯是:凡是复杂命令,先加--help看一下参数说明,再结合官方文档示例使用。看帮助不是丢人的事,反而是专业性的体现。不要觉得“我连这个都要查是不是太菜”,真正专业的人会在不确定参数的时候第一时间查文档,而不是靠记忆硬撑。

3. Java开发高频命令实战拆解

3.1 文件与目录操作:别再用鼠标了

进入正题前先说明一点:Java开发用Linux命令,前半段几乎都在跟文件打交道。网上有不少把命令分类成文件管理、文本处理、网络工具、进程管理的文章,我按Java开发的实际使用频率重新排个序,先介绍你每天都要碰的这批。

最常用的文件操作命令不多:ls、cd、pwd、mkdir、cp、mv、rm,再加上压缩解压的tar和跨服务器传文件的scp。简单列一下核心用途:

  • ls -l 查看文件权限和大小,ll是很多人的习惯别名;cd和pwd负责切换和确认位置;mkdir -p可以一次创建多层目录;cp -r复制目录;mv既能移动文件也能重命名;rm删除文件或目录。
  • tar -czvf app.tar.gz /opt/app 能打包压缩,tar -xzvf app.tar.gz -C /opt/app 可以解压到指定目录。这组参数我几乎每周都会敲。
  • scp app.jar user@remote:/opt/app 是把本地文件传到远程的常用方式,反过来就是scp user@remote:/path/file .。如果目录大,建议先打包再传,速度会稳定很多。

这里我要认真提醒一句:rm -rf是很多事故的根源。我刚入行时在一台开发机上想删除一个临时文件夹,结果路径拼错了,差点把项目目录删掉。从那以后,我删除之前都会先ls确认,或者用mv把文件移到一个名为trash的目录里,确认不要了再清掉。生产环境更是如此,哪怕你觉得自己打过几百遍命令,也不要轻视它。另外,Java开发经常会遇到一个问题:Jar包到底能不能打开?其实命令行里就能看:

jar tf app.jar unzip -l app.jar

如果你需要快速确认某个class文件是否存在,用unzip -l加grep是最快的。别在服务器上解压整个Jar包,没那个必要,也很占空间。

3.2 看日志:tail、grep、awk组合拳

日志是Java应用的第一现场,几乎每次排查问题都是从日志开始的。我把这部分排在文件操作后面,因为它的出现频率实在太高了。

最基础的两个命令是tail和grep。tail -f实时跟踪文件新增内容,比如服务启动后开着这个窗口观察日志变化;tail -n 100只显示最后100行,适合看启动信息。grep按关键字过滤,比如查错误堆栈:

grep -n "Exception" app.log grep -E "ERROR|WARN" app.log

单纯用grep还可以,但日志文件动辄几百MB,你直接grep整个文件会非常慢。我的习惯是先用tail或head切出一段,再做过滤。举个例子,只关心最后5000行里的异常:

tail -n 5000 app.log | grep -E "ERROR|Exception" | tail -n 50

这条命令看起来简单,里面其实用了三层:tail切数据,grep过滤,再tail取最近的部分。管道符号|是Linux里最神奇的设计,它把前一个命令的输出当成后一个命令的输入,一层层加工下去。理解管道,很多复杂操作都能通过组合简单命令来完成。再进一步,用awk提取字段。比如日志里每一行以空格分割,第一个字段是时间,你想把时间戳单独列出来看看某段时间内的错误分布,可以写成:

awk '{print $1, $2}' error.log

或者统计某个异常出现的次数:

grep -c "NullPointerException" error.log

很多Java程序员不看日志,因为日志太多不知道从哪下手。我的经验是先看ERROR级别,再看WARN;先看堆栈的第一行,再往上翻上下文;如果日志里有traceId或者requestId,一定要用这个ID把一次请求的完整链路捞出来,这比大海捞针高效得多。还有一个重要提醒:别直接打开几百MB的日志文件,很多终端和编辑器会卡到怀疑人生。要查看片段就用tail、head、sed,比如sed -n '1000,1100p' app.log可以打印第1000到1100行,这在定位特定时间段的问题时很好用。

3.3 排查进程与线程:ps、top、jstack

这部分对Java程序员来说是必修中的必修。平时开发环境有IDE帮你看着,但生产环境没有图形界面,一切状态都藏在ps和top的输出里。先学会看进程。ps -ef会列出所有进程,一般配合grep使用:

ps -ef | grep java

输出中能看到程序的PID、启动用户、启动命令。如果你部署了多个应用,建议命令行里的jar包名字起得有意义一些,否则ps出来全是java,根本分不清谁是谁。然后看资源占用。top进入一个实时刷新的界面,默认按CPU使用率排序。直接敲top可以看到哪个进程在吃CPU。如果某个Java进程CPU很高,再敲:

top -H -p PID

-H表示显示线程,-p限定进程ID,这样你看到的就是这个Java进程内部各线程的CPU占用。记下CPU高的线程ID,注意这个ID是十进制的,而jstack导出的线程栈里,线程ID是用十六进制nid表示的。转换一下:

printf '%x\n' 12345

接下来用jstack导出Java进程的线程快照:

jstack PID > thread_dump.txt

然后在thread_dump.txt里搜索nid=0x十六进制值,就能看到那个线程当前在做什么。如果大量的线程阻塞在一起,通常就是锁竞争;如果是某个业务线程一直在循环里执行,就要重点看它在哪个类哪个方法上。这一套动作我称之为“CPU飙高三板斧”:top找进程,top -H找线程,jstack找代码。

补充一个容易忽略的点:jstack命令本身要能读取目标进程,往往需要和Java进程同一个用户权限。如果你用sudo执行jstack,导出的时候要注意权限范围。另外,jstack抓到的是一次瞬时状态,如果问题隔几秒就变,可以多抓几次,对比看线程状态的变化。手动敲一次没问题,但每次线上都要敲好几条命令确实麻烦。你完全可以写一段小脚本,给它传一个PID,自动完成线程ID转换和jstack抓取。这部分我会在后面的实战流程里再展开。

3.4 磁盘与内存:df、du、free

Java应用看起来是纯软件问题,但很多故障的根因其实是磁盘和内存。最常见的情况是日志把磁盘塞满,应用写不进日志,或者直接卡死。所以df和du要养成随手用的习惯。

df -h du -sh /var/log du -h --max-depth=1 /opt/app | sort -h

df -h查看文件系统使用情况,后面加目录可以只看具体分区;du -sh查看某个目录总大小;如果要一层层找哪个子目录占空间,用du --max-depth=1再排个序。有一次某应用突然对外表现像假死,排查半天发现是磁盘写满,当时如果先看一眼df -h,几分钟就能定位。这个教训我记到现在。

内存方面,free -h展示系统内存和交换分区的使用情况。Java程序占的是物理内存还是虚拟内存,命令里都能看到一些信息,但理解起来需要注意:top里的RES是真实占用,VIRT是虚拟内存,Java程序因为JVM预分配声明空间,VIRT往往很大,不要被它吓到。真正需要关注的是RES、free里的available,以及JVM自身的堆内存状态。后者可以继续用jstat:

jstat -gcutil PID 1000 5

输出里能看到各个内存区域的使用比例和GC次数。如果Old区域涨到90%以上,且一直没有回落,那基本可以怀疑有内存泄漏或者堆配置不足。配合取出堆快照,就能进一步分析。这块很多初学者会觉得是运维的事。其实Java开发掌握到能判断“磁盘还剩多少、内存够不够、GC有没有失控”这个程度就足够了。再深入的内存剖析,工具链也已经成熟,后面具体场景我会演示怎么用命令拿到第一手数据。

3.5 网络诊断:端口、连接和健康检查

Java应用离不开网络,端口被占用是最经典的新手报错。我几乎每周都能在问题里看到这种错误:端口已在使用。别慌,一条命令就能查到是谁占着端口:

netstat -tlnp | grep 8080

netstat是传统命令,新一些的Linux发行版默认可能没装,可以用ss替代。ss -tlnp和netstat的参数差不多,效果也一样。如果你不想记两套,优先用ss,现代系统基本都带。关于输出里的PID,有时候因为权限问题不显示,就要加sudo再看一遍。

Java服务起来之后,想知道它是否真的对外提供服务,可以用curl做健康检查。比如应用内部暴露了一个/health端点:

curl -v http://localhost:8080/health

-v可以看到HTTP协议的交互过程,包括请求头、响应头、连接状态。如果连接不上,先检查本机端口,再检查网络。这里有个排查顺序:先在服务器本机curl,通了再让同事从远端访问;本机都不通,问题就在应用或端口;本机通远端不通,那就去看防火墙、安全组和网络链路。这个思路能帮你少走很多弯路。

再扩展一下,如果你怀疑应用请求外部服务超时,可以用nc或者telnet测试端口连通性,但很多最小化安装的服务器没有telnet,直接用nc -vz IP PORT也能实现类似效果。至于ping,它测的是ICMP连通性,端口是否开放ping是测不出来的。我见过不少新人拿ping测端口,发现ping都通就觉得网络没问题,其实端口可能根本连不上。要分清楚工具各自的用途。

3.6 权限与用户:别轻易给满权限

部署Java应用时,权限问题也很常见。为什么会遇到?因为服务器上不止一个用户,运维、开发、程序各自有各自的身份。Java进程一般需要用自己的用户来启动,不能随便用超级用户。所以你要能看懂文件权限的含义,也要会改权限。

Linux权限的概念不复杂:每个文件有属主、属组和其他人三类角色,每类角色可读、可写、可执行。rwx分别对应读、写、执行,用数字表示就是r=4,w=2,x=1。常见的755表示属主可读可写可执行,其他人可读可执行;644表示属主可读写,其他人可读。改属主、属组用chown:

chmod +x start.sh chown -R appuser:appgroup /opt/app sudo -u appuser java -jar app.jar

第一个命令给脚本加执行权限,第二个把目录归属给应用账号,第三个以应用账号身份启动Java进程。不要小看这三条,它们能解决大部分Java服务的权限类启动问题。另外,不要把文件都设成777,也就是所有人可读可写可执行。开发机上图省事也许没事,生产环境一旦某个文件对所有人可写,安全风险会直线上升。如果日志目录权限不对,应用启动后写日志也会失败,所以启动Java应用之前检查一下日志目录是否属于启动用户,是很划算的事。

4. 一个Java应用的完整运维排障流程(实操场景)

4.1 部署Jar包:后台运行、服务管理还是脚本?

当命令练到一定熟练度,就该面对Java程序员最关心的一个问题:一个Jar包该怎么在生产环境里跑起来。这听起来简单,但不同环境下最佳方式不太一样。最早我用的是最简单的方式:在终端前台直接跑。

java -jar app.jar

前台跑的好处是日志直接输出,Ctrl+C就能关掉,非常适合第一次启动时验证配置。但它不能被挂在后台长期运行,终端一关进程就没了。所以要后台运行,最朴素的方式是用nohup:

nohup java -jar app.jar > app.log 2>&1 &

nohup的意思是忽略挂断信号,&表示放后台,> app.log 2>&1把标准输出和错误输出都重定向到app.log。这条命令当年救了我很多次,但它也有问题:进程没有守护机制,崩了就崩了,不会自动拉起;开机也不会自启动。

如果你所在的环境有系统的服务管理机制,我更推荐用它来管理Java服务。它不仅能开机自启,还能在进程异常退出时自动重启,会用之后比nohup省心太多。一个最简配置可以这样写:

[Unit] Description=My Java App After=network.target [Service] User=appuser ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /opt/app/app.jar SuccessExitStatus=143 Restart=on-failure [Install] WantedBy=multi-user.target

保存到系统的服务配置目录后,执行配置重载,再用服务管理命令启动。这里的SuccessExitStatus=143主要是为了让系统把Java进程被正常停止时返回的143这个退出码当成正常退出,否则每次停止服务都会被记录为失败。

既然有了服务管理,自己写启动脚本还有意义吗?我的经验是,在企业里很多服务器系统比较老,或者被运维集中管控,不一定允许你随便创建服务;这时候一个简单的start.sh脚本依然很实用。脚本可以封装jar包路径、日志目录、PID处理逻辑,让同事也能一键启停。我建议你至少掌握nohup和系统服务管理这两种方式,哪一种都能用上。

4.2 应用启动失败排查套路

Java应用启动失败是最常见的线上事故场景。很多新手一看到失败就慌,直接把日志刷没了再重启一遍,结果反复横跳,问题还是那个问题。其实启动失败是有套路的,按步骤来,大多数情况下十分钟内能定位。

第一步,别用后台方式启动。第一次启动或者怀疑配置有问题时,先用前台命令跑一次,让日志直接打到屏幕:

cd /opt/app java -jar app.jar

如果启动报错,屏幕上就能看到异常堆栈。常见的一种是端口被占用。这时候直接用ss查看占用端口的进程:

ss -tlnp | grep 8080

拿到PID之后,根据实际需求选择杀掉旧进程或者换端口。注意不要贸然使用强制杀参数,先ps -ef确认它是谁。第二步,如果应用能启动但又立刻退出,比如进程出来几秒就没了,那要检查启动日志和系统日志。常见原因是:内存参数配得过大,系统根本分配不了那么多;依赖的数据库或服务没连上;配置文件读取路径不对。用前台启动时,这些错误都会在屏幕上留下线索。把关键堆栈复制下来,逐条分析。

第三步,一切正常但后台启动后看进程不在,可能是启动脚本的问题。后台启动时标准和错误输出都被重定向到了某个日志文件,你tail那个文件看最后的报错就知道原因。不要光看进程,先把日志读完,再决定下一步。这里有个非常实用的习惯:每次启动前都先看端口、磁盘、内存这三个基础状态。端口有没有被占,磁盘有没有满,内存还够不够。这三个因素导致启动失败的概率很高,提前检查能帮你节省大量时间。

4.3 高CPU占用排查:top配合线程快照走一遍

前面讲了单个命令,这里我来串一个完整场景:Java应用突然CPU飙到几百,接口响应变慢。这种场景在线上很常见,而且非常考验命令的综合运用。整个过程我通常分四步。第一步,登录服务器后立刻执行top,按CPU使用率排序,确认是哪个进程在吃CPU。如果有个进程名字是java,记下它的PID。

第二步,用top -H -p PID继续看这个进程内部的线程,找到CPU占用高的线程,记下它的线程ID。注意这里默认显示的线程ID是十进制的,而线程快照里用的是十六进制nid,所以要先转换:

printf '%x\n' 12345

假设结果为3039,那么后面搜nid=0x3039。第三步,导出当前线程快照:

jstack PID > /tmp/thread_dump.txt

第四步,在快照里搜索:

grep -A 20 "nid=0x3039" /tmp/thread_dump.txt

这会显示出那个线程当前的调用栈,栈里会包含类名、方法名、行号。看到行号,事情就好办多了。回去对照代码,大概率就能确认是哪个循环、哪段锁或者哪个数据库查询在消耗CPU。

这个流程我用了很多年,每次基本都能命中。要说有什么坑,一是操作系统线程ID和JVM内部线程ID的进制转换不能忘,忘掉了就是查不到;二是线程快照抓取的是一次性状态,如果线程状态变化很快,可以多抓几次做对比;三是有时候高CPU不一定是业务线程,也可能是GC线程,如果栈里看到GC相关线程在猛跑,就要结合GC统计看情况。多抓几次,分支判断就容易了。

4.4 内存溢出与GC问题初步定位

Java应用另一种典型故障是内存溢出。常见堆栈包括堆空间不足、元空间不足、无法创建本地线程。命令层面的排查思路是先确认内存和GC状态,再获取分析素材。第一步用jstat看GC情况:

jstat -gcutil PID 1000 5

每隔一秒打印一次,连续五次。如果看到老年代一直居高不下,垃圾回收频繁,堆内存可能已经接近上限。再配合free -h看系统内存,判断是不是JVM配置的堆内存超出了物理内存的承受范围。第二步,如果确定需要分析堆,就导出堆快照。实操时我一般这样写:

jmap -dump:format=b,file=/tmp/app.hprof PID

导出的文件可能很大,但这就是你分析的原材料。随后用专业的内存分析工具打开,找大对象、找可疑的引用路径。这里先不展开分析工具,主要强调一条:导出前要确认磁盘空间够不够,因为快照动辄几个G,导出到磁盘满的目录会让事情雪上加霜。

还有一个更聪明的做法:在JVM启动参数里加上-XX:+HeapDumpOnOutOfMemoryError,这样一旦发生内存溢出,JVM会自动把堆快照写到指定路径。虽然线上加它有一点额外开销,但大多数团队都愿意保留这个参数,因为不指望人工逮到OOM瞬间,自动保存才是可靠的。如果你是初学者,第一次看到快照文件别慌,它就是一份文件,在本地用分析工具打开就能看到对象分布。命令本身很简单,难的是分析,但命令至少要保证你能拿得到这份数据。这一步做不到,后面再专业的分析工具也白搭。

4.5 端口占用与连接数异常的排查

除了CPU和内存,网络连接相关的异常也经常出现在Java应用里。最常见的两种:端口被占、连接数状态异常。前面简单提过查端口,这里再说说对Java开发更实用的连接状态统计。先看一个连接数统计场景。如果你的应用是一个TCP服务,怀疑连接数不够用了,可以用ss查看当前所有连接状态:

ss -tan | awk '{print $1}' | sort | uniq -c

输出会统计出LISTEN、ESTABLISHED、TIME_WAIT等状态各有多少条。如果TIME_WAIT特别多,常见原因是短连接创建太频繁,服务端的连接会被动地进入TIME_WAIT状态,需要等到超时才能释放。Java程序里如果你用了一些连接池配置不当的客户端,或者频繁创建连接,就很容易出现这个现象。

再看端口被占的另一种场景:应用启动时报端口被占用,但ss查了之后发现没有进程监听这个端口。这就有可能是端口只绑定在某些IP上,或者被防火墙策略影响,也可能是残留的TIME_WAIT连接。处理方式可以先看ss -tlnp有没有显示本地地址,是全部地址还是特定IP,再结合防火墙规则判断。有时候一句“端口明明没占用却认为被占用”的问题,其实需要从绑定地址入手。网络排查还有一个常见误区:只在自己的电脑上测试端口,而不登录服务器本机验证。如果本地网络到服务器的链路有隔离,很可能你在公司能访问,到了家里就访问不了,但应用本身没有问题。排查的优先级一定是从离应用最近的地方开始:先本机、再局域网、再公网。工具不是越多越好,能解决问题即可。

5. 那些年我踩过的坑:命令使用避坑清单

5.1 管道、重定向与后台运行的坑

命令本身不难,难的是组合使用。我在这里记录一些实际绊倒过我的坑。第一个是管道和缓冲。很多人会写这样一条命令去实时看日志中的Exception:

tail -f app.log | grep Exception

看起来没问题,但实际输出可能半天不出现。原因是grep默认对标准输出做了全缓冲,而不是行缓冲,所以它收到tail的输入后,会等到缓冲区满了才输出。解决办法是给grep加--line-buffered:

tail -f app.log | grep --line-buffered Exception

这个细节知道的人不多,但实际遇到时特别容易让人怀疑命令写错了。类似的还有awk和sed,处理多行数据时也可能因为缓冲机制造成输出延迟。第二个坑是重定向中>和>>完全不同,前者会清空文件再写入,后者是追加。如果你每次启动应用都用> app.log,历史日志就会被清掉,线上报错时想看启动前发生了什么,已经来不及了。建议日志文件名带上日期,或者至少保留一份历史文件。

第三个坑是后台任务没有想象中那么安全。nohup java -jar app.jar > app.log 2>&1 &之后,终端可以关闭,进程会继续运行。但如果你在交互式Shell里启动了前台任务,然后直接按Ctrl+C,可能同时把整个进程组都信号了,包括你不想杀的任务。理解进程组、作业控制的原理,能避免一些误操作。初学者至少要知道:想真正放到后台,加nohup和&是标准姿势;只是临时想挂后台继续操作,可以用Ctrl+Z加bg。

5.2 文本处理时的中文编码与文件格式

Java程序员和文本文件的交集特别多,日志、配置、脚本都是文本。在生产环境里最烦的问题之一就是日志乱码,以及Windows下编辑的文件传到Linux后执行报错。乱码的根源往往是文件实际编码和系统locale不一致。比如你在Windows下用记事本保存了一个带BOM的文件,到了Linux上解析可能出现乱码;或者有些老系统日志是GBK编码,你用UTF-8的终端打开就是一堆问号。先判断文件编码:

file -i app.log

如果显示charset不是UTF-8,而内容其实是中文,可能需要转码。比如把GBK转成UTF-8:

iconv -f GBK -t UTF-8 app.log -o app_utf8.log

当然,更根本的解决办法是从源头统一:让应用日志直接输出UTF-8,脚本文件全部用UTF-8无BOM格式。另一个文件格式问题是回车符。Windows下写的shell脚本,每一行末尾是\r\n,Linux只认\n,所以脚本执行时会报错No such file or directory,其实只是多了个看不见的\r。解决办法是转换格式,或者用sed直接删掉:

sed -i 's/\r$//' start.sh dos2unix start.sh

这条小技巧真的救过我好几次,那种莫名其妙的报错,根源往往是文件格式,不是语法问题。建议第一次在Windows上创建脚本时,就把编辑器的换行符改成LF,一劳永逸。

5.3 危险命令和误操作恢复

学命令最重要的不是学得有多花哨,而是不制造事故。Linux里没有回收站,rm删除的文件很难找回。我见过不止一个初学者在服务器上敲rm -rf的时候,路径变量为空,结果把当前目录给删了。这些事故的共性都是用太自信。我自己的几个习惯:一是删除之前先用ls把目标列出来确认;二是能不用rm就不rm,优先用mv把文件挪到备份目录,等确认没问题再清理;三是脚本里凡是涉及删除的行,都要加路径保护,比如先判断变量非空再执行;四是生产环境的操作尽量留痕,用服务管理或脚本替代手动命令。

还有压缩包解压的覆盖问题。如果服务器上已经有一个同名目录,直接解压会把新文件覆盖进去,旧版本被悄悄替换。所以解压前先查看压缩包内容,确认目标目录是否是预期位置,必要时先备份当前运行版本。版本切换时,我用软链接来管理:新版本放到带版本号的目录,链接指向当前版本,服务启动时指向这个链接。这样回滚只需要改一下链接。

误操作之后也有办法,但都很被动。比如你删除了一个正在被进程打开的文件,虽然路径没了,但进程的文件描述符还保留着,在进程相关目录下还能找到文件内容,可以尝试恢复。这个操作比较考验功底,新手很难处理得当。所以我更愿意反复强调预防,别给自己留这种惊险体验。

5.4 提升效率的小技巧:别名、历史和操作留痕

最后分享几个能提升日常效率的小技巧。第一个是alias,给常用命令起短名。我在自己的配置文件里配了几个常用的:

alias ll='ls -lh' alias la='ls -la' alias jstatgc='jstat -gcutil' alias du1='du -h --max-depth=1'

配置完记得让配置生效。alias最大的价值是把你肌肉记忆里最常用的命令缩短,减少击键次数。不过生产服务器上很多是共享环境,你自己的别名可能不存在,所以依赖别名同时,也要记住原始命令的完整写法。第二个是history和Ctrl+R。在终端里按Ctrl+R可以直接搜索历史命令,不用翻屏幕。遇到复杂命令,先history一下,找到上次用过的完整命令稍作修改,比重新敲一遍快得多。如果想记录某次操作全过程,可以用script命令:

script -a /tmp/my_session.log

操作结束后退出,日志会保存。这个技巧在给团队演示排障过程或者做线上复盘时特别有用,能让每一步操作都有据可查。第三个是学会看kill信号。很多Java程序员用强制杀参数很随意,但强制杀进程不会给进程处理善后工作的机会,可能让状态文件、数据库连接处于不一致状态。优先用普通终止信号,让Spring Boot这类框架有优雅关闭的机会;真的没反应再考虑强制手段。这个习惯在生产环境尤其重要。

6. 下一步:命令之外的Linux进阶路线

6.1 Shell脚本:把重复劳动自动化

命令熟练之后,下一个自然要学的是把命令串成脚本。脚本的意义不是炫技,而是把重复的操作固化下来,让每次部署、每次启停都变成可控的。Java程序员写脚本不要太复杂,能解决实际问题就行。我最早写的一个脚本就是启停应用:

#!/bin/bash APP_NAME=app.jar APP_DIR=/opt/app cd "$APP_DIR" PID=$(pgrep -f "$APP_NAME") if [ -n "$PID" ]; then kill -15 $PID sleep 5 fi nohup java -jar "$APP_NAME" > app.log 2>&1 & echo "app started, pid: $!"

这个脚本的逻辑是停掉旧进程,再启动新进程。看起来很简陋,但在没有服务管理机制管控的旧环境下足够好用。写脚本时最容易出问题的是变量和空格,比如变量赋值等号两边不能有空格,变量引用最好都加双引号,否则路径带空格时脚本会跑偏。不确定语法时,先做一下语法检查,再跟踪执行过程,这些问题都能尽早暴露。升级一点,还可以把脚本写成同时支持启动、停止、重启参数的风格,让同事不需要看脚本内容就能操作。脚本本身也是一份文档,把常见的部署路径、日志位置都写进去,后来的人接手时少走很多弯路。

6.2 系统服务管理与容器基础

到这一步,Linux命令已经不再只是敲敲打打的工具,你要开始理解系统怎么管理服务。系统服务管理器是现在主流Linux的服务管理机制,前面已经给过一个Java应用的配置样例。学会服务管理命令的启停、状态查看、设置开机自启之后,大多数服务的启停和自启都能搞定。定时任务则用来安排周期性的操作,比如定时清理日志:

0 3 * * * find /opt/app/logs -name "*.log" -mtime +7 -delete

这行配置表示每天凌晨3点删除7天前生成的.log文件。这里有一个细节我建议你注意:某些发行版默认没有开启定时任务日志,任务到底执行没执行,最好先在脚本里加上输出或写一条日志,跑几天再确认。

容器时代,Java应用的运行环境往往被封装成镜像。你仍然需要Linux命令,只是很多时候操作对象是容器而不是完整系统。进入容器用执行类命令,看容器日志用日志类命令,查看进程和资源同样是top、ps这些命令。容器里缺失很多系统工具的情况下,至少掌握这些通用命令,到了现场不至于无从下手。底层是Linux,很多经验是相通的,区别只是你的视野从一台服务器变成了一个集群。

6.3 从运维角度理解Linux

很多Java程序员学命令容易停留在背命令的层面,碰到新命令就发怵。我的经验是,与其背命令,不如把Linux理解成一张表。进程是表,网络连接是表,文件也是表,命令就是查询这张表的接口。想清楚这个类比,你就知道为什么ps、netstat、ls这些命令长得这么像,都是列出某个对象的状态,然后配合grep去筛。更进一步,Linux把很多资源都抽象成了文件,所以看一个服务的状态,很多时候就是看某个目录或者文件的变化。系统运行信息目录下几乎包含了进程的所有运行时信息,jstack、jmap这些工具底层也要读这些信息。理解了这条线,遇到新工具你会更有底:它大概率是帮你解析某个系统文件的工具,而不是魔法。

我建议Java开发在学命令的同时,补一点点操作系统的基本概念:进程的状态、内存的地址空间、文件描述符、端口和连接的区别。不用太深,够你用命令解释现象就够了。比如你知道内存分页、交换两种状态,就明白内存查看命令为什么有那么多行;知道TCP连接状态为什么存在,就更容易理解连接数统计所反映的应用行为。知识是横向连接的,命令只是入口。

6.4 沉淀自己的命令手册

最后想分享的习惯是沉淀。我的Linux命令不是一天学完的,而是断断续续在故障现场里练出来的。每次遇到一个值得记录的命令或排查模式,我会立刻写到文档里,配好示例和解释。时间长了,这份文档就成了自己的排障手册。再遇到线上问题,先翻手册,避免重复踩坑。我也建议你建一个类似的文档来积累。内容不用规整,关键是记下当时为什么用这条命令、输出里哪些信息是关键、最后是怎么解决的。记录动作本身,会逼你把命令背后的原因想清楚。否则当时搜到了答案,敲完就忘,下次还是不会。这个过程比背命令值钱得多。等你积累到一定程度,再回头看那些命令,就不再是孤立的单词,而是一整套解决Java应用问题的工具箱了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询