JVM系列(四十三) JVM调优实战-Arthas 动态执行命令ognl

832 阅读4分钟

Arthas 动态执行命令ognl

前面我们学习了监控相关的命令,包括minitor,watch,trace,stack等监控方法,这些已经很秀了,但是对于今天讲的ognl来说,还不够秀,ognl是非常实用的命令,可以动态的执行方法,今天我们专门花一篇文章来介绍ognl命令,它可以动态的修改加载的类文件,比如 日志级别,实现热更新

演示代码

本文采用SpringBoot 结构, 且 日志输出方式使用 logback,也就是Springboot默认的日志格式 之前采用了log4j2的方式,但是没有找到log的实现方式,没能成功的修改日志级别,所以今天演示,使用的是logback的默认日志形式

现在有一个测试类,TestController如下

package com.jzj.jvmtest.font;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    private static Logger log = LoggerFactory.getLogger(TestController.class);

    private static Integer age = 10;

    @GetMapping("/ping")
    private String ping() {
        log.info("sssss");
        return "pong";
    }

    @GetMapping("/print")
    private String getScore(Integer score) {
        log.debug("debug ===========");
        log.info("info ===========");
        log.error("error ===========");


        //输入分数超过 100, 就取100,否则返回真正的分数
        if (score > 100) {
            return "10";
        }

        return String.valueOf(score);
    }

}

还有一个静态工具类,静态方法TestOgnl

package com.jzj.jvmtest.util;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestOgnl {

    private static Logger logger = LoggerFactory.getLogger(TestOgnl.class);

    private Integer aa = 11;
    private static String bb = "22";


    public static String print(String cc) {
        logger.info("=============print cc:{}", cc);
        return cc + "123";
    }

    public static void main(String[] args) {
        System.out.println();
    }

    public static Logger getLogger() {
        return logger;
    }

    public static void setLogger(Logger logger) {
        TestOgnl.logger = logger;
    }

    public Integer getAa() {
        return this.aa;
    }

    public void setAa(Integer aa) {
        this.aa = aa;
    }

    public static String getBb() {
        return bb;
    }

    public static void setBb(String bb) {
        TestOgnl.bb = bb;
    }
}

1. ognl 修改Logback 日志级别,热更新

下面我们来演示一下 ognl动态调整 logback的日志格式,上面我们已经看了TestController

执行 curl 127.0.0.1:8078/print,看下打印日志的级别

#执行结果 返回 99, 并且打印了日志信息
C:\Users\jzj>curl 127.0.0.1:8078/print?score=99
99
C:\Users\jzj>

# 打印日志 info级别
2023-05-09 22:46:10.540  INFO 17656 --- [nio-8078-exec-3] com.jzj.jvmtest.font.TestController      : info ===========
2023-05-09 22:46:10.540 ERROR 17656 --- [nio-8078-exec-3] com.jzj.jvmtest.font.TestController      : error ===========

打印日志格式,输出 info, error 级别的日志 image.png

现在我们使用 ognl来动态调整日志级别

  • 先看下 TestController 类的 属性 log的 类信息
[arthas@17656]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.jzj.jvmtest.font.TestController@log'
@Logger[
    serialVersionUID=@Long[5454405123156820674],
    FQCN=@String[ch.qos.logback.classic.Logger],
    name=@String[com.jzj.jvmtest.font.TestController],
    level=null,
    effectiveLevelInt=@Integer[20000],
    parent=@Logger[Logger[com.jzj.jvmtest.font]],
    childrenList=null,
    aai=null,
    additive=@Boolean[true],
    loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]
[arthas@17656]$
  • 看到TestConroller的log属性的 level级别 level=null 设置为null,且它使用的是logback的Logger信息

image.png

  • 然后我们设置 log的日志输出级别 DEBUG
# 修改日志级别为 DEBUG,返回null
[arthas@17656]$
[arthas@17656]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.jzj.jvmtest.font.TestController@log.setLevel(@ch.qos.logback.classic.Level@DEBUG)'
null
[arthas@17656]$

日志级别 到底修改成功了没, 现在的日志级别是什么? 下面我们来测试一下

  • 执行 curl 127.0.0.1:8078/print,看下打印日志的级别
#执行结果 返回 99, 并且打印了日志信息
C:\Users\jzj>curl 127.0.0.1:8078/print?score=99
99
C:\Users\jzj>

# 打印日志 debug级别, 把debug,info,error debug以上的全都打印出来了,修改日志级别成功
2023-05-09 23:02:13.378 DEBUG 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController      : debug ===========
2023-05-09 23:02:13.378  INFO 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController      : info ===========
2023-05-09 23:02:13.378 ERROR 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController      : error ===========

修改日志级别成功,现在是debug级别 image.png

2.ognl 调用静态方法简单参数

我们使用ognl可以调用类的静态方法, 输入参数,就相当于我们在jvm中,执行了该方法,会根据实际入参参数,输出结果

下面我们调用下 TestOgnl的静态方法 print ,输入参数 hello world

[arthas@18012]$ ognl '@com.jzj.jvmtest.util.TestOgnl@print("hello world")'
@String[hello world123]

我们执行的代码就是

  • TestOgnl的静态方法 print
  • 输入参数 hello world
  • 输出参数 hello world123
  • 代码逻辑就是在输入参数上拼接123,作为返回值,逻辑正确

3.ognl 调用表达式,返回List

我们可以使用表达式 给变量赋值比如我现在有两个变量 value1 和 value2 我们使用表达式 获取系统变量,来给 value1 和value2 赋值,并返回List信息

[arthas@17656]$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
    @String[C:\Program Files\Java\jdk-17],
    @String[Java(TM) SE Runtime Environment],
]

可以看到执行结果 List 中两个元素

  1. String 类型的 C:\Program Files\Java\jdk-17
  2. String 类型的 Java(TM) SE Runtime Environment

4.ognl 返回复杂对象 -x number 解析多少层

有时候我们返回的对象是一个List 列表, 列表中的还是一个对象,对象还会嵌套对象,我们该如何展示这些信息

ognl -x 1 表示的就是堆返回结果 执行1次深度的解析,下面我们看下这个例子 UserInfo 结构如下

package com.jzj.jvmtest.entity;

import cn.hutool.core.util.RandomUtil;
import com.google.common.collect.Lists;
import lombok.Data;

import java.util.List;

@Data
public class UserInfo {
    private String name;
    private List<Address> addressList;

    public static UserInfo getInstance() {
        UserInfo user = new UserInfo();
        user.setName(RandomUtil.randomString(50));
        Address a1 = new Address("hubei", "wuhan", "hanyang");
        Address a2 = new Address("guangdong", "guangzhou", "shekou");
        user.setAddressList(Lists.newArrayList(a1, a2));
        return user;
    }
}

内嵌的Address 地址信息如下

package com.jzj.jvmtest.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private String province;
    private String city;
    private String hometown;
}

TestOgnl 类的 getUser方法如下

public static List<UserInfo> getUser() {
    UserInfo u1 = UserInfo.getInstance();
    u1.setName(name1);
    UserInfo u2 = UserInfo.getInstance();
    u2.setName(name2);
    return Lists.newArrayList(u1, u2);
}

现在我们来用 ognl来执行下代码

ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 1

我们可以看到 返回对象是List, -x 1/2/3 分别是返回对象的深度

# 返回List -x 1 解析1层深度
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 1
@ArrayList[
    @UserInfo[UserInfo(name=t9ujdmxryt2idic0y6uqw5f7cb1zmwbeokfyptfvrqnkbh9258, addressList=[Address(province=hubei, city=wuhan, hometown=hanyang), Address(province=guangdong, city=guangzhou, hometown=shekou)])],
    @UserInfo[UserInfo(name=mxlatnys6ydak4dfjf84iswnu6qxtacbkg7e3kudrsfkkqtud5, addressList=[Address(province=hubei, city=wuhan, hometown=hanyang), Address(province=guangdong, city=guangzhou, hometown=shekou)])],
]
[arthas@10112]$


# 返回List -x 2 解析2层深度
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 2
@ArrayList[
    @UserInfo[
        name=@String[nsz3xmh1br28auam546wkbe2cc7i2b58mqsjj9lnjsv7f5pfzi],
        addressList=@ArrayList[isEmpty=false;size=2],
    ],
    @UserInfo[
        name=@String[na8eilayunkk49fpb9htgyvyvzljtx0ujcqmzm26y1tfevo9ph],
        addressList=@ArrayList[isEmpty=false;size=2],
    ],
]


# 返回List -x 3 解析3层深度
[arthas@10112]$
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 3
@ArrayList[
    @UserInfo[
        name=@String[hi5pzu5wq7ucf2y3obmamzpijj2979a770j50gtckccgc21qy5],
        addressList=@ArrayList[
            @Address[Address(province=hubei, city=wuhan, hometown=hanyang)],
            @Address[Address(province=guangdong, city=guangzhou, hometown=shekou)],
        ],
    ],
    @UserInfo[
        name=@String[reku5stt3jkc9gjgwrf4ja40weji9hvwyqqmde1lrf7ziwfyue],
        addressList=@ArrayList[
            @Address[Address(province=hubei, city=wuhan, hometown=hanyang)],
            @Address[Address(province=guangdong, city=guangzhou, hometown=shekou)],
        ],
    ],
]
[arthas@10112]$

本文专门用一篇文章 介绍了ognl,其实ognl还有很多用法,比如针对输入类型参数是复杂类型如何处理,连续调用的方法A的出参是B方法的入参如何处理,我们要在实际项目中多使用它,才能记住它的语法,另外针对ognl动态执行的能力,专门用了日志logger日志级别的修改 来说明ognl的强大功能