天天看點

Jmh測試JDK,CGLIB,JAVASSIST動态代理方式的性能

前言

JDK,CGLIB,JAVASSIST是常用的動态代理方式。

JDK動态代理僅能對具有接口的類進行代理。

CGLIB動态代理方式的目标類可以沒有接口。

Javassist是一個開源的分析、編輯和建立Java位元組碼的類庫,JAVASSIST可以動态修改類,比如添加方法和屬性。JAVASSIST的目标類也沒有接口限制。

動态代理常用在RPC接口調用中,是以選擇一個好的動态代理方式,會對系統性能有一定的提升。

對于代碼的性能測試,正常的方法如下,如此是無法擷取到準确的性能資料的

long start = System.currentTimeMillis();
xxx.xx();
long end = System.currentTimeMillis();
System.out.println("運作時間:"+(end-start));      

JMH用來做基準測試,由于JIT編譯器會根據代碼運作情況進行優化,代碼在第一次執行的時候,速度相對較慢,随着運作的次數增加,JIT編譯器會對代碼進行優化,以達到最佳的性能狀态。

JMH可以對代碼進行預熱,讓代碼達到最佳的性能狀态,再進行性能測試。

更詳細的說明參考: 使用JMH做Benchmark基準測試

本部落客要講解使用JMH對這三種動态代理的對象建立過程和方法調用進行測試。JDK版本是8.0.

代理實作

Jdk方式

public class JdkInvocationHandler  implements InvocationHandler {

    private Object target = null;
    public JdkInvocationHandler(Object object){
        this.target = object;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = method.invoke(this.target,args);
        return result;
    }

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

}      

Cglib方式

引入pom

     <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>      

代碼

public class CglibProxy  implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public  Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();

    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        Object result = methodProxy.invokeSuper(o,objects);

        return result;
    }
}      

Javassist方式

pom

    <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.20</version>
            <scope>compile</scope>
        </dependency>

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

代碼

public class JavassistProxy {

    public <T> T getProxy(Class<T> interfaceClass){
        ProxyFactory proxyFactory  = new ProxyFactory();

        if(interfaceClass.isInterface()){
            Class[] clz  = new Class[1];
            clz[0] = interfaceClass;
            proxyFactory.setInterfaces(clz);
        }
        else {
            proxyFactory.setSuperclass(interfaceClass);
        }
        proxyFactory.setHandler(new MethodHandler() {
            public Object invoke(Object proxy, Method method, Method method1, Object[] args) throws Throwable {
                Object result = method1.invoke(proxy,args);
                return  result;
            }
        });
        try{
            T bean =  (T)proxyFactory.createClass().newInstance();
            return  bean;
        }
        catch(Exception ex){
            log.error("Javassit 建立代理失敗:{}",ex.getMessage());
            return null;
        }
    }

}      

性能測試

建立性能測試

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ProxyCreateTest {

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

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

    @Benchmark
    public void CglibProxyCreate(){
        ProxyService proxyService =  (ProxyService)new CglibProxy().getProxy(ProxyServiceImpl.class);
    }


   @Benchmark
    public void JdkProxyCreate(){
       ProxyService proxyService = (ProxyService) new JdkInvocationHandler(new ProxyServiceImpl()).getProxy();
    }

    @Benchmark
    public void JavassistProxyCreate(){
        ProxyService proxyService = (ProxyService)  new JavassistProxy().getProxy(ProxyServiceImpl.class);
    }

}      

 測試結果

第一次測試
 * Benchmark                             Mode  Cnt        Score        Error  Units
 * ProxyCreateTest.CglibProxyCreate      avgt   20      192.691 ±      5.962  ns/op
 * ProxyCreateTest.JavassistProxyCreate  avgt   20  2741254.026 ± 334384.484  ns/op
 * ProxyCreateTest.JdkProxyCreate        avgt   20      130.982 ±     14.467  ns/op
 *
 * 第二次測試
 * Benchmark                             Mode  Cnt        Score        Error  Units
 * ProxyCreateTest.CglibProxyCreate      avgt   20      212.150 ±     15.399  ns/op
 * ProxyCreateTest.JavassistProxyCreate  avgt   20  2995729.108 ± 265629.897  ns/op
 * ProxyCreateTest.JdkProxyCreate        avgt   20      124.842 ±      8.404  ns/op
 * 
   第三次測試
 * Benchmark                             Mode  Cnt        Score        Error  Units
 * ProxyCreateTest.CglibProxyCreate      avgt   20      206.603 ±      6.834  ns/op
 * ProxyCreateTest.JavassistProxyCreate  avgt   20  2979335.282 ± 290935.626  ns/op
 * ProxyCreateTest.JdkProxyCreate        avgt   20      129.260 ±      9.020  ns/op      

從測試結果來看,javassist的代理對象建立性能最差。最好的是JDK方式。

調用性能測試

//所有測試線程共享一個執行個體
@State(Scope.Benchmark)
//調用的平均時間,例如“每次調用平均耗時xxx毫秒”,機關是時間/操作數
@BenchmarkMode(Mode.AverageTime)
//機關為納秒
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ProxyRunTest {


    private  ProxyService proxyServiceCglib =  (ProxyService)new CglibProxy().getProxy(ProxyServiceImpl.class);
    private  ProxyService proxyServiceJdk = (ProxyService) new JdkInvocationHandler(new ProxyServiceImpl()).getProxy();
    private  ProxyService proxyServiceJavassist = (ProxyService)  new JavassistProxy().getProxy(ProxyServiceImpl.class);
    public static void main(String args[]) throws Exception{

        Options ops = new OptionsBuilder().include(ProxyRunTest.class.getSimpleName())
                .forks(1).build();
        new Runner(ops).run();
    }
    //方法注解,表示該方法是需要進行 benchmark 的對象。
    @Benchmark
    public void CglibProxyRun(){
        proxyServiceCglib.run();
    }
   @Benchmark
    public void JdkProxyRun(){
       proxyServiceJdk.run();
    }
    @Benchmark
    public void JavassistProxyRun(){
        proxyServiceJavassist.run();
    }

}      

測試結果

第一次測試
Benchmark                       Mode  Cnt   Score   Error  Units
ProxyRunTest.CglibProxyRun      avgt   20   9.918 ± 1.268  ns/op
ProxyRunTest.JavassistProxyRun  avgt   20  34.226 ± 2.655  ns/op
ProxyRunTest.JdkProxyRun        avgt   20   5.225 ± 0.449  ns/op
第二次測試
Benchmark                       Mode  Cnt   Score   Error  Units
ProxyRunTest.CglibProxyRun      avgt   20   6.975 ± 0.629  ns/op
ProxyRunTest.JavassistProxyRun  avgt   20  31.707 ± 0.885  ns/op
ProxyRunTest.JdkProxyRun        avgt   20   5.442 ± 0.514  ns/op
第三次測試
Benchmark                       Mode  Cnt   Score   Error  Units
ProxyRunTest.CglibProxyRun      avgt   20   8.079 ± 1.381  ns/op
ProxyRunTest.JavassistProxyRun  avgt   20  33.916 ± 2.904  ns/op
ProxyRunTest.JdkProxyRun        avgt   20   5.947 ± 0.498  ns/op      

從測試結果來看,javassist的代理對象調用執行性能最差。最好的是JDK方式。

總結

1.不管是代理建立還是方法調用執行,Javassist方式的性能最好,JDK的方式最差。

2.對于實際使用過程中。代理對象一般隻會建立一次,建立完成後緩存起來供後續使用,是以對整體性能影響不大。JDK方式和CGLIB方式的方法調用執行性能差不多,實際可根據代理對象有無接口進行選擇。