【JVM】调优工具

这里简单介绍一下各种调优用到的工具

一,环境准备

首先我们需要准备好Java环境,和win上的jdk环境(图形化界面如jconsole只有jdk中有)。

有这样一个类Prolem,每个线程都会带来100个垃圾对象,线程new完100个垃圾对象基本就结束了,等待3秒后重新开始制造新的垃圾对象,而线程池中有50个这样的线程。毫无疑问,这样会造成JVM中内存的占用的彪高。

cpp 复制代码
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 numbermap - histo命令用于生成Java堆内存中实例对象数量及占用内存的直方图,显示堆中各个对象类型的数量和大小。后面跟上管道符和head命令,展示占用靠前多少的对象。

jmap -dump,使用jmap去dump堆栈信息。分析dump文件,如果是上G的文件,直接用vim太Low了效率也低。。

分析dump文件的工具,eclipse有MAT(免费),idea有JProfiler插件(收费,499美元),好货不便宜啊。。此外也可以使用jvirsualvm来进行dump文件分析,jhat也可以,但是比较古老。


jmap -heap pid,打印堆内存信息。

cpp 复制代码
[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包后启动即可。

cpp 复制代码
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个类如下:

cpp 复制代码
import java.io.IOException;

public class TestRedefine {

    public static void main(String[] args) throws IOException {
        while (true){
            System.in.read();
            new RedefineNode().out();
        }
    }

}
cpp 复制代码
public class RedefineNode {

    public static void out(){
        System.out.println(1);
    }

}

将其启动后,输入字母,只能得到输出1.

cpp 复制代码
[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,热替换成功。

cpp 复制代码
[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协议的远程通讯端口。

cpp 复制代码
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目录下,

cpp 复制代码
vi jstatd.all.policy 

在其中保存如下内容:

cpp 复制代码
grant codebase "file:${java.home}/../lib/tools.jar" {
   permission java.security.AllPermission;
};

启动jstatd,开启jstatd后,此时就可以用win的jvirsualvm连接Java进程了

cpp 复制代码
jstatd -J-Djava.security.policy=D:\tools.policy

参考文章:

[1],使用jvisualvm的jstatd方式远程监控Java程序

[2],jstatd 启动报错解决:Could not create remote
object

[3],arthas/README_CN.md

相关推荐
ThisIsClark2 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
王佑辉2 小时前
【jvm】内存泄漏与内存溢出的区别
jvm
大G哥4 小时前
深入理解.NET内存回收机制
jvm·.net
泰勒今天不想展开4 小时前
jvm接入prometheus监控
jvm·windows·prometheus
东阳马生架构1 天前
JVM简介—3.JVM的执行子系统
jvm
程序员志哥1 天前
JVM系列(十三) -常用调优工具介绍
jvm
后台技术汇1 天前
JavaAgent技术应用和原理:JVM持久化监控
jvm
程序员志哥1 天前
JVM系列(十二) -常用调优命令汇总
jvm
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭1 天前
聊聊volatile的实现原理?
java·jvm·redis