前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >JavaAgent技术应用和原理:JVM持久化监控

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

原创
作者头像
后台技术汇
发布2024-12-23 22:38:00
发布2024-12-23 22:38:00
4610
代码可运行
举报
文章被收录于专栏:后台技术汇后台技术汇
运行总次数:0
代码可运行

背景

字节码增强技术

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

添加描述

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

JavaAgent

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

JavaAgent定义和启动方式

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

  • preMain:主程序执行前执行
  • agentMain:主程序运行后执行

添加描述

一般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-Class或Agent-Class属性。

  • Premain-Class:指定实现premain方法的类。
  • Agent-Class:指定实现agentmain方法的类。
字节码增强工具:Instrumentation API
  • Instrumentation API:Java Agent通过Instrumentation接口来进行字节码增强。
  • addTransformer:注册一个类文件转换器,用于在类加载时修改字节码。
  • redefineClasses:重新定义已经加载的类。
  • retransformClasses:重新转换已经加载的类。

技术案例

加载时启动:JVM应用监控

  • 写一个Agent:JvmMonitorPreMainAgent
代码语言:javascript
代码运行次数:0
复制
@Slf4j
public 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信息工具类

代码语言:javascript
代码运行次数:0
复制
@Slf4j
public 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
代码语言:javascript
代码运行次数:0
复制
Manifest-Version: 1.0
Premain-Class: org.example.agent.JvmMonitorPreMainAgent
Can-Retransform-Classes: true
Can-Redefine-Classes: true
Created-By: Apache Maven
Built-By: bryant

  • 配置到应用App的启动项
代码语言:javascript
代码运行次数:0
复制
-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(可以看到监控日志输出)
代码语言:javascript
代码运行次数:0
复制
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.032ms
2024-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
代码语言:javascript
代码运行次数:0
复制
@Slf4j
public 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
代码语言:javascript
代码运行次数:0
复制
Manifest-Version: 1.0
Premain-Class: org.example.agent.JvmMonitorPreMainAgent
Agent-Class: org.example.agent.JvmMonitorAgentMainAgent
Can-Retransform-Classes: true
Can-Redefine-Classes: true
Created-By: Apache Maven
Built-By: bryant

  • 用JPS查看刚刚启动的应用程序APP的PID
代码语言:javascript
代码运行次数:0
复制
1571 EurekaApplication
1492 RemoteMavenServer36
1606 Launcher
1607 UserServer
1576 ConfigServer 
1498 RemoteMavenServer36
1631 Jps

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

  • 写一个main方法完成agent的植入
代码语言:javascript
代码运行次数:0
复制
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进行字节码增强,有需要的话可以自行补充字节码增量逻辑)
代码语言:javascript
代码运行次数:0
复制
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.PositiveOrZeroValidatorForByte
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.PositiveOrZeroValidatorForShort
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.PositiveOrZeroValidatorForInteger
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.PositiveOrZeroValidatorForLong
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.PositiveOrZeroValidatorForFloat
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.PositiveOrZeroValidatorForDouble
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.PositiveOrZeroValidatorForBigInteger
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.PositiveOrZeroValidatorForBigDecimal
2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = javax.validation.constraints.PositiveOrZero
2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.springframework.boot.system.ApplicationPid
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.PositiveValidatorForNumber
2024-12-22 11:30:32.620  INFO [users,,,] 1607 --- [Attach Listener] o.e.agent.JvmMonitorAgentMainAgent       : findout class, name = org.springframework.boot.logging.LoggingSystemProperties
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.PositiveValidatorForByte
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.PositiveValidatorForShort
2024-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(面向切面编程)功能,进行事务管理、安全检查等。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
    • 字节码增强技术
    • JavaAgent
      • JavaAgent定义和启动方式
    • 技术案例
      • 加载时启动:JVM应用监控
      • 运行时启动:JVM运行期的类变更
  • 应用拓展
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档