这里简单介绍一下各种调优用到的工具
一,环境准备
首先我们需要准备好Java环境,和win上的jdk环境(图形化界面如jconsole只有jdk中有)。
有这样一个类Prolem,每个线程都会带来100个垃圾对象,线程new完100个垃圾对象基本就结束了,等待3秒后重新开始制造新的垃圾对象,而线程池中有50个这样的线程。毫无疑问,这样会造成JVM中内存的占用的彪高。
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
*/
public class Problem {
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birthdate = new Date();
public void m() {
}
}
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (;;){
modelFit();
Thread.sleep(100);
}
}
private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
将这个类复制到Linux中,javac Problem
编译Problem类到当前文件夹下,然后使用java -Xms20M -Xmx20M -XX:+PrintGC -XX:+PrintGCDetails Problem
,指定堆大小为20M不变,这样使得堆GC频率会很频繁,-XX:+PrintGCDetails开启GC细节日志,直接启动Problem中的主方法。
二,主要工具
在收到报警后,如何检查问题出在哪里呢?
top
命令,查看本台Linux机器中资源占用情况,可以看到CPU占满的是pid为27282的Java进程。内存没有显示占用多少是因为我指定了堆内存为20M,其实内存此时已经占用满了。
top -Hp Pid
命令,top -Hp加上Java进程的pid,展示Java内部线程的资源占用情况。在这个栗子中可以看到cpu很高,是因为垃圾占用一直很高,于是GC线程一直工作。
GC日志,这里的GC日志是我们直接使用-XX:+PrintGCDetails在前台打印的日志,生产看JVM日志要去分析jmap的dump文件。可以看到先是YGC,回收速度小于垃圾对象制造速度,于是很快就变成了Full GC,后期甚至还会堆内存溢出。
jps
,列出Java进程的进程ID和主类名称。这个命令通常用于查看正在运行的Java进程,以便进行监控或管理。通过这个命令查看Problem类的进程的pid,相比top,快速便捷,方便后面使用jmap等工具。
jstack
定位线程堆栈信息,看到线程在等待
jmap
,比较重要的分析工具。
jmap -histo pid | head number
,map - histo
命令用于生成Java堆内存中实例对象数量及占用内存的直方图,显示堆中各个对象类型的数量和大小。后面跟上管道符和head命令,展示占用靠前多少的对象。
jmap -dump
,使用jmap去dump堆栈信息。分析dump文件,如果是上G的文件,直接用vim太Low了效率也低。。
分析dump文件的工具,eclipse有MAT
(免费),idea有JProfiler
插件(收费,499美元),好货不便宜啊。。此外也可以使用jvirsualvm
来进行dump文件分析,jhat
也可以,但是比较古老。
jmap -heap pid
,打印堆内存信息。
[root@192 code]# jmap -heap 26983
Attaching to process ID 26983, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.361-b09
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 209715200 (200.0MB)
NewSize = 69730304 (66.5MB)
MaxNewSize = 69730304 (66.5MB)
OldSize = 139984896 (133.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 16777216 (16.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 4294963200 (4095.99609375MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 52428800 (50.0MB)
used = 18120048 (17.280624389648438MB)
free = 34308752 (32.71937561035156MB)
34.561248779296875% used
From Space:
capacity = 8650752 (8.25MB)
used = 8639984 (8.239730834960938MB)
free = 10768 (0.0102691650390625MB)
99.87552527225378% used
To Space:
capacity = 8650752 (8.25MB)
used = 0 (0.0MB)
free = 8650752 (8.25MB)
0.0% used
PS Old Generation
capacity = 139984896 (133.5MB)
used = 14180352 (13.5234375MB)
free = 125804544 (119.9765625MB)
10.129915730337078% used
1230 interned Strings occupying 70952 bytes.
jmap -clstats pid
,打印Java进程的元数据信息,也就是方法区的信息
arthas
,阿里的开源工具,用attach的方式监控,相当于对流进行代理,而不是图形化界面实时命令,因此arthas对系统的负荷很小,命令行界面也比较友好。
安装arthas[3],安装很简单,直接下载jar包后启动即可。
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
回车后,arthas会显示检测到的Java进程,按顺序从1开始排列,我们输入1,选择把arthas挂到第一个进程上。看到下面的ARTHAS图形,启动成功。
arthas启动后(在启动arthas之前,我将Java进程停掉,堆内存指定为200M并重新启动,不然20M容易导致arthas挂失败)。可以看到Linux用户从root变为了arthas,arthas用户可以使用如下命令。
jvm
,类似于jinfo的效果,显示jvm的各种参数信息
dashboard
,监控大盘,可以看到线程、内存的各种状态
thread
,只查看进程
jad
,反编译,定位动态代理生成类的问题、版本问题等。
redefine
,热替换,可以在不用重新部署项目的情况下,将代码替换掉。但是有一些限制,只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性。
比如我们有2个类如下:
import java.io.IOException;
public class TestRedefine {
public static void main(String[] args) throws IOException {
while (true){
System.in.read();
new RedefineNode().out();
}
}
}
public class RedefineNode {
public static void out(){
System.out.println(1);
}
}
将其启动后,输入字母,只能得到输出1.
[root@192 code]# java TestRedefine
a
1
1
a
1
1
s
1
1
现在我们直接vim类RedefineNode ,将1换成2,并且javac RedefineNode.java。编译成class文件。最终在arthas中将其热替换,再用反编译查看代码,发现已经换成了2.
测试输入输出,输出变成了2,热替换成功。
[root@192 code]# java TestRedefine
a
1
1
a
1
1
s
1
1
a
2
2
d
2
2
s
2
2
heapdump /root/gc2024051401.hprof
,arthas对jvm进行dump,格式要为hprof。
配合jdk工具jhat
对dump文件进行分析,
Linux ip+7000端口,打开jhat网址
jhat的各项是按照字母顺序排列的,直接拉到最下方,others才是有用的。
如jhat统计类的实例对象数量
OQL
,通过语句查询想要的结果
或者也可以把dump文件拿出来,用jvirsualvm的装入,载入dump文件来分析。比如这里我用finalshell,将dump文件下载后,使用jvrisualvm分析。
下载在桌面自动创建的文件夹中,将其装入,查看dump文件中的信息,可以看到哪个对象是最多的。
三,作用不大的工具
jinfo pid
,可以看到一些java进程的配置信息,作用不大
jstat -gc pid
。每500毫秒打印一次gc日志,但是信息很抽象,比较晦涩
jconsole
,下图能看到一些类似jinfo的信息。jconsole等调试工具在jdk中包含,在win中远程连接Linx即可,但是远程连接图形化界面,相当于实时使用命令监控,对线上系统有很大压力,因此图形化界面如jconsole、jvirsualvm不建议线上使用。jconsole在连接时有一些注意事项【注1】。
能看到内存在节节升高,回收完短暂的掉下去一点,然后又升高,但是没啥用,分析不出来问题
jvirsualvm
,也是jdk中自带。使用时也是有一些注意事项,放到最后【注2】。
可以看到jvirsualvm终于比jconsole好用一些了,能够看到内存中哪些对象最多,但是如果业务代码多且复杂,可能无法借此分析出结果。不过总归比jconsole好一些,同样,jvirsualvm不推荐线上使用。
中止Linux上的jstatd,就可以终止jvirsualvm的可视化。
【注1】
连接jconsole的Java进程,在启动时,需要加上以下参数,以支持JMX协议。ip换成Linux的,port不用换,是JMX协议的远程通讯端口。
java -Djava.rmi.server.hostname=192.168.18.128
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=11111
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false Problem
自己的虚拟机如果防火墙或者iptables开启,可以关闭。生产上开放出端口即可。
然后在win上的jconsole上连接192.168.18.128:11111。
【注2】
Linux上,进入JAVA_HOME的bin目录下,
vi jstatd.all.policy
在其中保存如下内容:
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
启动jstatd,开启jstatd后,此时就可以用win的jvirsualvm连接Java进程了
jstatd -J-Djava.security.policy=D:\tools.policy
参考文章:
[1],使用jvisualvm的jstatd方式远程监控Java程序
[2],jstatd 启动报错解决:Could not create remote
object
[3],arthas/README_CN.md