JavaAgent技术应用和原理:JVM持久化监控

news/2024/12/25 22:58:11/

c1aede4c116734e5bcd154424549d7e2.png

背景

字节码增强技术

  • 字节码增强:Java Agent通过修改字节码来实现对应用程序的增强,例如添加日志、性能监控、事务管理等。

  • 工具:常用的字节码增强工具包括ASM、Javassist、Byte Buddy等。

fe529280a912dfc1a6ae11b00846b692.png

JavaAgent技术基于JVM工具接口(JVMTI),通过字节码插桩实现其功能,字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。

JavaAgent

JavaAgent技术是一种在Java虚拟机(JVM)上运行的代理程序,它允许开发者在运行时修改Java字节码,从而实现对Java应用程序的动态增强和监控。

JavaAgent定义和启动方式

Java Agent:是一个特殊的Java类,它实现了premain方法或agentmain方法;最终解耦了对代码的增强处理。

  • preMain:主程序执行前执行

  • agentMain:主程序运行后执行

4f55cdc9f91493c2fddf671111182e43.png

一般JavaAgent技术通过两种方式启动:加载时启动和运行时启动。

  • 加载时启动的JavaAgent在类加载时进行修改,具有完全的修改权限,但修改后需要重启应用才能生效。

    • 通过在JVM启动参数中添加-javaagent:path/to/agent.jar来加载Java Agent。

    • 实现JavaAgent的premain方法:在JVM启动时调用,用于在应用程序启动前进行字节码增强。

  • 运行时启动的JavaAgent在应用运行过程中加载,可以随时对应用进行修改,但修改权限有限。

    • 通过VirtualMachine.attach(pid)方法在运行时动态加载Java Agent。

    • 实现JavaAgent的agentmain方法:在JVM运行时调用,用于在应用程序运行时进行字节码增强。

JavaAgent配置文件:MANIFEST.MF

MANIFEST.MF:Java Agent的JAR文件必须包含一个MANIFEST.MF文件,其中指定了Premain-ClassAgent-Class属性。

  • Premain-Class:指定实现premain方法的类。

  • Agent-Class:指定实现agentmain方法的类。

字节码增强工具:Instrumentation API
  • Instrumentation API:Java Agent通过Instrumentation接口来进行字节码增强。

    • addTransformer:注册一个类文件转换器,用于在类加载时修改字节码。

    • redefineClasses:重新定义已经加载的类。

    • retransformClasses:重新转换已经加载的类。

技术案例

加载时启动:JVM应用监控

  • 写一个Agent:JvmMonitorPreMainAgent

@Slf4jpublic class JvmMonitorPreMainAgent {    public static void premain(String agentArgs, Instrumentation inst) {        log.info("this is my agent - premain:{}", agentArgs);        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {            @SneakyThrows            public void run() {                JvmStack.printMemoryInfo();                JvmStack.printGCInfo();                log.info("===================================================================================================");            }        }, 0, 5000, TimeUnit.MILLISECONDS);    }}

打印JVM信息工具类

@Slf4jpublic class JvmStack {    private static final long MB = 1048576L;    public static void printMemoryInfo() {        MemoryMXBean memory = ManagementFactory.getMemoryMXBean();        MemoryUsage headMemory = memory.getHeapMemoryUsage();        String info = String.format("init: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s\n",                headMemory.getInit() / MB + "MB",                headMemory.getMax() / MB + "MB", headMemory.getUsed() / MB + "MB",                headMemory.getCommitted() / MB + "MB",                headMemory.getUsed() * 100 / headMemory.getCommitted() + "%"        );        log.info("printMemoryInfo = {}", info);        MemoryUsage nonheadMemory = memory.getNonHeapMemoryUsage();        info = String.format("init: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s\n",                nonheadMemory.getInit() / MB + "MB",                nonheadMemory.getMax() / MB + "MB", nonheadMemory.getUsed() / MB + "MB",                nonheadMemory.getCommitted() / MB + "MB",                nonheadMemory.getUsed() * 100 / nonheadMemory.getCommitted() + "%"        );        log.info("nonheadMemory = {}", info);    }    public static void printGCInfo() {        List<GarbageCollectorMXBean> garbages = ManagementFactory.getGarbageCollectorMXBeans();        for (GarbageCollectorMXBean garbage : garbages) {            String info = String.format("name: %s\t count:%s\t took:%s\t pool name:%s",                    garbage.getName(),                    garbage.getCollectionCount(),                    garbage.getCollectionTime(),                    Arrays.deepToString(garbage.getMemoryPoolNames()));            log.info("printGCInfo = {}", info);        }    }}
  • 写MAINFEST.MF

Manifest-Version: 1.0Premain-Class: org.example.agent.JvmMonitorPreMainAgentCan-Retransform-Classes: trueCan-Redefine-Classes: trueCreated-By: Apache MavenBuilt-By: bryant
  • 配置到应用App的启动项

-XX:+PrintGCDetails -Xmx300m -Xms100m -Xmn50m \-javaagent:/Users/bryantmo/Downloads/code/springcloud_test/agent/target/agent.jar=youCanDoIt \-XX:+HeapDumpOnOutOfMemoryError \-XX:HeapDumpPath=/Users/bryantmo/Desktop \-XX:ErrorFile=/Users/bryantmo/Desktop/error.log

-javaagent:配置了agent的jar包位置,并通过分隔符传入一个参数值"youCanDoIt"

  • 启动应用App(可以看到监控日志输出)

2024-12-22 11:25:59.300  INFO [users,,,] 1607 --- [pool-1-thread-1] org.example.agent.util.JvmStack          : printMemoryInfo = init: 104MB         max: 304MB         used: 42MB         committed: 104MB         use rate: 40%2024-12-22 11:25:59.300  INFO [users,,,] 1607 --- [pool-1-thread-1] org.example.agent.util.JvmStack          : nonheadMemory = init: 7MB         max: 0MB         used: 75MB         committed: 78MB         use rate: 95%2024-12-22 11:25:59.301  INFO [users,,,] 1607 --- [pool-1-thread-1] org.example.agent.util.JvmStack          : printGCInfo = name: G1 Young Generation         count:12         took:35         pool name:[G1 Eden Space, G1 Survivor Space, G1 Old Gen]2024-12-22 11:25:59.301  INFO [users,,,] 1607 --- [pool-1-thread-1] org.example.agent.util.JvmStack          : printGCInfo = name: G1 Old Generation         count:0         took:0         pool name:[G1 Eden Space, G1 Survivor Space, G1 Old Gen]2024-12-22 11:25:59.301  INFO [users,,,] 1607 --- [pool-1-thread-1] o.example.agent.JvmMonitorPreMainAgent   : ===================================================================================================2024-12-22 11:25:59.351  INFO [users,,,] 1607 --- [           main] o.s.b.a.e.web.ServletEndpointRegistrar   : Registered '/actuator/hystrix.stream' to hystrix.stream-actuator-endpoint[5.300s][info   ][gc,start      ] GC(14) Pause Young (Normal) (G1 Evacuation Pause)[5.300s][info   ][gc,task       ] GC(14) Using 8 workers of 8 for evacuation[5.302s][info   ][gc,phases     ] GC(14)   Pre Evacuate Collection Set: 0.0ms[5.302s][info   ][gc,phases     ] GC(14)   Evacuate Collection Set: 2.1ms[5.302s][info   ][gc,phases     ] GC(14)   Post Evacuate Collection Set: 0.5ms[5.302s][info   ][gc,phases     ] GC(14)   Other: 0.1ms[5.302s][info   ][gc,heap       ] GC(14) Eden regions: 45->0(45)[5.302s][info   ][gc,heap       ] GC(14) Survivor regions: 5->5(7)[5.302s][info   ][gc,heap       ] GC(14) Old regions: 24->26[5.302s][info   ][gc,heap       ] GC(14) Archive regions: 0->0[5.302s][info   ][gc,heap       ] GC(14) Humongous regions: 0->0[5.302s][info   ][gc,metaspace  ] GC(14) Metaspace: 54544K(56064K)->54544K(56064K) NonClass: 47889K(48896K)->47889K(48896K) Class: 6654K(7168K)->6654K(7168K)[5.302s][info   ][gc            ] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 73M->29M(104M) 2.795ms[5.302s][info   ][gc,cpu        ] GC(14) User=0.02s Sys=0.00s Real=0.00s[5.444s][info   ][gc,start      ] GC(15) Pause Young (Concurrent Start) (Metadata GC Threshold)[5.444s][info   ][gc,task       ] GC(15) Using 8 workers of 8 for evacuation[5.448s][info   ][gc,phases     ] GC(15)   Pre Evacuate Collection Set: 0.0ms[5.448s][info   ][gc,phases     ] GC(15)   Evacuate Collection Set: 3.0ms[5.448s][info   ][gc,phases     ] GC(15)   Post Evacuate Collection Set: 0.9ms[5.448s][info   ][gc,phases     ] GC(15)   Other: 0.1ms[5.448s][info   ][gc,heap       ] GC(15) Eden regions: 40->0(45)[5.448s][info   ][gc,heap       ] GC(15) Survivor regions: 5->5(7)[5.448s][info   ][gc,heap       ] GC(15) Old regions: 26->28[5.448s][info   ][gc,heap       ] GC(15) Archive regions: 0->0[5.448s][info   ][gc,heap       ] GC(15) Humongous regions: 0->0[5.448s][info   ][gc,metaspace  ] GC(15) Metaspace: 58802K(60160K)->58802K(60160K) NonClass: 51577K(52352K)->51577K(52352K) Class: 7225K(7808K)->7225K(7808K)[5.448s][info   ][gc            ] GC(15) Pause Young (Concurrent Start) (Metadata GC Threshold) 68M->31M(104M) 3.947ms[5.448s][info   ][gc,cpu        ] GC(15) User=0.02s Sys=0.00s Real=0.01s[5.448s][info   ][gc            ] GC(16) Concurrent Cycle[5.448s][info   ][gc,marking    ] GC(16) Concurrent Clear Claimed Marks[5.448s][info   ][gc,marking    ] GC(16) Concurrent Clear Claimed Marks 0.060ms[5.448s][info   ][gc,marking    ] GC(16) Concurrent Scan Root Regions[5.450s][info   ][gc,marking    ] GC(16) Concurrent Scan Root Regions 1.617ms[5.450s][info   ][gc,marking    ] GC(16) Concurrent Mark (5.450s)[5.450s][info   ][gc,marking    ] GC(16) Concurrent Mark From Roots[5.450s][info   ][gc,task       ] GC(16) Using 2 workers of 2 for marking[5.462s][info   ][gc,marking    ] GC(16) Concurrent Mark From Roots 11.884ms[5.462s][info   ][gc,marking    ] GC(16) Concurrent Preclean[5.462s][info   ][gc,marking    ] GC(16) Concurrent Preclean 0.064ms[5.462s][info   ][gc,marking    ] GC(16) Concurrent Mark (5.450s, 5.462s) 11.962ms[5.462s][info   ][gc,start      ] GC(16) Pause Remark[5.465s][info   ][gc,stringtable] GC(16) Cleaned string and symbol table, strings: 29316 processed, 19 removed, symbols: 182551 processed, 580 removed[5.466s][info   ][gc            ] GC(16) Pause Remark 35M->35M(104M) 3.572ms[5.466s][info   ][gc,cpu        ] GC(16) User=0.02s Sys=0.00s Real=0.00s[5.466s][info   ][gc,marking    ] GC(16) Concurrent Rebuild Remembered Sets[5.474s][info   ][gc,marking    ] GC(16) Concurrent Rebuild Remembered Sets 8.430ms[5.474s][info   ][gc,start      ] GC(16) Pause Cleanup[5.474s][info   ][gc            ] GC(16) Pause Cleanup 36M->36M(104M) 0.047ms[5.474s][info   ][gc,cpu        ] GC(16) User=0.00s Sys=0.00s Real=0.00s[5.474s][info   ][gc,marking    ] GC(16) Concurrent Cleanup for Next Mark[5.474s][info   ][gc,marking    ] GC(16) Concurrent Cleanup for Next Mark 0.094ms[5.474s][info   ][gc            ] GC(16) Concurrent Cycle 26.032ms2024-12-22 11:25:59.654  INFO [users,,,] 1607 --- [           main] org.redisson.Version                     : Redisson 3.14.0[5.587s][info   ][gc,start      ] GC(17) Pause Young (Normal) (G1 Evacuation Pause)[5.587s][info   ][gc,task       ] GC(17) Using 8 workers of 8 for evacuation[5.591s][info   ][gc,phases     ] GC(17)   Pre Evacuate Collection Set: 0.0ms[5.591s][info   ][gc,phases     ] GC(17)   Evacuate Collection Set: 3.0ms[5.591s][info   ][gc,phases     ] GC(17)   Post Evacuate Collection Set: 0.6ms[5.591s][info   ][gc,phases     ] GC(17)   Other: 0.1ms[5.591s][info   ][gc,heap       ] GC(17) Eden regions: 45->0(43)[5.591s][info   ][gc,heap       ] GC(17) Survivor regions: 5->7(7)[5.591s][info   ][gc,heap       ] GC(17) Old regions: 28->29[5.591s][info   ][gc,heap       ] GC(17) Archive regions: 0->0[5.591s][info   ][gc,heap       ] GC(17) Humongous regions: 0->0[5.591s][info   ][gc,metaspace  ] GC(17) Metaspace: 61046K(62592K)->61046K(62592K) NonClass: 53574K(54528K)->53574K(54528K) Class: 7472K(8064K)->7472K(8064K)[5.591s][info   ][gc            ] GC(17) Pause Young (Normal) (G1 Evacuation Pause) 76M->35M(104M) 3.693ms[5.591s][info   ][gc,cpu        ] GC(17) User=0.03s Sys=0.00s Real=0.00s

运行时启动:JVM运行期的类变更

  • 写一个Agent:JvmMonitorAgentMainAgent

@Slf4jpublic class JvmMonitorAgentMainAgent {    public static void agentmain(String agentArgs, Instrumentation inst){        log.info("this is my agent - agentmain:{}", inst);        //针对运行期的类进行增强        inst.addTransformer(new ClassTransformer(),true);        //agentmain运行时 由于堆里已经存在Class文件,所以新添加Transformer后        // 还要再调用一个  inst.retransformClasses(clazz); 方法来更新Class文件        for (Class clazz:inst.getAllLoadedClasses()) {            log.info("findout class, name = {}", clazz.getName());//            if (clazz.getName().contains("com.bryant.agent.TestAgentBean")){//                try {//                    instrumentation.retransformClasses(clazz);//                } catch (Exception e) {//                    e.printStackTrace();//                }//            }        }    }}
  • 修改MANIFEST.MF,补充Agent-Class

Manifest-Version: 1.0Premain-Class: org.example.agent.JvmMonitorPreMainAgentAgent-Class: org.example.agent.JvmMonitorAgentMainAgentCan-Retransform-Classes: trueCan-Redefine-Classes: trueCreated-By: Apache MavenBuilt-By: bryant
  • 用JPS查看刚刚启动的应用程序APP的PID

1571 EurekaApplication1492 RemoteMavenServer361606 Launcher1607 UserServer1576 ConfigServer 1498 RemoteMavenServer361631 Jps

我们的启动app是UserServer,对应的PID是1607。

  • 写一个main方法完成agent的植入

public class Main {    /**     * 这个main方法可以多次被执行,在字节码层面,完成对JVM的多次热修改部署     * @param args     * @throws Exception     */    public static void main(String[] args) throws Exception {        // 获取当前 JVM 进程的 PID        String pid = "12460";//        String pid = Long.toString(ProcessHandle.current().pid());        // 加载代理        VirtualMachine vm = VirtualMachine.attach(pid);        vm.loadAgent("/Users/bryantmo/Downloads/code/springcloud_test/agent/target/agent.jar");        vm.detach();    }}
  • 执行效果是,被植入Agent的app会输出agent的操作日志(JvmMonitorAgentMainAgent会遍历每个class进行字节码增强,有需要的话可以自行补充字节码增量逻辑)

2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForByte2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForShort2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForInteger2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForLong2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForFloat2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForDouble2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForBigInteger2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveOrZeroValidatorForBigDecimal2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = javax.validation.constraints.PositiveOrZero2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.springframework.boot.system.ApplicationPid2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveValidatorForNumber2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.springframework.boot.logging.LoggingSystemProperties2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveValidatorForByte2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.hibernate.validator.internal.constraintvalidators.bv.number.sign.PositiveValidatorForShort2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = [Lorg.springframework.boot.ansi.AnsiColor;2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = [Lorg.springframework.boot.ansi.AnsiElement;

应用拓展

  • JVM监控和性能分析:通过JavaAgent技术,可以在不修改源代码的情况下,对Java应用程序进行CPU、内存、线程等性能指标的监控和分析

  • 代码热替换:在运行时动态替换类定义,实现热部署和快速迭代。

  • 框架和库增强:对框架和库进行增强,如实现AOP(面向切面编程)功能,进行事务管理、安全检查等。

6ce7ffe138575a56088a315e175910de.png


http://www.ppmy.cn/news/1558102.html

相关文章

京准电钟解读,NTP网络授时服务器如何提升DCS系统效率

京准电钟解读&#xff0c;NTP网络授时服务器如何提升DCS系统效率 京准电钟解读&#xff0c;NTP网络授时服务器如何提升DCS系统效率 NTP 网络授时服务器为防火墙内的网络设备、终端、服务器提供准确、可靠和安全的高精度卫星时间参考&#xff0c;可为它支持数万台支持标准的网…

Vue3中路由跳转之后删除携带的query参数

场景 今天在开发时遇到一个需求&#xff0c;需要页面跳转后&#xff0c;将路由携带的参数输入搜索框进行筛选&#xff0c;筛选条件回显后产生了一个问题&#xff0c;刷新页面筛选条件会一致存在&#xff0c;因为在页面挂载时将路由上的query参数赋值给了筛选条件&#xff0c;需…

时钟抖动定义和测量方法

1. 简介 抖动是一组信号边沿与其理想值的时序变化。时钟信号中的抖动通常是由系统中的噪声或其他干扰引起的。影响因素包括热噪声、电源变化、负载条件、设备噪声以及附近电路耦合的干扰。 2. 抖动的类型 抖动可以通过多种方式测量。以下是抖动的主要类型&#xff1a; 周期…

Win10系统下:启动若依3.8.8版本的前后端框架

本文章参考了该博主写的文章&#xff0c;安装及其他详情见链接&#xff1a;若依安装教程&#xff08;保姆级教程&#xff09; 下面重点写启动前后端走的弯路&#xff0c;顺序一定是 先启动Redis&#xff0c;运行vue框架&#xff0c;先数据库&#xff0c;再后端&#xff0c;最后…

Docker搭建YesPlayMusic云音乐播放器并实现异地远程连接播放歌曲

文章目录 前言1. 安装Docker2. 本地安装部署YesPlayMusic3. 安装cpolar内网穿透4. 固定YesPlayMusic公网地址 前言 本文主要介绍如何在本地Linux服务器快速搭建 YesPlayMusic 云音乐播放器&#xff0c;并结合 cpolar 内网穿透工具实现随时随地远程访问局域网内的音乐播放器&am…

设计模式-创建型模式-简单工厂模式详解

简单工厂模式 简介 简单工厂模式 &#xff1a; Simple Factory Pattern 是一种创建型设计模式 。 通过一个工厂类&#xff0c;封装了对象的创建逻辑。 客户端使用时不需要通过 new 的方式进行对象的创建&#xff0c;而是直接调用工厂类中的方法获取对象。 应用场景 场景介绍 …

怎样配备公共配套设施,才能让啤酒酿造流程高效环保?

今天&#xff0c;天泰邀请大家和我一起走进啤酒厂&#xff0c;了解水、蒸汽、压缩空气和二氧化碳这些基础设施如何助力啤酒生产&#xff0c;实现高效与环保的完美结合。 水 水是啤酒酿造的基础&#xff0c;啤酒厂对水质的要求极高。为了确保水质达标&#xff0c;啤酒厂设有专…

2、C#基于.net framework的应用开发实战编程 - 设计(二、二) - 编程手把手系列文章...

二、设计&#xff1b; 二&#xff0e;二、设计用户界面&#xff1b; 这个编程例子主要用的Visual Studio 2022开发的&#xff0c;所以此文记录VS 2022的UI界面设计过程。 1、 窗体&#xff1b; 1) 此例子的窗体主要是便签窗体&#xff1b; 主要是便签的内容保存。还有一个标题…