JVM调优是面试中常问的问题,同时也是实际工作中可能遇到的难题,本文简单介绍JVM调优在实战中的应用。
在程序上线前,需要根据需求预估用户数和并发量,并按照这个目标对JVM进行规划和预调优;同时程序运行时间久了,可能会出现程序卡顿、访问变慢等情况;严重时会出现OOM导致程序崩溃。这些情况都需要进行JVM的调优。
调优的目标通常有两个:
JVM调优前,首先需要了解使用的是什么垃圾回收器。不同的垃圾回收器有不同的调优参数。随着JVM的发展,调优越来越简单,可能以后不再需要程序员对JVM进行调优。比如ZGC垃圾回收器,基本不用调优。
JDK使用的java虚拟机时Hotspot,JDK1.8使用的垃圾回收器默认是Parallel Scanvage + Parallel Old ( -XX:+UseParallelGC)。我们通常说的调优也是也是基于这两个垃圾回收器。
调优时要了解相关的命令行调优参数。 官方文档
Hotspot调优参数分类:
标准参数: - 开头,所有的hotspot都支持。java
命令可以直接查看。
非标准参数: -X开头,特定版本的Hotspot支持。 java -X
命令可以直接查看。
不稳定参数: -XX开头,下个版本可能取消,但是很多实际调优都是使用-XX参数 。
打印所有的参数列表的方式:
方式一
[superuser@t-001 ~]$ java -XX:+PrintFlagsWithComments // debug版本才能用,可以自己编译一个Hotspot
Error: VM option 'PrintFlagsWithComments' is notproduct and is available only in debug version of VM.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
[superuser@t-001 ~]$
方式二
[superuser@h-001 ~]$ java -XX:+PrintFlagsFinal
方式三
[superuser@h-001 ~]$ java -XX:+PrintFlagsInitial
查看java启动的默认参数:
[superuser@hb3-ack-test-kabu-001 ~]$ java -XX:+PrintCommandLineFlags
-XX:InitialHeapSize=524474496 -XX:MaxHeapSize=8391591936 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
常用命令:
jstack pid: 打印堆栈相关执行信息,可以用于死锁发现。
jinfo pid: 打印进程的启动详细参数、JVM正在使用的参数。
jstat -gc pid: 打印各个分代的内存使用情况。可以每个一段时间打印一次。
jvisualvm: windows图形化界面,不常用。上线之前内测可以使用。
jmap: 如果内存特别大,jmap会导致线程的停止,所以线上不能使用。
线上时不能dump下来文件,因为文件可能很大(堆内存占了多少文件就有多大),dump时java程序会暂停,线上不能dump。
jmap -histo pid | head -n 20 :查找有多少对象产生,及对象占用的内存,对象的名称。可用于内存泄露导致的OOM。
合理的设置线上GC的日志文件,有助于出现问题时快速定位。通常设置多个(比如5个)日志文件,为了便于分析,每个文件不要特别大。日志会滚动写到文件,第五个写文件满了,会从第一个文件再写。也可以每天产生一个GC日志文件。
可以使用如下参数:
-Xloggc:/opt/xxxx/logs/xxxx-xx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
Arthas是阿里开源的JVM调优工具,非常好用,很多公司都使用这个工具。
Arthas提供了强大的调优功能,有很多好用的命令。JDK自带的调优命令arthas基本都实现了。
* 下载:curl -O https://arthas.aliyun.com/arthas-boot.jar 参考:https://arthas.aliyun.com/doc/install-detail.html#arthas-boot
* 运行:java -jar arthas-boot.jar
* 运行后可以:
* 1.可以看到检测到的进程编号,需要看哪个进程,直接输入进程编号。进入进程后,可以使用很多命令:
* 1.1.dashboard: 可以查看一个仪表盘,可以综合的简单查看这个进程相关的运行情况。
* 1.2.jvm: 类似jinfo命令,查询相关参数使用情况,包括使用的垃圾回收器是什么
* 1.3.thread: 把这个进程中的所有线程展示出来,这个很有用。和jstack有点像,但是比它好用。
* a.找到对应的线程后,使用 thread tid查看线程的详细执行情况。
* b.thread | grep XXX。过滤功能,所以建议线程要起名字。
* c.thread -b,可以直接找到有死锁的线程的名字
* 3.命令 -help : 查看具体命令的用法
* 4.查找类 sc; 查找方法 sm; 找到类名和方法名称后使用trace或monitor跟踪方法的整个运行情况,相关的统计。
* 5.heapdump: 类似jmap,可以将堆内存dump下来,使用工具分析。这个命令也会暂停程序,可以内测使用。
* 6.jad 类名: 反编译某个类,类似javap。分析线上运行的版本是不是最新的代码,是不是被别人提交的代码覆盖过。
/**
* 下面案例评估: 对数据库中的信用卡进行贷款评估。该程序执行一定时间后可能频繁FullGC,然后OOM。查询什么原因。
* 启动参数:-XX:+PrintGC -Xms200M -Xmx200M
* @author 诸葛小猿
* @date 2021-03-06
*/
public class JVMTest1 {
/**
* 线程池
*/
public static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* main方法
* @param args
*/
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
// 死循环 每次去100条数
for(;;){
modFit();
Thread.sleep(100);
}
}
/**
* 模型匹配
*/
public static void modFit(){
// 取100条数据
List<CardInfo> list = getAllCard();
list.forEach(cardInfo -> {
// to do something
executor.scheduleWithFixedDelay(()->{ cardInfo.m();},2,3, TimeUnit.SECONDS);
});
}
/**
* 每次从数据库中获得100个行用卡信息
*/
public static List<CardInfo> getAllCard(){
List<CardInfo> list = new ArrayList<>();
for(int i=0; i<100; i++){
list.add(new CardInfo());
}
return list;
}
/**
* 模拟一张行用卡,
* 里面有个空的m()
*/
public static class CardInfo{
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 30;
Date birthDate = new Date();
public void m(){
}
}
}
程序运行过程中使用的内存不断升高
FullGC出现的频率越来越高
每次FullGC能回收的内存越来越少,最后基本回收不到内存
最终出现OOM
出现了内存泄露,一定是有的对象一直占着内存不释放,导致FullGC回收不了,最终OOM。那么是哪些对象呢?
通常线上不会很容易出现OOM,因为运维有监控,当内存使用到一定的阈值后,监控就会报警。就会及时定位问题,不会等到OOM时才处理。
第一步:查询哪些实例占的内存比较大,实例数比较多。获取堆内存dump文件。
方式一:使用jvm自带命令:jmap -histo pid | head -n 20
直接找到排名前20的对象。查找有多少对象产生,及对象占用的内存、对象的名称。也可以直接使用这个命令导出堆内存的dump文件。jmap
会暂停程序,堆内存小时可以使用,通常线上不能直接使用。
方式二:使用arthas的heapdump,导出堆内存的dump文件。这个命令也会暂停程序,通常线上不能直接使用。
方式三:程序启动时设置参数:-XX:+HeapDumpOnOutOfMemoryError
,程序oom后会自动生成dump文件。这种情况少,因为有监控,一般不会OOM。
第二步:使用相关的工具分析dump文件,找到占用内存使用最多的几个实例对象。比如:使用JDK自带的jvisualVM,导入dump文件进行分析。Dump文件比较大时,可能分析需要几天。
第三步:找到占用内存最多的对象后,分析他们在代码中的逻辑是什么,为什么产生了如此多的对象。
第四步:找到原因后,对代码进行优化。
关注公众号,输入“java-summary”即可获得源码。
完成,收工!
【传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。