在windows上,我们安装jdk之后,jdk安装包的bin目录下自带很多exe文件。
在 linux 中,一般自带了 OpenJdk, 一般情况下 JPS 等命令不能用,要么选择去安装 JPS 等插件,要么把 OpenJdk 卸载,去重新安装 Oracle 的 JDK,我推荐后者。
列出当前机器上正在运行的虚拟机进程,JPS 从操作系统的临时目录上去找(所以有一些信息可能显示不全)。类似于linux下的pid查询
/**
* @author
* VM参数
* -Xms30m -Xmx30m -XX:MaxMetaspaceSize=30m -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops
*
*
*/
public class JVMObject {
public final static String MAN_TYPE = "man"; // 常量
public static String WOMAN_TYPE = "woman"; // 静态变量
public static void main(String[] args)throws Exception {
Teacher T1 = new Teacher();
T1.setName("Jack");
T1.setSexType(MAN_TYPE);
T1.setAge(36);
for(int i =0 ;i<15 ;i++){
System.gc();//主动触发GC 垃圾回收 15次--- T1存活
}
Teacher T2 = new Teacher();
T2.setName("Tom");
T2.setSexType(MAN_TYPE);
T2.setAge(18);
Thread.sleep(Integer.MAX_VALUE);//线程休眠
}
}
class Teacher{
String name;
String sexType;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSexType() {
return sexType;
}
public void setSexType(String sexType) {
this.sexType = sexType;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
jps命令格式:jps [ options ] [ hostid ]
-q :仅仅显示进程,
-m:输出主函数传入的参数. 下的 hello 就是在执行程序时从命令行输入的参数
-l: 输出应用程序主类完整 package 名称或 jar 完整名称.
-v: 列出 jvm 参数, -Xms20m -Xmx50m 是启动程序指定的 jvm 参数
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
jstat命令格式为:
jstat [ option vmid [interval[s|ms] [count]] ]
VMID:本地虚拟机进程ID,也就是JPS查询出来的运行时进程id
选项option代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状况
option | 说明 |
---|---|
-class | (类加载器) |
-compiler | (JIT) |
-gc | (GC 堆状态) |
-gccapacity | (各区大小) |
-gccause | (最近一次 GC 统计和原因) |
-gcnew | (新区统计) |
-gcnewcapacity | (新区大小) |
-gcold | (老区统计) |
-gcoldcapacity | (老区大小) |
-gcpermcapacity | (永久区大小) |
-gcutil | (GC 统计汇总) |
-printcompilation | (HotSpot 编译统计) |
参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:
jstat -gc 2764 250 20
参考上面的代码块,我们查询jstat信息
名称 | 说明 |
---|---|
S0C | 第一个幸存区(From 区)的大小 |
S1C | 第二个幸存区(To 区)的大小 |
S0U | 第一个幸存区的使用大小 |
S1U | 第二个幸存区的使用大小 |
EC | 伊甸园(Eden)区的大小 |
EU | 伊甸园(Eden)区的使用大小 |
OC | 老年代大小 |
OU | 老年代使用大小 |
MC | 方法区大小 |
MU | 方法区使用大小 |
CCSC | :压缩类空间大小 |
CCSU: | 压缩类空间使用大小 |
YGC | 年轻代垃圾回收次数 |
YGCT :年轻代垃圾回收消耗时间
FGC :老年代垃圾回收次数
FGCT :老年代垃圾回收消耗时间
GCT :垃圾回收消耗总时间
jinfo命令格式:
jinfo [ option ] pid
查看和修改虚拟机的参数
jinfo –sysprops 可以查看由 System.getProperties()取得的参数
jinfo –flag 未被显式指定的参数的系统默认值
jinfo –flags(注意 s)显示虚拟机的参数
VM 参数分类
JVM 的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
1 。标准: - 开头,所有的 HotSpot 都支持
保证 Java 虚拟机(JVM)的所有实现都支持标准选项。它们用于执行常见操作,例如检查 JRE 版本,设置类路径,启用详细输出等
2.标准:-X 开头,特定版本 HotSpot 支持特定命令
非标准选项是特定于 Java HotSpot 虚拟机的通用选项,因此不能保证所有 JVM 实现都支持它们,并且它们可能会发生变化。这些选项以开头 -X 。
例如:-Xms30m -Xmx30m -Xss1m
3 . 高级选项:以开头-XX:
这些是开发人员选项,用于调整 Java HotSpot 虚拟机操作的特定区域,这些区域通常具有特定的系统要求,并且可能需要对系统配置参数的特权访问。也不能保证所有 JVM 实现都支持它们,并且它们可能会发生变化。
在 windows 上可以通过以下 java -XX:+PrintFlagsFinal –version 查询所有-XX 的
注意 :manageable 的参数 , 代表可以运行时修改 。
演示例子如下:首先我们得知 PrintGC 这个 XX 参数是可以运行时修改的。
jinfo –flag -[参数] pid 可以修改参数
import java.util.LinkedList;
import java.util.List;
/**
*
* VM参数: -XX:+PrintGCDetails
*
*/
public class StopWorld {
/*不停往list中填充数据*/
//就使用不断的填充 堆 -- 触发GC
public static class FillListThread extends Thread{
List<byte[]> list = new LinkedList<>();
@Override
public void run() {
try {
while(true){
if(list.size()*512/1024/1024>=990){
list.clear();
System.out.println("list is clear");
}
byte[] bl;
for(int i=0;i<100;i++){
// bl = new byte[512];
bl = new byte[512];
list.add(bl);
}
Thread.sleep(1);
}
} catch (Exception e) {
}
}
}
/*每100ms定时打印*/
public static class TimerThread extends Thread{
public final static long startTime = System.currentTimeMillis();
@Override
public void run() {
try {
while(true){
long t = System.currentTimeMillis()-startTime;
// System.out.println(t/1000+"."+t%1000);
Thread.sleep(100); //0.1s
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) {
//填充对象线程和打印线程同时启动
FillListThread myThread = new FillListThread(); //造成GC,造成STW
TimerThread timerThread = new TimerThread(); //时间打印线程
myThread.start();
timerThread.start();
}
}
总结:通过 jinfo 命令,我可以在生产上临时打开一下 GC 日志或者进行一些数据的配置。(不需要重启应用条件下),也是我们去排查问题的一个关键命令
用于生成堆转储快照(一般称为 heapdump 或 dump 文件)。jmap 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalize 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。和 jinfo 命令一样,jmap 有不少功能在 Windows 平台下都是受限的,除了生成 dump 文件的-dump 选项和用于查看每个类的实例、空间占用统计的-histo 选项在所有操作系统都提供之外,其余选项都只能在 Linux/Solaris 下使用。
-heap 打印 heap 的概要信息
jmap –heap
项目 | Value |
---|---|
Heap Configuration: | -##堆配置情况,也就是 JVM 参数配置的结果[平常说的 tomcat 配置 JVM 参数,就是在配置这些] |
MinHeapFreeRatio | -##最小堆使用比例 |
MaxHeapFreeRatio | -##最大堆可用比例 |
MaxHeapSize | -= 2118123520 (2020.0MB) ##最大堆空间大小 |
NewSize | -= 44564480 (42.5MB) ##新生代分配大小 |
MaxNewSize | -= 705691648 (673.0MB) ##最大可新生代分配大小 |
OldSize | - = 89653248 (85.5MB) ##老年代大小 |
NewRatio | -= 2 ##新生代比例 |
SurvivorRatio | -= 8 ##新生代与 suvivor 的比例 |
PermSize | -##perm 区 永久代大小 |
MaxPermSize | -##最大可分配 perm 区 也就是永久代大小 |
Heap Usage: | -##堆使用情况【堆内存实际的使用情况】 |
New Generation (Eden + 1 Survivor Space): | - ##新生代(伊甸区 Eden 区 + 幸存区 survior(1+2)空间) |
capacity | -= 241631232 (230.4375MB) ##伊甸区容量 |
used | -##已经使用大小 |
free | - ##剩余容量 |
used | -##使用比例 |
Eden Space: | - ##伊甸区 |
capacity | -=235929600 (225.0MB) ##伊甸区容量 |
used | -= 42469296 (40.50187683105469MB) ##伊甸区使用 |
free | -= 193460304 (184.4981231689453MB) 18.0008341471354 ##伊甸区当前剩余容量 |
18% used | -##伊甸区使用情况 |
From Space: | - ##survior1 区 |
capacity | -= 234881024 (224.0MB) ##survior1 区容量 |
used | -= 0 ##surviror1 区已使用情况 |
free | -= 0(0.0MB) ##surviror1 区剩余容量 |
100% | -##survior1 区使用比例 |
To Space: | - ##survior2 区 |
capacity | -=234881024 (224.0MB)##survior2 区容量 |
used | -= 0 (0.0MB) ##survior2 区已使用情况 |
free | -=234881024 (224.0MB) ##survior2 区剩余容量 |
0.0% used | -## survior2 区使用比例 |
PS Old Generation: | -##老年代使用情况 |
capacity | -= 1412431872 (1347.0MB) ##老年代容量 |
used | -= 867655240 (827.4605178833008MB) ##老年代已使用容量 |
free | -= 544776632 (519.5394821166992MB) ##老年代剩余容量 |
used | -##老年代使用比例 |
-histo 打印每个 class 的实例数目, 内存占用, 类全名信息.
jmap –histo
jmap –histo:live 如果 live 子参数加上后,只统计活的对象数量.
但是这样显示太多了,一般在 linux 上会这么操作
jmap –histo 1196 | head -10
(这样只会显示排名前 10 的数据)
不太重要的参数
-finalizerinfo 打印正等候回收的对象的信息,还有 jmap –clstats 这个命令最好也不要去使用
-dump 生成的堆转储快照(比较重要)
jmap -dump:live,format=b,file=heap.bin
Sun JDK 提供 jhat(JVM Heap Analysis Tool)命令与 jmap 搭配使用,来分析 jmap 生成的堆转储快照
dump文件一般内存比较大,在生产环境文件流导出会占用cpu资源。
jhat
jhat dump 文件名
后屏幕显示“Server is ready.”的提示后,用户在浏览器中键入 http://localhost:7000/就可以访问详情
使用 jhat 可以在服务器上生成堆转储文件分析(一般不推荐,毕竟占用服务器的资源,比如一个文件就有 1 个 G 的话就需要大约吃一个 1G 的内存资源)
(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。
在代码中可以用 java.lang.Thread 类的 getAllStackTraces()方法用于获取虚拟机中所有线程的 StackTraceElement 对象。使用这个方法可以通过简单的几行代码就完成 jstack 的大部分功能,在实际项目中不妨调用这个方法做个管理员页面,可以随时使用浏览器来查看线程堆栈。一般来说 jstack 主要是用来排查是否有死锁的情况
/**
*类说明:演示死锁的产生
*/
public class NormalDeadLock {
private static Object No13 = new Object();//第一个锁
private static Object No14 = new Object();//第二个锁
//第一个拿锁的方法
private static void markDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (No13){
System.out.println(threadName+" get NO13");
Thread.sleep(100);
synchronized (No14){
System.out.println(threadName+" get NO14");
}
}
}
//第二个拿锁的方法
private static void jackDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (No14){
System.out.println(threadName+" get NO14");
Thread.sleep(100);
synchronized (No13){
System.out.println(threadName+" get NO13");
}
}
}
//子线程,代表Jack老师
private static class Jack extends Thread{
private String name;
public Jack(String name) {
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try {
markDo();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//主线程,代表markj老师
Thread.currentThread().setName("mark");
Jack jack = new Jack("jack");
jack.start();
jackDo();
}
}
查看当前进程号
执行命令 jstack 17912
命令工具总结
生产服务器推荐开启
-XX:-HeapDumpOnOutOfMemoryError 默认关闭,建议开启,在 java.lang.OutOfMemoryError 异常出现时,输出一个 dump 文件,记录当时的堆内存快照。
-XX:HeapDumpPath=./java_pid.hprof 用来设置堆内存快照的存储文件路径,默认是 java 进程启动位置。
调优之前开启、调优之后关闭
-XX:+PrintGC
调试跟踪之 打印简单的 GC 信息参数:
-XX:+PrintGCDetails, +XX:+PrintGCTimeStamps
打印详细的 GC 信息
-Xlogger:logpath
设置 gc 的日志路,如: -Xlogger:log/gc.log, 将 gc.log 的路径设置到当前目录的 log 目录下.应用场景: 将 gc 的日志独立写入日志文件,将 GC 日志与系统业务日志进行了分离,方便开发人员进行追踪分析。
考虑使用
-XX:+PrintHeapAtGC, 打印推信息 参数设置: -XX:+PrintHeapAtGC
应用场景: 获取 Heap 在每次垃圾回收前后的使用状况
-XX:+TraceClassLoading
参数方法: -XX:+TraceClassLoading
应用场景: 在系统控制台信息中看到 class 加载的过程和具体的 class 信息,可用以分析类的加载顺序以及是否可进行精简操作。
-XX:+DisableExplicitGC 禁止在运行期显式地调用 System.gc()
JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
管理远程进程需要在远程程序的启动参数中增加:
-Djava.rmi.server.hostname=……
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment、Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进行信息收集和参数动态调整。启动在jdk的bin目录下运行jconsole.exe文件
通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询
插件中心地址https://visualvm.github.io
但是注意版本问题,不同的 JDK 所带的 visualvm 是不一样的,下载插件时需要下对应的版本。一般来说,这个工具是本机调试用,一般生产上来说,你一般是用不了的(除非启用远程连接)
官方文档参考 https://arthas.aliyun.com/
https://alibaba.github.io/arthas/
Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位
和诊断
下载和安装
不需要安装,就是一个 jar 包
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
启动 arthas 的 jar 包是 arthas-boot.jar
快速入门
1、直接 java -jar arthas-boot.jar。选择 attach 的进程绑定
通过 jps 命令快速查找 java 进程,再次直接绑定 java -jar arthas-boot.jar pid 启动 arthas 工具 attach 到目标进程,上图直接选择2进入想要查看的进程
java -jar arthas-boot.jar 16104也行
进入 arthas 后命令行前面出现标识,上图可以看到。
dashboard
注意在 arthas 中,有 tab 键填充功能,所以比较好用。但是这个界面是实时刷新的,一般 5s 刷新一次,使用 q 键退出刷新(没有退出arthasq)
Thread
这个命令和 jstack 很相似,但是功能更加强大,主要是查看当前 JVM 的线程堆栈信息同时可以结合使用 thread –b 来进行死锁的排查死锁。参数解释:
-n 指定最忙的前 n 个线程并打印堆栈
-b 找出阻塞当前线程的线程
-i 指定 cpu 占比统计的采样间隔,单位为毫秒
thread –h 显示帮助
如果有死锁,会有红色的字提醒着,这个阻塞的线程已经被另外一个线程阻塞。
thread -i 1000 -n 3 每过 1000 毫秒进行采样,显示最占 CPU 时间的前 3 个线程
thread --state WAITING 查看处于等待状态的线程
Jad
反编译指定已加载类的源码
trace
使用 trace 命令可以跟踪统计方法耗时。
继续跟踪耗时高的方法,然后再次访问
比如使用一个 Springboot 项目(当然,不想 Springboot 的话,你也可以直接在 UserController 里 main 方法启动)控制层 getUser 方法调用了 userService.get(uid);,这个方法中分别进行 check、service、redis、mysql 等操作操作。就可以根据这个命令跟踪出来哪里的耗时最长。
执行命令 tarce 包路径 方法名
monitor
每 5 秒统计一次 cn.enjoyedu.demo.controller.DemoController 类的 test 方法执行情况
watch
观察方法的入参出参信息
#查看入参和出参
$ watch cn.enjoyedu.demo.controller.DemoController test ‘{params[0],returnObj}’