当前位置: 首页 > article >正文

从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服务器。

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的计算资源。

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

字段说明
S0CSurvivor Space 0 Capacity(幸存区 0 容量),表示幸存区 0 的容量。
S1CSurvivor Space 1 Capacity(幸存区 1 容量),表示幸存区 1 的容量。
S0USurvivor Space 0 Utilization(幸存区 0 使用量),表示幸存区 0 当前使用的内存量。
S1USurvivor Space 1 Utilization(幸存区 1 使用量),表示幸存区 1 当前使用的内存量。
ECEden Space Capacity(伊甸园区容量),表示伊甸园区的容量。
EUEden Space Utilization(伊甸园区使用量),表示伊甸园区当前的使用量。
OCOld Generation Capacity(老年代容量),表示老年代的容量。
OUOld Generation Utilization(老年代使用量),表示老年代当前使用的内存量。
MCMetaspace Capacity(元空间容量),表示元空间的容量。
MUMetaspace Utilization(元空间使用量),表示元空间当前使用的内存量。
CCSCCompressed Class Space Capacity(压缩类空间容量),表示压缩类空间的容量。
CCSUCompressed Class Space Utilization(压缩类空间使用量),表示压缩类空间的当前使用量。
YGCYoung Generation GC Count(年轻代垃圾回收次数),表示年轻代的垃圾回收次数(包括 Minor GC)。
YGCTYoung Generation GC Time(年轻代垃圾回收时间),表示年轻代垃圾回收所花费的时间(单位:秒)。
FGCFull GC Count(Full GC 次数),表示 Full GC(完全垃圾回收)发生的次数。
FGCTFull GC Time(Full GC 时间),表示 Full GC 所花费的时间(单位:秒)。
CGCConcurrent GC Count(并发 GC 次数),表示并发垃圾回收的次数。
CGCTConcurrent GC Time(并发 GC 时间),表示并发垃圾回收的总时间。
GCTTotal 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


http://www.kler.cn/a/527410.html

相关文章:

  • JSP 标准标签库(JSTL)
  • Maven的单元测试
  • GitHub Actions定时任务配置完全指南:从Cron语法到实战示例
  • JavaScript系列(50)--编译器实现详解
  • java.math 包 中的 BigDecimal 类(详细案例拆解)
  • WINDOWS安装eiseg遇到的问题和解决方法
  • Day31-【AI思考】-深度学习方法论全解析——科学提升学习效率的终极指南
  • Synology 群辉NAS安装(7)lsof指令和synogear
  • 半导体SAP管理系统:数字化转型的驱动力
  • ComfyUI使用教程、开发指导、资源下载
  • 微服务配置中心 Apollo解析——Portal 关联 Namespace
  • 什么是麦克斯韦方程
  • 2025年01月31日Github流行趋势
  • 3 Spark SQL
  • 【leetcode】T541 (两点反思)
  • 新一代搜索引擎,是 ES 的15倍?
  • ARM嵌入式学习--第十一天(中断处理 , ADC)
  • 编程大模型之—Qwen2.5-Coder
  • JVM方法区
  • Array,String,Number
  • ZZNUOJ(C/C++)基础练习1021——1030(详解版)
  • 白话DeepSeek-R1论文(二)| DeepSeek-R1:AI “升级打怪”,从“自学成才”到“全面发展”!
  • 数据结构-Stack和栈
  • python学opencv|读取图像(五十二)使用cv.matchTemplate()函数实现最佳图像匹配
  • FBX SDK的使用:基础知识
  • mysql索引snkw