美文网首页Java生产环境下性能监控与调优详解
第一章:基于JDK命令行工具的监控

第一章:基于JDK命令行工具的监控

作者: 秦仙云 | 来源:发表于2018-07-15 16:58 被阅读221次

    本文主要参考慕课网若鱼老师课程Java生产环境下性能监控与调优详解

    请支持正版,抵制盗版,维护每一位辛苦付出的人的合法权益!

    本章关键词:JVM参数、jps、jstat、内存溢出、MAT、jstack

    一、JVM的基本参数

    1. 标准参数

    这类参数相对比较稳定

    1. -help
    2. -server -client
    3. -version -showversion
    4. -cp -classpath

    例如:

    • java -version
    • java -help

    2. X参数

    非标准化参数(可能在JVM的各个版本中会有变化,但是变化的比较小)

    1. -Xint:解释执行
    2. -Xcomp:第一次使用就编译成本地代码
    3. -Xmixed:混合模式,JVM自己来决定是否编译成本地代码

    3. XX参数

    非标准化参数,相对不稳定,主要用于JVM调优和Debug

    • Boolean类型

    格式:-XX:[+-]<name> 表示启用或者禁用name属性

    • 比如:
      -XX:+UseConcMarkSweepGC
      -XX:+UseG1GC
    • 非Boolean类型

    格式:-XX:<name>=<value> 表示name属性的值是value

    • 比如:-XX:MaxGCPauseMillis=500
      XX:GCTimeRatio=19
    • -Xmx -Xms -Xss属于XX参数
    • -Xms等价于-XX:InitialHeapSize(初始化堆大小)
    • -Xmx等价于-XX:MaxHeapSize(最大的堆大小)
    • -Xss等价于-XX:ThreadStackSize(堆栈内存大小)

    二、查看JVM运行时参数

    1. 相关参数

    • -XX:+PrintFlagsInitial(初始值,有可能被修改)
    • -XX:+PrintFlagsFinal(最终值)
    • -XX:+UnlockExperimentalVMOptions(解锁实验参数)
    • -XX:+UnlockDiagnosticVMOptions(解锁诊断参数)
    • -XX:+PrintCommandLineFlags(打印命令行参数)


      参数.png
    • java -XX:+PrintFlagsFinal -version > flags.txt 将内容打印到flags.txt文件中


      文件截图.png

    2. jps(专门用来查看java进程的id,类似Linux上的ps)

    jsp例子.png

    jdk8工具集: 相关命令都有完整的文档,不明白的可以查看此文档

    3.jinfo(查看一个正在运行的JVM里的参数值)

    jinfo示例.png
    jinfo示例2.png

    三、jstat查看JVM统计信息

    1.可以查看信息

    • 类加载
    • 垃圾收集
    • JIT编译

    2.命令格式

    • jstat -help|-options
    • jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

    options:-class,-compiler,-gc,-printcompilation等,详情参考相关文档

    jstat示例.png

    注:1000代表每隔1000毫秒(1秒) 10代表输出十次

    查看垃圾回收信息:-gc,-gcutil,-gccause,-gcnew,-gcold

    • -gc输出结果
      s0c、s1c、s0u、s1u:s0与s1的总量和使用量;
      EC,EU:eden区总量和使用量;
      OC,OU:old区总量和使用量;
      MC,MU:Metaspace区总量和使用量;
      CCSC,CCSU:压缩类空间总量和使用量;
      YGC,YGCT:YoungGC的次数与时间;
      FGC,FGCT:FullGC的次数与时间;
      GCT:总的GC时间。

    3.JVM内存结构

    JVM内存结构.png

    JDK8中非堆区叫Metaspace,1.7之前是没有的,1.7之前有一个区域叫PermGen space(Permanent Generation space,是指内存的永久保存区域),JDK8中完全移除了PermGen space
    s0又叫From Survivor
    s1又叫To Survivor

    4.JIT编译(了解)

    • -compiler
    • -printcompilation

    四、演示内存溢出

    1.代码演示堆内存溢出

    • 新建一个SpringBoot项目
    • 新建一个用户实体类
    public class User {
        private int id;
    
        private String name;
    
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 建一个Controller做测试
    /**
     * @author Qinxianyun
     * @version V1.0
     * @time 2018/7/15.14:51
     * @description 堆内存溢出
     */
    @RestController
    public class MemoryController {
    
        private List<User> userList = new ArrayList<>();
    
        /**
         * -Xmx32M -Xms32M
         * @return
         */
        @GetMapping("heap")
        public String heap(){
            int i= 0;
            while (true){
                userList.add(new User(i++,UUID.randomUUID().toString()));
            }
        }
    }
    
    • 修改jvm参数

    idea设置项目的jvm参数方法:run->Edit Configurations,详情见下图

    idea设置jvm参数.png idea设置jvm参数2.png

    2.代码演示Metaspcace内存溢出

    • pom文件引入jar
          <dependency>
              <groupId>asm</groupId>
                <artifactId>asm</artifactId>
                <version>3.3.1</version>
            </dependency>
    
    • 编写动态生成类代码
    package com.qinxianyun.monitor_tuning.chapter2;
    
    import org.hibernate.validator.constraints.EAN;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author Qinxianyun
     * @version V1.0
     * @time 2018/7/15.15:20
     * @description 动态创建类
     */
    public class MetaSpace extends ClassLoader{
    
        public static List<Class<?>> createClasses(){
            //类持有
            List<Class<?>> classes = new ArrayList<>();
            //循环1000w次生成1000w个不同的类
            for (int i = 0;i < 10000000; ++i){
                ClassWriter cw = new ClassWriter(0);
                //定义一个类名称为Class{i} 它的访问域为public 父类为java.lang.Object 不实现任何接口
                cw.visit(Opcodes.V1_1,Opcodes.ACC_PUBLIC,"Class" + i,null,"java/lang/Object",null);
                //定义构造函数<init>方法
                MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC,"<init>","()V",null,null);
                //第一个指令为加载this
                mw.visitVarInsn(Opcodes.ALOAD,0);
                //第二个指令为调用父类Object的构造函数
                mw.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/Object","<init>","()V");
                //第三条指令为return
                mw.visitInsn(Opcodes.RETURN);
                mw.visitMaxs(1,1);
                mw.visitEnd();
                MetaSpace test = new MetaSpace();
                byte[] code = cw.toByteArray();
                //定义类
                Class<?> exampleClass = test.defineClass("Class" + i,code,0,code.length);
                classes.add(exampleClass);
            }
            return classes;
        }
    }
    
    • controller测试代码
    /**
         * MetaSpace内存溢出
         * -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
         * @return
         */
        @GetMapping("nonheap")
        public String nonheap(){
            while (true){
                classArrayList.addAll(MetaSpace.createClasses());
            }
        }
    
    • 设置jvm参数

    -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M

    • 启动项目,访问nonheap,过一段时间会出现报错


      Metaspace内存溢出报出.png
      Metaspace内存溢出错误.png

    注意:我这边用idea,将-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M都设置了64M,如果设置32M的话,项目启动就报错


    启动报错.png

    五、导出内存映像文件

    1.JAVA内存泄漏和C++ 内存泄漏区别

    C++中内存泄漏: new了一个对象之后,把这个对象的指针丢失,这块内存就永远达不到释放了
    JAVA中内存泄漏:new了一个对象之后,一直不释放,占着内存

    2.如何导出内存映像文件

    • 内存溢出自动导出

    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=./

    设置了这两个参数后,出现内存溢出后,会自动下载文件到当前目录,详情如下:


    下载文件.png
    • 使用jmap命令手动导出

    jmap -dump:format=b,file=heap.hprof 10148

    jmap导出.png
    • 取舍
      两种方式都可以用,但是内存比较大的时候,自动导出可能会导不出来,所以jmap比较常用

    六、MAT分析内存溢出

    1.下载MAT

    官网下载对应版本MAT

    2.打开.hprof文件

    MAT分析.png
    • Leak Suspects指怀疑有内存泄漏


      分析报告.png
    • 查看对象数量


      查看对象数量.png

    Retained Heap 所占内存大小
    Shallow Heap不包含内部对象,所占内存大小

    查看是谁引用了该对象,排除虚引用,只看强引用


    是谁引用该对象.png
    • 查看对象所占字节数


      查看所占字节数.png

    常用的两个分析方式就是以上两种,线上环境肯定更为复杂,需要更为仔细的分析与考证

    七、jstack实战死循环与死锁

    前面介绍了jmap来导出内存映像,MAT分析内存溢出原因,jstack则用来打印JVM内部所有的线程

    1.命令格式

    命令格式.png

    2.示例

    C:\Users\Administrator\Desktop>jps -l
    7200 org.jetbrains.jps.cmdline.Launcher
    7888 org.jetbrains.idea.maven.server.RemoteMavenServer
    10148 com.qinxianyun.monitor_tuning.MonitorTuningApplication
    3636
    2136 D:\mat\plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
    2984 sun.tools.jps.Jps
    C:\Users\Administrator\Desktop>jstack 10148 > 10148.txt

    • 输出了10148.txt文件到桌面

    • java线程状态

    NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED
    状态解释查看官方文档

    3.实战死循环导致CPU飚高

    package com.qinxianyun.monitor_tuning.chapter2;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author Qinxianyun
     * @version V1.0
     * @time 2018/7/15.17:19
     * @description 死循环
     */
    @RestController
    public class CpuController {
        @RequestMapping("/loop")
        public List<Long> loop(){
            String data = "{\"data\":[{\"partnerid\":]";
            return getPartneridsFromJson(data);
    
        }
        public static List<Long> getPartneridsFromJson(String data){
            //{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]}
            //上面是正常的数据
            List<Long> list = new ArrayList<>(2);
            if(data == null || data.length() <= 0){
                return list;
            }
            int datapos = data.indexOf("data");
            if(datapos < 0){
                return list;
            }
            int leftBracket = data.indexOf("[",datapos);
            int rightBracket= data.indexOf("]",datapos);
            if(leftBracket < 0 || rightBracket < 0){
                return list;
            }
            String partners = data.substring(leftBracket+1,rightBracket);
            if(partners == null || partners.length() <= 0){
                return list;
            }
            while(partners!=null && partners.length() > 0){
                int idpos = partners.indexOf("partnerid");
                if(idpos < 0){
                    break;
                }
                int colonpos = partners.indexOf(":",idpos);
                int commapos = partners.indexOf(",",idpos);
                if(colonpos < 0 || commapos < 0){
                    //partners = partners.substring(idpos+"partnerid".length());//1
                    continue;
                }
                String pid = partners.substring(colonpos+1,commapos);
                if(pid == null || pid.length() <= 0){
                    //partners = partners.substring(idpos+"partnerid".length());//2
                    continue;
                }
                try{
                    list.add(Long.parseLong(pid));
                }catch(Exception e){
                    //do nothing
                }
                partners = partners.substring(commapos);
            }
            return list;
        }
    }
    
    • 定位问题
      首先top命令查看cpu使用率,找到使用最高的pid
      使用jstack pid > pid.txt
      sz pid.txt下载文件
      top -p pid -h 打印所有线程,查看占用cpu最高的几个线程

    4.死锁导致CPU飚高

    private Object lock1 = new Object();
        private Object lock2 = new Object();
    
        /**
         * 死锁
         * @return
         */
        @RequestMapping("/deadlock")
        public String deadlock(){
            new Thread(()->{
                synchronized (lock1){
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e){
    
                    }
                    synchronized (lock2){
                        System.out.println("Thread1 over");
                    }
                }
            }).start();
            new Thread(()->{
                synchronized (lock2){
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e){
    
                    }
                    synchronized (lock1){
                        System.out.println("Thread1 over");
                    }
                }
            }).start();
            return "deadlock";
        }
    

    注意:使用nohup java -jar XXX.jar启动,表示把日志输出到nohup.out文件中
    实时查看日志文件:tail -f nohup.out

    • 定位问题
      查看进程id
      jstack pid >pid.txt
      文件最后会帮我们定位到一个死锁的问题(Found 1 deadlock)

    jstack详细使用参考官方文档

    相关文章

      网友评论

        本文标题:第一章:基于JDK命令行工具的监控

        本文链接:https://www.haomeiwen.com/subject/wpbppftx.html