java方法耗时统计,JavaAgent javassist bytebuddy统计方法耗时,jvm监控prometheus

news2024/9/21 14:28:30

前言介绍

  • JavaAgent是在JDK5之后提供的新特性,又叫叫java代理。开发人员可通过这种机制(Instrumentation)在jvm加载class文件之前修改类的字节码,动态更改类方法实现AOP,提供监控服务如:方法调用时长、jvm内存等。
  • 修改字节码领域有三个比较常见的框架;ASM、byte-buddy、javassist,其操作方式和控制粒度不同。
    • ASM 更偏向于底层,直接面向字节码编程,需要了解 JVM 虚拟机中指定规范以及对局部变量以及操作数栈的知识。虽然在编写起来比较麻烦,但是它也是性能最好功能最强的字节码操作库。 CGLIB 动态代理使用的就是ASM。
    • Javassist与byte-buddy提供了强大的 API,操作使用上更加容易控制,可以在不了解Java字节码规范的前提下修改class文件。

案例

使用javassist统计方法执行耗时

  • 引入maven依赖
<dependency>
   <groupId>org.javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.25.0-GA</version>
</dependency>
  • 将javassist打包到构建的javaAgent包中
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <compilerArgument>-parameters</compilerArgument>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>
            <!-- 将javassist包打包到Agent中 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <artifactSet>
                        <includes>
                            <include>org.javassist:javassist:jar:</include>
                        </includes>
                    </artifactSet>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • 编写耗时统计,在目标方法字节码前后插入方法耗时统计逻辑
public class ClazzTransform implements ClassFileTransformer {

    /** 增强类所在包名白名单 */
    private final String BASE_PACKAGE;

    public ClazzTransform(String basePackage) {
        this.BASE_PACKAGE = basePackage;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) {
        className = className.replace("/", ".");
        if ( !className.startsWith(BASE_PACKAGE) ){
            return null;
        }
        try {
            CtClass ctKlass = ClassPool.getDefault().get(className);
            CtBehavior[] behaviors = ctKlass.getDeclaredBehaviors();
            //遍历方法进行增强
            for (CtBehavior m : behaviors) {
                enhanceMethod(m);
            }
            byte[] bytes = ctKlass.toBytecode();
            //输出修改后的class文件内容
            Files.write(Paths.get("D:\\A.class"), bytes);
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classFileBuffer;
    }
    /** 方法增强,添加方法耗时统计 */
    private void enhanceMethod(CtBehavior method) throws CannotCompileException {
        if ( method.isEmpty() ){
            return;
        }
        String methodName = method.getName();
        method.addLocalVariable("start", CtClass.longType);
        method.insertBefore("start = System.currentTimeMillis();");
        method.insertAfter( String.format("System.out.println(\"%s cost: \" + (System.currentTimeMillis() - start) + \"ms\");", methodName) );
    }
}
  • 编写探针引导类
    类似于java主类的main方法,jvm会调用java agent的premain方法
public class AgentPremain {

    public static void premain(String ages, Instrumentation instrumentation){
        instrumentation.addTransformer( new ClazzTransform("com.lauor.agent") );
    }
    //如果没有实现上面的方法,JVM将尝试调用该方法
    public static void premain(String agentArgs) {
    }
}
  • 配置jar包探针引导类
    在这里插入图片描述
Manifest-Version: 1.0
Premain-Class: com.lauor.agent.AgentPremain
Can-Redefine-Classes: true
  • 测试
    • 基于jdk11 httpclient调用百度首页
    public class AgentTest {
    
        public static void main(String[] args) throws Exception {
            timeMonitor();
        }
    
        private static void timeMonitor() throws Exception {
            HttpRequest request = HttpRequest.newBuilder( URI.create("https://www.baidu.com/") ).GET().build();
            HttpResponse<String> rs = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(rs);
        }
    }
    
    • 配置带有javaagent的启动参数
    -javaagent:D:\agent\target\agent-1.1.1-SNAPSHOT.jar=agentArgs
    
    在这里插入图片描述
    • 调用结果
      在这里插入图片描述
    • 查看修改后的class文件
      在这里插入图片描述

使用bytebuddy统计方法调用耗时

  • 介绍
    使用bytebuddy比使用javassist更为简单,使用上类似于Java动态代理模式
  • 引入maven依赖
<properties>
    <bytebuddy.version>1.11.22</bytebuddy.version>
</properties>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>${bytebuddy.version}</version>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>${bytebuddy.version}</version>
    <scope>test</scope>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven.compiler.version}</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <compilerArgument>-parameters</compilerArgument>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                </archive>
            </configuration>
        </plugin>
        <!-- 将bytebuddy包打包到Agent中 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <artifactSet>
                    <includes>
                        <include>net.bytebuddy:byte-buddy:jar:</include>
                        <include>net.bytebuddy:byte-buddy-agent:jar:</include>
                    </includes>
                </artifactSet>
            </configuration>
        </plugin>
    </plugins>
</build>
  • 编写耗时统计逻辑
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class MethodMonitor {

    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
        long sTime = System.currentTimeMillis();
        try {
            return callable.call();
        } finally {
            System.out.println( String.format("%s cost %dms", method.getName(), System.currentTimeMillis() - sTime) );
        }
    }
}
  • 编写探针引导类
public static void premain(String ages, Instrumentation instrumentation){
    System.out.println( String.format("invoke premain args=%s", ages) );

    runMethodCost(instrumentation);
}
private static void runMethodCost(Instrumentation instrumentation) {
   AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder
       //拦截任意方法
       .method(ElementMatchers.any())
       //委托
       .intercept(MethodDelegation.to(MethodMonitor.class));

   new AgentBuilder
       .Default()
       //指定需要拦截的类
       .type(ElementMatchers.nameStartsWith("com.lauor.agent"))
       .transform(transformer)
       .installOn(instrumentation);
}
  • 同javassist方式一样配置探针jar包引导类,并配置带有javaagent的启动参数
  • 运行javassist的基于jdk11 httpclient调用百度首页的例子
    在这里插入图片描述

使用javaagent监控jvm

  • 编写代码收集jvm gc与内存信息
public class JvmMonitor {

    /** 堆内存手机 */
    public void gatherHeap(){
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapMemory = memoryMXBean.getHeapMemoryUsage();

        //初始堆内存
        BigDecimal initSize = toMb( heapMemory.getInit() );
        BigDecimal usedSize = toMb( heapMemory.getUsed() );
        BigDecimal maxSize = toMb( heapMemory.getMax() );
        //os已分配大小
        BigDecimal committedSize = toMb( heapMemory.getCommitted() );

        String info = String.format("init:%sMB,max:%sMB,committed:%sMB,used:%sMB",
            initSize.floatValue(), maxSize.floatValue(), committedSize.floatValue(), usedSize.floatValue());

        System.out.println(info);
    }

    /** 字节转mb */
    private BigDecimal toMb(long bytes){
        final long mInBytes = 1024 * 1024;
        //四舍五入保留一位小数
        BigDecimal value = BigDecimal.valueOf(bytes).divide(BigDecimal.valueOf(mInBytes), 1, RoundingMode.HALF_UP);
        return value;
    }

    /** GC信息收集 */
    public void gatherGc(){
        List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gcMXBean : gcMXBeans) {
            //gc总计次数
            long count = gcMXBean.getCollectionCount();
            //gc名
            String name = gcMXBean.getName();
            //gc大致耗时
            long costInMill = gcMXBean.getCollectionTime();
            String memNames = Arrays.deepToString( gcMXBean.getMemoryPoolNames() );

            String info = String.format("name:%s,count:%s,cost %dms,pool name:%s",
                name, count, costInMill, memNames);
            System.out.println(info);
        }
    }
}
  • 编写定时任务,定时执行收集jvm gc与内存信息任务
public class AgentPremain {

    public static void premain(String ages, Instrumentation instrumentation){
        runJvmTask();
    }

    private static void runJvmTask(){
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            private final AtomicInteger threadNumber = new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "pool-jvm-" + threadNumber.getAndIncrement());
                if (t.isDaemon()){
                    t.setDaemon(false);
                }
                if (t.getPriority() != Thread.NORM_PRIORITY){
                    t.setPriority(Thread.NORM_PRIORITY);
                }
                return t;
            }
        });

        JvmMonitor jvmMonitor = new JvmMonitor();
        executorService.scheduleAtFixedRate(() -> {
            jvmMonitor.gatherGc();
            jvmMonitor.gatherHeap();
        }, 5, 5, TimeUnit.SECONDS);
    }
}
  • 同javassist方式一样配置探针jar包引导类,并配置带有javaagent的启动参数
  • 编写样例不停创建对象,模拟线上后端应用
public class AgentTest {

    public static void main(String[] args) throws Exception {
        jvmMonitor();
    }

    private static void jvmMonitor(){
        while (true){
            List<String> list = new ArrayList<>();
            list.add("print jvm heap");
            list.add("print jvm gc");
            list.clear();
        }
    }
}
  • 运行结果
    在这里插入图片描述
  • 开箱即用的基于javaagent的监控解决方案:prometheus(普罗米修斯)
    • 介绍:
      prometheus是一个开源的监控系统和报警工具集合。主要有以下特点:
      1:由指标名称和和键/值对标签标识的时间序列数据组成的多维数据模型。
      2:强大的查询语言 PromQL。
      3:不依赖分布式存储;单个服务节点具有自治能力。
      4:时间序列数据是服务端通过 HTTP 协议主动拉取获得的,
      也可以通过中间网关来推送时间序列数据。
      5:可以通过静态配置文件或服务发现来获取监控目标。
      6:支持多种类型的图表和仪表盘。
    • prometheus结合grafana的效果图
      在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/99350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jsp+ssm计算机毕业设计大学生校园新闻发布系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

色温及其相关参数

光学膜层和大多数着色表面不是自发光的。为了看到它们&#xff0c;我们需要一个光源。显然&#xff0c;对颜色的任何评估都将包括光源的属性。在计算颜色时&#xff0c;我们通常使用标准光源&#xff0c;其中大部分是由CIE根据其相对光谱输出来定义的&#xff0c;并且尽可能地表…

java计算机毕业设计springboot+vue在线选课系统

项目介绍 本系统是针对目前在线选课系统管理的实际需求,从实际工作出发,对过去的在线选课系统系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概…

ARM寻址方式(ARM指令获取操作数的方式)

所谓寻址方式&#xff0c;指的是CPU去获取一个操作数的方式&#xff0c;可以是从指令中获取立即数、可以是从寄存器中获取&#xff0c;也可以是从内存中获取。 目录 1、立即数寻址 2、寄存器寻址 3、寄存器移位寻址 4、寄存器间接寻址 5、基址加变址寻址 (1) 前向索引…

springboot-mybatis/JPA流式查询

项目中有几个batch需要检查所有的用户参与的活动的状态&#xff0c;以前是使用分页&#xff0c;一页一页的查出来到内存再处理&#xff0c;但是随着数据量的增加&#xff0c;效率越来越低。于是经过一顿搜索&#xff0c;了解到流式查询这么个东西&#xff0c;不了解不知道&…

计算机毕业设计springboot+vue基本微信小程序的演出门票管理系统-票务转票系统

项目介绍 转票是一个传统的行业。根据当前发展现状,网络信息时代的全面普及,转票也在发生着变化,单就出票这一方面,利用手机预约考试正在逐步进入人们的生活。传统的转票方式,不仅会耗费大量的人力、时间,有时候还会出错。小程序系统伴随智能手机为我们提供了新的方向。手机微信…

新兴物种:程序猿的饲养指南

程序猿&#xff0c;一种主要生存在中国印度等亚太国家的新型猿类&#xff0c;生存活动以及消费活动的范围遍布世界各地&#xff0c;其中最优渥的产地位于美国硅谷。 主要的生存环境需求有&#xff0c;两脚兽一切的日用饮食以及物资需求。 该物种所获得的荣誉勋章有&#xff0…

Spring系列之SpringBoot概述及入门

SpringBoot入门 文章目录SpringBoot入门一、SpringBoot是什么&#xff1f;二、Spring的缺点1.配置繁琐2.依赖繁琐三、SpringBoot功能四、SpringBoot起步依赖原理五、SpringBoot快速入门总结一、SpringBoot是什么&#xff1f; SpringBoot是由Pivotal团队提供的全新框架&#xf…

Splunk Enterprise 9.0.X Crack

Splunk Enterprise 9.0.X Crack Splunk 有能力了解用户小型企业中实际发生的情况&#xff0c;并快速采取有目的的行动来了解用户和开发人员的情况。它能够轻松灵活地将简单信息转化为答案&#xff0c;以及自动机器学习支持的分析过程 搜索、分析和可视化&#xff0c;从您的所…

基于springcloud的简单易用的java分布式日志组件

真正的大师,永远都怀着一颗学徒的心&#xff01; 一、项目简介 基于springcloud的简单易用的java分布式日志组件 二、实现功能 支持基于traceId的日志记录 支持日志查询 支持日志缓冲队列 redis或者kafka 支持错误报警模块 支持内容组合查询功能 支持日志分应用统计条数…

【神奇bug】“金”、“⾦”不是同一个字

身为程序员&#xff0c;总能遇见那些神奇的bug。我前段时间遇到了 “中国黄金” 和 “中国黄⾦”&#xff0c;我咋看咋觉得是同一个词&#xff0c;但是程序就是判定不一致&#xff0c;十分郁闷&#xff0c;多方搜索&#xff0c;最后发现2个金居然不是一个字。真是个神奇的bug&a…

计算机基础学习笔记:操作系统篇之硬件结构,CPU Cache基础概念

三、CPU Cache的数据结构和读取过程 本文知识来源小林Coding阅读整理思考&#xff0c;原文链接请见该篇文章 Cache结构 CPU Cache 是由很多个 Cache Line 组成的&#xff0c;Cache Line 是 CPU 从内存读取数据的基本单位&#xff0c;而 Cache Line 是由各种**标志&#xff08;…

基于java+springmvc+mybatis+vue+mysql的智能新冠疫苗接种助手

项目介绍 随着全球新冠疫情的蔓延&#xff0c;基本所有的发达国家都开始了全民疫苗接种的行为&#xff0c;在我国更是进行了全民的新冠疫苗接种&#xff0c;为了能够让民众更加方便快捷的进行疫苗的接种我们通过java编程语言&#xff0c;后端ssm框架&#xff0c;前端vue技术开…

【Python百日进阶-数据分析】Day129 - plotly柱状图(条形图):px.bar()实例

文章目录四、实例4.1 Plotly Express条形图4.1.1 加拿大人口4.1.2 一维数据的条形图4.1.3 多维数据条形图4.1.4 彩条4.1.5 堆叠与分组条形图4.1.6 聚集成单色条4.1.7 带文本的条形图4.1.8 填充图案4.1.9 分面子图4.1.10 带Plotly Express的基本水平条形图4.1.11 配置水平条形图…

nacos服务注册与发现

目录 1. 应用系统架构的演变&#xff08;单应用>分布式&#xff09; 2. Spring Cloud Alibaba介绍 3. 开发示例 3.1 版本的选择 3.2 nacos安装 3.3 创建工程 3.3.1 创建父工程 3.3.2 创建服务提供者模块 3.3.2 服务消费者 3.4 测试 今天与大家们简单的聊一下&#…

远程的Win11主机没有连接屏幕,通过向日葵远程后只有一个640x480的分辨率选项

背景 远程的 Win11 主机没有连接屏幕&#xff0c;通过向日葵远程后只有一个 640x480 的分辨率选项&#xff0c;界面特别小&#xff0c;用起来很不方便。而且远程主机本身还无法调整分辨率&#xff0c;向日葵上面的工具栏里也没有分辨率这一选项。 问题分析 主要原因是远程主机…

threejs之圆弧

文章目录弧线相关方法getPointssetFromPoints直线样条曲线与贝塞尔曲线样条曲线贝塞尔曲线专栏目录请点击 弧线 一般我们绘制弧线都会使用ArcCurve来进行绘制&#xff0c;他是EllipseCurve的别名&#xff0c;关于他的所有的方法&#xff0c;我们都可以看EllipseCurve 官网例子…

Linux网络协议之UDP协议(传输层)

Linux网络协议之UDP协议(传输层) 文章目录Linux网络协议之UDP协议(传输层)1.深入理解传输层1.1 对于端口号的理解1.2 端口号范围1.3 常用的知名端口号1.4 进程和端口号的两个问题1.5 查看网络状态命令(netstat)2.UDP协议2.1 UDP协议格式2.2 UDP的特点2.3 面向数据报2.4 UDP的缓…

基于nodejs仿京东商城系统的设计与实现.zip(论文+源码+ppt文档+视频录制)

第一章绪论 3 1.1项目开发的背景和意义 3 1.2国内外研究的现状 3 1.3研究的主要内容 4 第2章系统相关技术介绍 4 2.1 相关技术介绍 4 2.2 系统环境开发条件 5 第三章系统分析 6 3.1可行性分析 6 3.1.1技术性可行性 6 3.1.2经济性可行性 6 3.1.3操作性可行性 7 3.2功能需求分析 …

(Java)【深基9.例1】选举学生会

【深基9.例1】选举学生会 一、题目描述二、输入格式三、输出格式四、样例输入五、样例输出六、失败经历七、正确代码八、正确思路及易错点&#xff08;1&#xff09;题目分析&#xff08;2&#xff09;思路分析&#xff08;3&#xff09;StringBuffer: 线程安全的可变字符串①S…