• 13-JVM调优实战-3


    上一篇:12-JVM调优实战-2

    今天来介绍一款阿里巴巴的调优工具。

    Arthas详解

    Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细,详见:https://arthas.aliyun.com/

    1.Arthas使用场景

    得益于 Arthas 强大且丰富的功能,让 Arthas 能做的事情超乎想象。下面仅仅列举几项常见的使用情况,更多的使用场景可以在熟悉了 Arthas 之后自行探索。

    1. 是否有一个全局视角来查看系统的运行状况?
    2. 为什么 CPU 又升高了,到底是哪里占用了 CPU ?
    3. 运行的多线程有死锁吗?有阻塞吗?
    4. 程序运行耗时很长,是哪里耗时比较长呢?如何监测呢?
    5. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
    6. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
    7. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
    8. 有什么办法可以监控到 JVM 的实时运行状态?

    2.Arthas使用

    # github下载arthas
    wget https://alibaba.github.io/arthas/arthas-boot.jar
    # 或者 Gitee 下载
    wget https://arthas.gitee.io/arthas-boot.jar
    
    • 1
    • 2
    • 3
    • 4

    用java -jar运行即可,可以识别机器上所有Java进程(我们这里之前已经运行了一个Arthas测试程序,代码见下方)
    在这里插入图片描述

    package com.gwx.jvm;
    
    import java.util.HashSet;
    
    public class Arthas {
    
        private static HashSet hashSet = new HashSet();
    
        public static void main(String[] args) {
            // 模拟 CPU 过高
            cpuHigh();
            // 模拟线程死锁
            deadThread();
            // 不断的向 hashSet 集合增加数据
            addHashSetThread();
        }
    
        /**
         * 不断的向 hashSet 集合添加数据
         */
        public static void addHashSetThread() {
            // 初始化常量
            new Thread(() -> {
                int count = 0;
                while (true) {
                    try {
                        hashSet.add("count" + count);
                        Thread.sleep(1000);
                        count++;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        public static void cpuHigh() {
            new Thread(() -> {
                while (true) {
    
                }
            }).start();
        }
    
        /**
         * 死锁
         */
        private static void deadThread() {
            /** 创建资源 */
            Object resourceA = new Object();
            Object resourceB = new Object();
            // 创建线程
            Thread threadA = new Thread(() -> {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get ResourceA");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resourceB");
                    synchronized (resourceB) {
                        System.out.println(Thread.currentThread() + " get resourceB");
                    }
                }
            });
    
            Thread threadB = new Thread(() -> {
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread() + " get ResourceB");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resourceA");
                    synchronized (resourceA) {
                        System.out.println(Thread.currentThread() + " get resourceA");
                    }
                }
            });
            threadA.start();
            threadB.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    选择进程序号1,进入进程信息操作
    在这里插入图片描述

    输入dashboard可以查看整个进程的运行情况,线程、内存、GC、运行环境信息:
    在这里插入图片描述

    输入thread可以查看线程详细情况
    在这里插入图片描述

    输入 thread加上线程ID 可以查看线程堆栈
    在这里插入图片描述

    输入 thread -b 可以查看线程死锁
    在这里插入图片描述

    输入 jad加类的全名 可以反编译,这样可以方便我们查看线上代码是否是正确的版本
    在这里插入图片描述

    使用 ognl 命令可以
    在这里插入图片描述

    更多命令使用可以用help命令查看,或查看文档:https://arthas.aliyun.com/doc/commands.html

    3.GC日志详解

    对于java应用我们可以通过一些配置把程序运行过程中的gc日志全部打印出来,然后分析gc日志得到关键性指标,分析GC原因,调优JVM参数。
    打印GC日志方法,在JVM参数里增加参数,%t 代表时间

    -Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
    -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
    
    • 1
    • 2

    Tomcat则直接加在JAVA_OPTS变量里。

    3.1.如何分析GC日志

    运行程序加上对应gc日志

    java -jar -Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
    -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M microservice-eureka-server.jar
    
    • 1
    • 2

    下图中是我截取的JVM刚启动的一部分GC日志
    在这里插入图片描述

    我们可以看到图中第一行红框,是项目的配置参数。这里不仅配置了打印GC日志,还有相关的VM内存参数。
    第二行红框中的是在这个GC时间点发生GC之后相关GC情况。

    1. 对于2.909: 这是从jvm启动开始计算到这次GC经过的时间,前面还有具体的发生时间日期。
    2. Full GC(Metadata GC Threshold)指这是一次full gc,括号里是gc的原因, PSYoungGen是年轻代的GC,ParOldGen是老年代的GC,Metaspace是元空间的GC
    3. 6160K->0K(141824K),这三个数字分别对应GC之前占用年轻代的大小,GC之后年轻代占用,以及整个年轻代的大小。
    4. 112K->6056K(95744K),这三个数字分别对应GC之前占用老年代的大小,GC之后老年代占用,以及整个老年代的大小。
    5. 6272K->6056K(237568K),这三个数字分别对应GC之前占用堆内存的大小,GC之后堆内存占用,以及整个堆内存的大小。
    6. 20516K->20516K(1069056K),这三个数字分别对应GC之前占用元空间内存的大小,GC之后元空间内存占用,以及整个元空间内存的大小。
    7. 0.0209707是该时间点GC总耗费时间。

    从日志可以发现几次fullgc都是由于元空间不够导致的,所以我们可以将元空间调大点

    java -jar -Xloggc:./gc-adjust-%t.log -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
    -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
    microservice-eureka-server.jar
    
    • 1
    • 2
    • 3

    调整完我们再看下gc日志发现已经没有因为元空间不够导致的fullgc了

    对于CMS和G1收集器的日志会有一点不一样,也可以试着打印下对应的gc日志分析下,可以发现gc日志里面的gc步骤跟我们之前讲过的步骤是类似的

    public class HeapTest {
    
        byte[] a = new byte[1024 * 100];  //100KB
    
        public static void main(String[] args) throws InterruptedException {
            ArrayList<HeapTest> heapTests = new ArrayList<>();
            while (true) {
                heapTests.add(new HeapTest());
                Thread.sleep(10);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    CMS

    -Xloggc:d:/gc-cms-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
     -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
     -XX:+UseParNewGC -XX:+UseConcMarkSweepGC   
    
    • 1
    • 2
    • 3

    G1

    -Xloggc:d:/gc-g1-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
     -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+UseG1GC 
    
    • 1
    • 2

    上面的这些参数,能够帮我们查看分析GC的垃圾收集情况。但是如果GC日志很多很多,成千上万行。就算你一目十行,看完了,脑子也是一片空白。所以我们可以借助一些功能来帮助我们分析,这里推荐一个gceasy(https://gceasy.io),可以上传gc文件,然后他会利用可视化的界面来展现GC情况。具体下图所示

    在这里插入图片描述

    上图我们可以看到年轻代,老年代,以及永久代的内存分配,和最大使用情况。
    在这里插入图片描述

    上图我们可以看到堆内存在GC之前和之后的变化,以及其他信息。
    这个工具还提供基于机器学习的JVM智能优化建议,当然现在这个功能需要付费(其实,你把语言切换为:中文,就免费啦,世界如此简单,哈哈哈
    在这里插入图片描述

    4.JVM参数汇总查看命令

    # 表示打印出所有参数选项的默认值
    java -XX:+PrintFlagsInitial 
    # 表示打印出所有参数选项在运行程序时生效的值
    java -XX:+PrintFlagsFinal 
    
    • 1
    • 2
    • 3
    • 4

    下一篇:14-JVM调优实战-常量池详解

  • 相关阅读:
    【编程题】【Scratch四级】2019.12 打棒球
    内部表和外部表的区别
    字节跳动测开实习生面试,拿15K过分吗?
    【无标题】
    某职业学院未落实网络安全等级保护制度造成数据泄露——等保测评
    当前系统并无桌面环境,或无显示器,无法显示远程桌面,您需要自行安装X11桌面环境,或者使用终端文件功能
    Greenplum-性能问题常见原因
    docker拉取kafka镜像|启动kafka容器
    LaTex 控制图片表格位置[h][t][b][htb]等
    【打卡】牛客网:BM54 三数之和
  • 原文地址:https://blog.csdn.net/weixin_40461030/article/details/132802141