从0开始,来看看怎么去linux排查Java程序故障

一,前提准备

最基本前提:你需要有liunx环境,如果没有请参考其它文献在自己得到local建立一个虚拟机去进行测试。

有了虚拟机之后,你还需要安装jdk和配置环境变量

1. 安装JDK(以OpenJDK 17为例)

下载JDK

进入用户主目录

cd ~

下载OpenJDK 17(以.tar.gz包为例)

wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz

或Oracle JDK(需官网同意许可协议)

wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz

这里就已经下好了

解压并安装

创建安装目录(需sudo权限)

sudo mkdir -p /usr/local/java

解压JDK到目标目录

sudo tar -xzvf openjdk-17.0.2_linux-x64_bin.tar.gz -C /usr/local/java

查看解压后的目录名(例如jdk-17.0.2)

ls /usr/local/java

命令解释

mkdir -p /path/to/directory
  • 递归创建目录 :当你指定的路径中某些目录不存在时,mkdir -p 会自动创建这些缺失的父目录。

  • 不会报错 :如果目录已经存在,mkdir -p 不会报错,命令会正常执行。

    tar -xzvf openjdk-17.0.2_linux-x64_bin.tar.gz -C /usr/local/java

tar -xzvf 是用来解压 .tar.gz.tgz 文件的命令。

  • x:表示 解压(extract)。
  • z:表示 通过 gzip 压缩格式进行解压(unzip the gzip file)。
  • v:表示 显示详细信息,在解压时列出文件名。这个选项是可选的,用来查看解压过程中有哪些文件被处理。
  • f:表示 指定文件,后面需要跟要解压的 .tar.gz 文件名。

2,配置环境变量

编辑环境变量文件

打开用户环境变量配置文件(以bash为例)

nano ~/.bashrc # 或 ~/.bash_profile、~/.zshrc(根据Shell类型)

命令解释

  • nano:是一个命令行下的文本编辑器。它非常简单,适合快速编辑文件。
  • ~/.bashrc :是当前用户主目录下的 Bash 配置文件。每当你打开一个新的终端时,Bash shell 会加载该文件。在这个文件中,你可以设置环境变量、命令别名、shell 提示符样式等。

添加以下内容到文件末尾

需要注意,不能有空格

手动安装配置

export JAVA_HOME=/usr/local/java/jdk-17.0.2 # 替换为实际解压路径

export PATH=JAVA_HOME/bin:PATH

export CLASSPATH=.:JAVA_HOME/lib/dt.jar:JAVA_HOME/lib/tools.jar

包管理器安装配置(若使用方式2)

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

export PATH=JAVA_HOME/bin:PATH

使配置生效

source ~/.bashrc # 根据实际配置文件选择

验证安装

# 检查Java版本
java -version

# 输出应类似:
openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode)

**# 检查JAVA_HOME
echo $JAVA_HOME

输出:/usr/local/java/jdk-17.0.2**

自动化脚本安装示例:

#!/bin/bash

一键安装并配置JDK 17(手动方式)

JDK_URL="https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz"

INSTALL_DIR="/usr/local/java"

下载并解压

sudo mkdir -p $INSTALL_DIR

wget $JDK_URL -O /tmp/jdk17.tar.gz

sudo tar -xzvf /tmp/jdk17.tar.gz -C $INSTALL_DIR

配置环境变量

echo "export JAVA_HOME=INSTALL_DIR/(ls $INSTALL_DIR)" >> ~/.bashrc

echo 'export PATH=JAVA_HOME/bin:PATH' >> ~/.bashrc

source ~/.bashrc

验证

java -version

二,Java代码准备

编写一个简单的让程序不断创建新对象,然后GC在不停地回收,但是又回收不掉地样例。并打包为jar包给到我们的liunx服务器。

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class Main {
    // 内存泄漏:让对象无法被GC回收
    private static final List<byte[]> LEAK_LIST = new ArrayList<>();

    // CPU密集型任务开关
    private static final AtomicBoolean RUNNING = new AtomicBoolean(true);

    public static void main(String[] args) {
        // 内存泄漏线程:持续分配内存,触发频繁GC
        new Thread(() -> {
            while (true) {
                // 每次分配1MB内存(不会被回收)
                LEAK_LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(100); // 控制内存分配速度
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "MemoryLeakThread").start();

        // CPU密集型线程:死循环计算,占用CPU
        new Thread(() -> {
            while (RUNNING.get()) {
                // 无意义但耗CPU的计算
                double result = 0;
                for (int i = 0; i < 100000; i++) {
                    result += Math.sin(i) * Math.cos(i);
                }
            }
        }, "CpuIntensiveThread").start();
    }
}

打包为可执行jar包

将jar包给到linux

这里我用的vmware的文件共享区进行的共享的。

在虚拟机的设置里面

在选项这里进行配置一下

vmware的文件共享区在Linux中的/mnt/hgfs/ 文件下

如果**/mnt/hgfs 目录为空**

  • 可能原因

    • VMware Tools未正确安装。

    • 共享文件夹未启用或配置错误。

    • 自动挂载服务未运行。

解决方案
步骤1:检查VMware Tools状态

# 查看服务是否运行(Ubuntu/Debian)
systemctl status vmware-tools.service

# 重启服务(如果未运行)
sudo systemctl restart vmware-tools.service

如果未安装 VMware Tools则

# 检查 open-vm-tools 状态

systemctl status open-vm-tools

# 若服务存在且未运行,

启动服务

sudo systemctl start open-vm-tools

sudo systemctl enable open-vm-tools

步骤2:手动挂载共享文件夹(临时生效)

# 创建挂载点(若目录不存在)
sudo mkdir -p /mnt/hgfs

# 手动挂载
sudo vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other

#如果因为权限不足,可以加权限

sudo usermod -aG root rojer(rojer是用户名)

步骤3:配置开机自动挂载(永久生效)

# 编辑/etc/fstab文件
sudo nano /etc/fstab

# 添加以下行(保存退出)
.host:/ /mnt/hgfs fuse.vmhgfs-fuse allow_other,defaults 0 0

# 重新挂载
sudo mount -a

准备调试工具

使用阿里的arthas

arthas/README_CN.md at master · alibaba/arthas · GitHub

下载 Arthas 的 arthas-boot.jar 文件

curl -O https://arthas.aliyun.com/arthas-boot.jar

三,开始测试

使用以下JVM参数启动程序,加速问题暴露(JDK1.8版本):

java -Xms100m -Xmx100m # 限制堆内存为100MB,加速GC触发

-XX:+UseG1GC # 使用G1垃圾回收器(观察GC日志)

-XX:+PrintGCDetails # 打印GC详细信息

-XX:+PrintGCDateStamps # 显示GC时间戳

-jar

GcDemo.jar

Java 9 及以上改用 -Xlog:gc

java -Xms100m -Xmx100m -XX:+UseG1GC -Xlog:gc -jar GCdemo.jar

启动之后问题出现

现象验证方法

观察GC频繁:

1,使用ps -ef | grep java

找到java进程的pid

2,通过top命令观察java进程的实时状态

2,使用 jstat -gc <pid> 1000(每秒刷新):

观察GC回收状况,从上面命令可以看出内存资源并不紧张,所以这里只做展示

YGC(Young GC次数)和 FGC(Full GC次数)会快速上升。

从上面可以看出fullGC的次数才3次,且回收时间不大,说明内存情况很健康。又从top中看到cpu资源已经打满,说明有程序在疯狂占用cpu的计算资源。

这里解释一下上图的代表意思

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
| | 字段 | 说明 | |----|----| |---------|------------------------------------------------------| | S0C | Survivor Space 0 Capacity(幸存区 0 容量),表示幸存区 0 的容量。 | |---------|------------------------------------------------------| | S1C | Survivor Space 1 Capacity(幸存区 1 容量),表示幸存区 1 的容量。 | |---------|---------------------------------------------------------------| | S0U | Survivor Space 0 Utilization(幸存区 0 使用量),表示幸存区 0 当前使用的内存量。 | |---------|---------------------------------------------------------------| | S1U | Survivor Space 1 Utilization(幸存区 1 使用量),表示幸存区 1 当前使用的内存量。 | |--------|--------------------------------------------| | EC | Eden Space Capacity(伊甸园区容量),表示伊甸园区的容量。 | |--------|---------------------------------------------------| | EU | Eden Space Utilization(伊甸园区使用量),表示伊甸园区当前的使用量。 | |--------|----------------------------------------------| | OC | Old Generation Capacity(老年代容量),表示老年代的容量。 | |--------|-------------------------------------------------------| | OU | Old Generation Utilization(老年代使用量),表示老年代当前使用的内存量。 | |--------|-----------------------------------------| | MC | Metaspace Capacity(元空间容量),表示元空间的容量。 | |--------|--------------------------------------------------| | MU | Metaspace Utilization(元空间使用量),表示元空间当前使用的内存量。 | |----------|----------------------------------------------------------| | CCSC | Compressed Class Space Capacity(压缩类空间容量),表示压缩类空间的容量。 | |----------|-----------------------------------------------------------------| | CCSU | Compressed Class Space Utilization(压缩类空间使用量),表示压缩类空间的当前使用量。 | |---------|---------------------------------------------------------------------| | YGC | Young Generation GC Count(年轻代垃圾回收次数),表示年轻代的垃圾回收次数(包括 Minor GC)。 | |----------|----------------------------------------------------------------| | YGCT | Young Generation GC Time(年轻代垃圾回收时间),表示年轻代垃圾回收所花费的时间(单位:秒)。 | |---------|-------------------------------------------------------------| | FGC | Full GC Count (Full GC 次数),表示 Full GC(完全垃圾回收)发生的次数。 | |----------|------------------------------------------------------------| | FGCT | Full GC Time (Full GC 时间),表示 Full GC 所花费的时间(单位:秒)。 | |---------|------------------------------------------------| | CGC | Concurrent GC Count(并发 GC 次数),表示并发垃圾回收的次数。 | |----------|------------------------------------------------| | CGCT | Concurrent GC Time(并发 GC 时间),表示并发垃圾回收的总时间。 | |---------|----------------------------------------------------| | GCT | Total GC Time(总垃圾回收时间),表示自启动以来的所有垃圾回收时间(单位:秒)。 | | |

详细观察cpu的占用情况

top -p <pid>

然后 按 H(大写的 H)切换到线程视图:

  • 按下 H 键后,top 会从显示进程列表切换到显示线程列表,每个线程会显示为一个单独的条目。

使用Arthas诊断:

1. 启动Arthas

java -jar arthas-boot.jar

这里选择所要监控的程序。

需要注意,如果出现连接异常,且

复制代码
~/logs/arthas/arthas.log

路径下log中有

Arthas server agent start...

java.lang.OutOfMemoryError: Java heap space

说明你当前的机器在不断消耗内存,导致arthas起不起来!

因为在jvm初始化的时候,这个阶段非常吃内存。

查看线程CPU占用

当前系统的实时数据面板,按 ctrl+c 退出。

dashboard

这里可以看出cpu已经打满,但是堆内存情况良好。

追踪高cpu线程

thread

参数名称 参数说明
id 线程 id
[n:] 指定最忙的前 N 个线程并打印堆栈
[b] 找出当前阻塞其他线程的线程
[i <value>] 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200
[--all] 显示所有匹配的线程

这里就已经能定位到问题了。我这个样例比较简单,就一个main类。大家可以构建复杂一点的项目,进行一点点调试看问题。

使用java原生的也能定位到问题。可能比较繁琐一点。

使用 jstack 获取线程堆栈信息

使用 jvisualvm 进行图形化分析

等等。

其它arthas常用方法

  • trace:追踪方法调用的堆栈,帮助你查看方法内部的执行情况。
  • monitor:监控方法的执行,特别是性能问题,像慢方法。
  • stack:查看线程堆栈,特别是线程阻塞和死锁问题。
  • watch:监控方法调用,查看参数和返回值,帮助定位参数错误。
  • heapdump:生成堆转储文件,帮助分析内存问题。
  • jstack:查看 Java 进程的堆栈信息,诊断崩溃或线程死锁。

参考官方文档使用说明!

auth | arthas

相关推荐
唐青枫25 分钟前
Linux egrep 命令使用详解
linux
WeiLai11121 小时前
面试基础--Redis 缓存穿透、缓存击穿、缓存雪崩深度解析
java·redis·分布式·后端·缓存·面试·架构
zctel1 小时前
java中小型公司面试预习资料(二):Redis
java·redis·面试
独行soc1 小时前
2025年渗透测试面试题总结-字某跳动-安全研究实习生(三面)(题目+回答)
linux·服务器·安全·web安全·面试·职场和发展
爱吃烤鸡翅的酸菜鱼2 小时前
Java【网络原理】(3)网络编程续
java·运维·服务器·网络
daizikui2 小时前
LVS+Nginx接入层架构图
服务器·nginx·lvs
╰つ゛木槿6 小时前
Spring Boot 调用DeepSeek API的详细教程
java·spring boot·后端·deepseek
hhw1991127 小时前
c#面试题整理6
java·开发语言·c#
movee7 小时前
一台低配云主机也能轻松愉快地玩RDMA
linux·人工智能·后端
饭九钦vlog7 小时前
机器人匹诺曹机制,真话假话平衡机制
服务器·经验分享·新浪微博