一,前提准备
最基本前提:你需要有liunx环境,如果没有请参考其它文献在自己得到local建立一个虚拟机去进行测试。
有了虚拟机之后,你还需要安装jdk和配置环境变量
1. 安装JDK(以OpenJDK 17为例)
下载JDK
进入用户主目录
cd ~
下载OpenJDK 17(以.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(手动方式)
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
文件

三,开始测试
使用以下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 进程的堆栈信息,诊断崩溃或线程死锁。
参考官方文档使用说明!