当前位置: 首页 > news >正文

Java代理(六)当前主流动态代理框架性能对比

0.测试范围

​​​​

  • Java proxies

    Java 类库带有一个代理工具包,允许创建实现一组给定接口的类。这个内置的代理供应商很方便,但也很有限。例如,上面提到的安全框架不能以这种方式实现,因为我们想要扩展类而不是接口。

  • cglib

    代码生成库是在 Java 早期实现的,遗憾的是它没有跟上 Java 平台的发展。尽管如此,cglib 仍然是一个非常强大的库,但它的积极发展变得相当模糊。出于这个原因,它的许多用户离开了 cglib。

  • Javassist

    该库带有一个编译器,该编译器采用包含 Java 源代码的字符串,这些字符串在应用程序运行时被翻译成 Java 字节码。这是非常雄心勃勃的,原则上是一个好主意,因为 Java 源代码显然是描述 Java 类的好方法。但是,Javassist 编译器的功能无法与 javac 编译器相比,并且在动态组合字符串以实现更复杂的逻辑时容易出错。此外,Javassist 带有一个代理库,它类似于 JCL 的代理实用程序,但允许扩展类并且不限于接口。然而,Javassist 的代理工具的范围在其 API 和功能方面同样受到限制。

  • ByteBuddy​​​​​​

    ByteBuddy 是一个强大的 Java 字节码操作库,它允许开发者在运行时动态地创建、修改和操作 Java 类。它类似于其他字节码操作工具(如 ASM 或 CGLIB),但提供了更高级的抽象和更易用的 API。ByteBuddy 广泛应用于 AOP(面向切面编程)、动态代理、插件系统、测试框架等领域。

1. ByteBuddy官网上的benchmark

在ByteBuddy的官网上给了一个benchmark,号称ByteBuddy是当前最快的动态测试框架。

上表中的第一个基准测试用于在不实现或覆盖任何方法的情况下为 Object 子类化的库的运行时间。这给了我们一个库在代码生成中的一般开销的印象。在这个基准测试中,由于只有在假设总是扩展接口时才有可能进行优化,Java 代理的性能优于其他库。 Byte Buddy 还检查类中的泛型类型和注解是什么导致了额外的运行时。在创建类的其他基准测试中也可以看到这种性能开销。

基准测试 (2a) 显示了用于创建(和加载)实现具有 18 个方法的单个接口的类的测量运行时间,(2b)显示了为此类生成的方法的执行时间。类似地,(3a) 显示了使用相同的 18 种已实现方法扩展类的基准。 Byte Buddy 提供了两个基准测试,这是由于对始终执行超级方法的拦截器可能进行的优化。在类创建期间牺牲一些时间,Byte Buddy 创建的类的执行时间通常达到基线,这意味着检测根本不会产生任何开销。应该注意的是,如果元数据处理被禁用,Byte Buddy 在类创建期间也优于任何其他代码生成库。然而,由于代码生成的运行时间与程序的总运行时间相比非常小,因此这种选择退出是不可用的,因为它会以牺牲库代码的复杂性为代价获得很少的性能。

英文版:Byte Buddy - runtime code generation for the Java virtual machine

中文版:https://juejin.cn/post/7031748974285422629

2.基于JMH的benchmark

2.1 各测试库版本

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.17.5</version>
        </dependency>

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.30.2-GA</version>
        </dependency>

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.37</version>
        </dependency>

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.37</version>
        </dependency>
    </dependencies>

2.2 各Proxy源码

为了方便测试,简化了各Proxy类,代码全部放到一个类里,同时方便观察benchmark日志,拦截BusinessCalculator类时不打印输出日志。

JdkProxy源码如下:

public class JdkProxy implements InvocationHandler {

    private Calculator calculator;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        System.out.println("invoke method: "+method.getName());
        long startTime = System.currentTimeMillis();

        Object result = method.invoke(calculator, args);

        long endTime = System.currentTimeMillis();
//        System.out.println("cost time: ["+(endTime-startTime)+"]ms");
        long cost = endTime - startTime;

        return result;
    }

    public  Object getProxy(Calculator calculator){
        this.calculator = calculator;
        return Proxy.newProxyInstance(
                this.calculator.getClass().getClassLoader(),
                this.calculator.getClass().getInterfaces(),
                this);
    }
}

CglibProxy源码如下:

public class CglibProxy implements MethodInterceptor {

    private Calculator calculator;

    public Object getProxy(Calculator calculator) {
        this.calculator = calculator;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(calculator.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object proxyObject, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//        System.out.println("invoke method: "+method.getName());
        long startTime = System.currentTimeMillis();

        Object result = methodProxy.invoke(calculator, objects);

        long endTime = System.currentTimeMillis();
//        System.out.println("cost time: ["+(endTime-startTime)+"]ms");
        long cost = endTime - startTime;

        return result;
    }
}

ByteBuddyProxy源码如下:
 

public class ByteBuddyProxy {

    private Calculator calculator;

    @RuntimeType
    public Object intercept(@This Object obj, @Origin Method method, @AllArguments Object[] args) throws Throwable {
//        System.out.println("invoke method: "+method.getName());
        long startTime = System.currentTimeMillis();

        Object result = method.invoke(calculator, args);

        long endTime = System.currentTimeMillis();
//        System.out.println("cost time: ["+(endTime-startTime)+"]ms");
        long costTime = endTime-startTime;

        return result;
    }

    public  Object getProxy(Calculator calculator){
        this.calculator = calculator;

        try {
            return new ByteBuddy()
                    .subclass(BusinessCalculator.class)
                    .method(ElementMatchers.any())
                    .intercept(MethodDelegation.to(this))
                    .make()
                    .load(getClass().getClassLoader())
                    .getLoaded()
                    .getDeclaredConstructor()
                    .newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}

JavassistProxy源码如下:
 

public class JavassistProxy {

    public Object getProxy(Calculator calculator){
        ProxyFactory proxyFactory  = new ProxyFactory();

        if(calculator.getClass().isInterface()){
            Class[] clz  = new Class[1];
            clz[0] = calculator.getClass();
            proxyFactory.setInterfaces(clz);
        } else {
            proxyFactory.setSuperclass(calculator.getClass());
        }

        proxyFactory.setHandler(new MethodHandler() {
            public Object invoke(Object proxy, Method method, Method method1, Object[] args) throws Throwable {
//                System.out.println("invoke method: "+method.getName());
                long startTime = System.currentTimeMillis();

                Object result = method1.invoke(proxy,args);

                long endTime = System.currentTimeMillis();
//                System.out.println("cost time: ["+(endTime-startTime)+"]ms");
                long costTime = endTime - startTime;

                return result;
            }
        });

        try{
            return proxyFactory.createClass().newInstance();
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}

2.3 测试的benchmark

共测试两个过程:

1)对象的创建过程,参考源码:CreateProxyBenchmark.java;

2)方法的调用过程, 参考源码:CallProxyBenchmark.java;

CreateProxyBenchmark.java 源码如下:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class CreateProxyBenchmark {

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

        Options ops = new OptionsBuilder().include(CreateProxyBenchmark.class.getSimpleName())
                .forks(1).build();
        new Runner(ops).run();
    }

    @Benchmark
    public void JdkProxyCreate(){
        Calculator proxyService = (Calculator) new JdkProxy().getProxy(new BusinessCalculator());
    }

    @Benchmark
    public void CglibProxyCreate(){
        Calculator proxyService = (Calculator) new CglibProxy().getProxy(new BusinessCalculator());
    }

    @Benchmark
    public void ByteBuddyProxyCreate(){
        Calculator proxyService = (Calculator) new ByteBuddyProxy().getProxy(new BusinessCalculator());
    }


    @Benchmark
    public void JavassistProxyCreate(){
        Calculator proxyService = (Calculator) new JavassistProxy().getProxy(new BusinessCalculator());
    }

}

CallProxyBenchmark.java如下:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class CallProxyBenchmark {
    private  final Calculator jdkProxy = (Calculator) new JdkProxy().getProxy(new BusinessCalculator());
    private  final Calculator cglibProxy = (Calculator) new CglibProxy().getProxy(new BusinessCalculator());
    private  final Calculator byteBuddyProxy = (Calculator) new ByteBuddyProxy().getProxy(new BusinessCalculator());
    private  final Calculator javassistProxy = (Calculator) new JavassistProxy().getProxy(new BusinessCalculator());

    public static void main(String[] args) throws Exception{
        Options ops = new OptionsBuilder().include(CallProxyBenchmark.class.getSimpleName())
                .forks(1).build();
        new Runner(ops).run();
    }

    @Benchmark
    public void jdkProxyCall(){
        jdkProxy.add(9, 2);
        jdkProxy.subtract(9, 2);
        jdkProxy.multiply(9, 2);
        jdkProxy.divide(9, 2);
    }

    @Benchmark
    public void cglibProxyCall(){
        cglibProxy.add(9, 2);
        cglibProxy.subtract(9, 2);
        cglibProxy.multiply(9, 2);
        cglibProxy.divide(9, 2);
    }

    @Benchmark
    public void byteBuddyProxyCall(){
        byteBuddyProxy.add(9, 2);
        byteBuddyProxy.subtract(9, 2);
        byteBuddyProxy.multiply(9, 2);
        byteBuddyProxy.divide(9, 2);
    }

    @Benchmark
    public void javassistProxyCall(){
        javassistProxy.add(9, 2);
        javassistProxy.subtract(9, 2);
        javassistProxy.multiply(9, 2);
        javassistProxy.divide(9, 2);
    }

}

2.4 创建代理对象benchmark结果

JMH用来做基准测试,由于JIT编译器会根据代码运行情况进行优化,代码在第一次执行的时候,速度相对较慢,随着运行的次数增加,JIT编译器会对代码进行优化,以达到最佳的性能状态。

JMH可以对代码进行预热,让代码达到最佳的性能状态,再进行性能测试。

在基准测试前会预热5个迭代,每个迭代10s。测试也会进行5个迭代,每个迭代10s。测试结果如下:

代理对象创建基准测试结果
Benchmark  ModeCntScore  Units
CreateProxyBenchmark.ByteBuddyProxyCreateavgt  5 2368439.951 ± 533202.838ns/op
CreateProxyBenchmark.CglibProxyCreateavgt  5 263.011 ±     72.902ns/op
CreateProxyBenchmark.JavassistProxyCreateavgt  5621833.137 ± 110444.823ns/op
CreateProxyBenchmark.JdkProxyCreateavgt 539.257 ±     11.334ns/op

创建代理对象性能如下:(耗时越短,性能越好,单位纳秒)

JDK Dynamic Proxy > CGLIB Proxy > Javassist Proxy > ByteBuddy Proxy.

详细日志如下:

# JMH version: 1.37
# VM version: JDK 17.0.12, Java HotSpot(TM) 64-Bit Server VM, 17.0.12+8-LTS-286
# VM invoker: C:\Program Files\Java\jdk-17\bin\java.exe
# VM options: --add-opens=java.base/java.lang=ALL-UNNAMED -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2024.3.3\lib\idea_rt.jar=49860:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2024.3.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.derek.CreateProxyBenchmark.ByteBuddyProxyCreate

# Run progress: 0.00% complete, ETA 00:06:40
# Fork: 1 of 1
# Warmup Iteration   1: 2209820.406 ns/op
# Warmup Iteration   2: 1339412.661 ns/op
# Warmup Iteration   3: 1385975.457 ns/op
# Warmup Iteration   4: 1631787.208 ns/op
# Warmup Iteration   5: 2300534.974 ns/op
Iteration   1: 2182113.184 ns/op
Iteration   2: 2258255.671 ns/op
Iteration   3: 2477342.521 ns/op
Iteration   4: 2476037.021 ns/op
Iteration   5: 2448451.357 ns/op


Result "org.derek.CreateProxyBenchmark.ByteBuddyProxyCreate":
  2368439.951 ±(99.9%) 533202.838 ns/op [Average]
  (min, avg, max) = (2182113.184, 2368439.951, 2477342.521), stdev = 138471.084
  CI (99.9%): [1835237.113, 2901642.788] (assumes normal distribution)

# Run progress: 25.00% complete, ETA 00:05:03
# Fork: 1 of 1
# Warmup Iteration   1: 176.052 ns/op
# Warmup Iteration   2: 255.153 ns/op
# Warmup Iteration   3: 276.424 ns/op
# Warmup Iteration   4: 273.844 ns/op
# Warmup Iteration   5: 252.013 ns/op
Iteration   1: 239.986 ns/op
Iteration   2: 249.230 ns/op
Iteration   3: 269.832 ns/op
Iteration   4: 288.434 ns/op
Iteration   5: 267.576 ns/op

Result "org.derek.CreateProxyBenchmark.CglibProxyCreate":
  263.011 ±(99.9%) 72.902 ns/op [Average]
  (min, avg, max) = (239.986, 263.011, 288.434), stdev = 18.932
  CI (99.9%): [190.110, 335.913] (assumes normal distribution)

...

2.5 调用代理方法benchmark结果

代理对象方法调用基准测试结果
Benchmark                             

Mode  

Cnt     

 Score          Units
CallProxyBenchmark.byteBuddyProxyCallavgt5123726968.263 ±  20871995.449ns/op
CallProxyBenchmark.cglibProxyCallavgt5263954955.266 ± 220931004.099ns/op
CallProxyBenchmark.javassistProxyCallavgt5237567867.221 ±  74435415.052ns/op
CallProxyBenchmark.jdkProxyCall  avgt5168462962.111 ± 337985782.973ns/op

从代理的方法调用性能来看:(调用时间越小,性能越好,单位纳秒)

ByteBuddy Proxy > JDK Dynamic Proxy> Javassist Proxy > CGLIB Proxy

详细日志如下:

# JMH version: 1.37
# VM version: JDK 17.0.12, Java HotSpot(TM) 64-Bit Server VM, 17.0.12+8-LTS-286
# VM invoker: C:\Program Files\Java\jdk-17\bin\java.exe
# VM options: --add-opens=java.base/java.lang=ALL-UNNAMED -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2024.3.3\lib\idea_rt.jar=56325:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2024.3.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.derek.CallProxyBenchmark.byteBuddyProxyCall

# Run progress: 0.00% complete, ETA 00:06:40
# Fork: 1 of 1
# Warmup Iteration   1: 108579593.548 ns/op
# Warmup Iteration   2: 108119516.129 ns/op
# Warmup Iteration   3: 113436715.730 ns/op
# Warmup Iteration   4: 107387134.043 ns/op
# Warmup Iteration   5: 154345370.769 ns/op
Iteration   1: 132647452.632 ns/op
Iteration   2: 121247436.145 ns/op
Iteration   3: 124925249.383 ns/op
Iteration   4: 119137738.095 ns/op
Iteration   5: 120676965.060 ns/op

Result "org.derek.CallProxyBenchmark.byteBuddyProxyCall":
  123726968.263 ±(99.9%) 20871995.449 ns/op [Average]
  (min, avg, max) = (119137738.095, 123726968.263, 132647452.632), stdev = 5420390.936
  CI (99.9%): [102854972.814, 144598963.712] (assumes normal distribution)

# Run progress: 25.00% complete, ETA 00:05:05
# Fork: 1 of 1
# Warmup Iteration   1: 116294859.770 ns/op
# Warmup Iteration   2: 123856798.765 ns/op
# Warmup Iteration   3: 123760371.605 ns/op
# Warmup Iteration   4: 118871832.941 ns/op
# Warmup Iteration   5: 123794728.395 ns/op
Iteration   1: 164936025.806 ns/op
Iteration   2: 272795927.027 ns/op
Iteration   3: 301178502.941 ns/op
Iteration   4: 274668105.405 ns/op
Iteration   5: 306196215.152 ns/op


Result "org.derek.CallProxyBenchmark.cglibProxyCall":
  263954955.266 ±(99.9%) 220931004.099 ns/op [Average]
  (min, avg, max) = (164936025.806, 263954955.266, 306196215.152), stdev = 57375080.168
  CI (99.9%): [43023951.167, 484885959.365] (assumes normal distribution)
 

# Run progress: 50.00% complete, ETA 00:03:24
# Fork: 1 of 1
# Warmup Iteration   1: 125305170.370 ns/op
# Warmup Iteration   2: 286637622.857 ns/op
# Warmup Iteration   3: 278121966.667 ns/op
# Warmup Iteration   4: 285848186.111 ns/op
# Warmup Iteration   5: 234395393.023 ns/op
Iteration   1: 258945625.641 ns/op
Iteration   2: 216181610.638 ns/op
Iteration   3: 249889931.707 ns/op
Iteration   4: 218131984.783 ns/op
Iteration   5: 244690183.333 ns/op


Result "org.derek.CallProxyBenchmark.javassistProxyCall":
  237567867.221 ±(99.9%) 74435415.052 ns/op [Average]
  (min, avg, max) = (216181610.638, 237567867.221, 258945625.641), stdev = 19330640.909
  CI (99.9%): [163132452.168, 312003282.273] (assumes normal distribution)

# Run progress: 75.00% complete, ETA 00:01:42
# Fork: 1 of 1
# Warmup Iteration   1: 123866507.407 ns/op
# Warmup Iteration   2: 205614083.673 ns/op
# Warmup Iteration   3: 233406518.605 ns/op
# Warmup Iteration   4: 237938358.140 ns/op
# Warmup Iteration   5: 241431535.714 ns/op
Iteration   1: 286094022.222 ns/op
Iteration   2: 239724626.190 ns/op
Iteration   3: 103729624.742 ns/op
Iteration   4: 107708296.774 ns/op
Iteration   5: 105058240.625 ns/op


Result "org.derek.CallProxyBenchmark.jdkProxyCall":
  168462962.111 ±(99.9%) 337985782.973 ns/op [Average]
  (min, avg, max) = (103729624.742, 168462962.111, 286094022.222), stdev = 87773834.518
  CI (99.9%): [≈ 0, 506448745.083] (assumes normal distribution)

# Run complete. Total time: 00:06:48

Benchmark                              Mode  Cnt          Score           Error  Units
CallProxyBenchmark.byteBuddyProxyCall  avgt    5  123726968.263 ±  20871995.449  ns/op
CallProxyBenchmark.cglibProxyCall      avgt    5  263954955.266 ± 220931004.099  ns/op
CallProxyBenchmark.javassistProxyCall  avgt    5  237567867.221 ±  74435415.052  ns/op
CallProxyBenchmark.jdkProxyCall        avgt    5  168462962.111 ± 337985782.973  ns/op

总结:

在代理对象创建过程中,JDK Dynamic Proxy 和 CGLIB Proxy 排名第一和第二。

在方法调用过程中,ByteBuddy Proxy 和 JDK Dynamic Proxy 排名第一和第二。

 参考资料: 

JMH 快速入门 | JAVA-TUTORIAL

https://github.com/topics/jmh

bytebuddy基本使用bytebuddy基本使用 为什么要生成运行时代码? Java 语言带有相对严格的类型系统。 - 掘金

Byte Buddy - runtime code generation for the Java virtual machine

http://www.dtcms.com/a/109605.html

相关文章:

  • 安全、可靠,企业内部im即时通讯软件选择
  • 十一、buildroot系统登录配置
  • 从0开始的构建的天气预报小时钟(基于STM32F407ZGT6,ESP8266 + SSD1309)——第1章 简单的介绍一下ESP8266和他的编程指令
  • Oracle数据库数据编程SQL<6.2 数据字典表之间的关联关系>
  • C++的智能指针weak_ptr和普通指针的区别
  • 第五课:高清修复和放大算法
  • MySQL安装教程(详细版)
  • Linux应用编程(文件IO)
  • 移远RG200U-CN模组WAKEUP_IN引脚
  • SAP ABAP AVL单元格颜色
  • 问题解决:glog中的LOG(INFO)与VLOG无法打印
  • 每日一题(小白)分析娱乐篇10
  • DDD与MVC扩展能力对比
  • Agent TARS与Manus的正面竞争
  • THUNLP_Multimodal_Excercise
  • Java - WebSocket配置及使用
  • Dart 语法
  • 【Tauri2】013——前端Window Event与创建Window
  • 搭建环境-opencv-qt
  • 震源车:震源激发平板模态分析
  • 使用python实现视频播放器(支持拖动播放位置跳转)
  • 第二十六章:Seaborn库实现统计数据可视化
  • 2025年机动车授权签字人考试判断题分享
  • 2025年渗透测试面试题总结- 某汽车厂商-安全工程师扩展(题目+回答)
  • 量子计算与经典计算的融合与未来
  • AI赋能——让人工智能助力工作提质增效
  • CVPR2024 | 构建时序动作检测模型对时序干扰的鲁棒性基准
  • 近日八股——计算机网络
  • 使用pycharm社区版调试DIFY后端python代码
  • 破解 N 皇后 II:位运算的高效艺术