亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

動態(tài)代理分為兩種,JDK動態(tài)代理和spring里邊使用的Cglib動態(tài)代理。分別使用的是interface和子類繼承的思路來對委托類進行wrap生成代理類。

一直據(jù)說由于JDK動態(tài)代理使用的是反射的方式對委托類的方法進行調(diào)用,性能低,而cglib使用的是字節(jié)碼修改的方式,性能高。

本篇就嘗試搞清楚低為什么低,而高為什么高。

以下分析環(huán)境所用的jdk版本:

JAVA version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

java反射:

  1. 動態(tài)化的調(diào)用使得JIT編譯優(yōu)化沒法做
  2. newInstance創(chuàng)建Object,getDeclareMethod,Method.invoke()耗時

ASM,Cglib:

可以直接生成class文件或在class load之前修改class文件

修改class文件 -> 生成$Proxy類 -> load到jvm,這樣一個過程,所以第一次會慢一些,但一旦載入jvm之后,就跟普通的Java類一樣了,對象的方法調(diào)用也是可以被JIT優(yōu)化的了。

避免大量循環(huán)使用反射調(diào)用,但如果跟JDBC這種SQL調(diào)用一起,那么反射的性能損耗基本可以忽略不記了。

比較Java反射與普通對象方法調(diào)用的性能

我們用一個例子來比較一下普通對象方法調(diào)用、java反射、基于字節(jié)碼修改的reflectAsm反射,這幾種方法調(diào)用方式的性能差別。

import com.esotericsoftware.reflectasm.MethodAccess;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * java反射性能測試
 * */
@Slf4j
public class ReflectTest {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        long start , end;
        int tenMillion = 10000000;

        //1、普通new對象,調(diào)用方法
        DummyObject obj = new DummyObject();
        start = System.currentTimeMillis();
        for(int i=0; i<tenMillion; i++){
            obj.setValue(i);
        }
        log.info("普通對象方法調(diào)用耗時{}ms" , System.currentTimeMillis() - start);
        log.info("value = {}", obj.getValue());

        //2、使用反射,method.invoke調(diào)用方法
        Class clazz = Class.forName("com.wangan.springbootone.aop.ReflectTest$DummyObject");
        Class[] argsType = new Class[1];
        argsType[0] = int.class;
        Method method = clazz.getDeclaredMethod("setValue", argsType);
        DummyObject dummyObject = (DummyObject) clazz.newInstance();
        start = System.currentTimeMillis();
        for(int i=0; i<tenMillion; i++){
            method.invoke(dummyObject, i);
        }
        log.info("反射方法invoke調(diào)用耗時{}ms" , System.currentTimeMillis() - start);
        log.info("value = {}", dummyObject.getValue());

        //3、反射調(diào)用,getDeclaredMethod + invoke耗時
        start = System.currentTimeMillis();
        for(int i=0; i<tenMillion; i++){
            method = clazz.getDeclaredMethod("setValue", argsType); //比較耗時
            method.invoke(dummyObject, i);
        }
        log.info("反射方法getDeclaredMethod + invoke調(diào)用耗時{}ms" , System.currentTimeMillis() - start);
        log.info("value = {}", dummyObject.getValue());

        //4、使用reflectAsm高性能反射庫invoke調(diào)用
        MethodAccess methodAccess = MethodAccess.get(DummyObject.class);
        int index = methodAccess.getIndex("setValue");
        start = System.currentTimeMillis();
        for(int i=0; i<tenMillion; i++){
            methodAccess.invoke(dummyObject, index, i);
        }
        log.info("使用reflectasm的invoke調(diào)用耗時{}ms" , System.currentTimeMillis() - start);
        log.info("value = {}", dummyObject.getValue());
    }

    public static class DummyObject{
        private int value;

        public void setValue(int v){
            value = v;
        }
        public int getValue(){
            return value;
        }
    }
}

輸出:

11:35:58.720 [main] INFO com.wangan.springbootone.aop.ReflectTest - 普通對象方法調(diào)用耗時4ms
11:35:58.787 [main] INFO com.wangan.springbootone.aop.ReflectTest - 反射方法invoke調(diào)用耗時62ms
11:35:59.913 [main] INFO com.wangan.springbootone.aop.ReflectTest - 反射方法getDeclaredMethod + invoke調(diào)用耗時1126ms
11:35:59.991 [main] INFO com.wangan.springbootone.aop.ReflectTest - 使用reflectasm的invoke調(diào)用耗時61ms

對一個DummyObject的set方法調(diào)用1千萬次,普通方法耗時僅4ms,java反射方法只method.invoke的話是62ms,使用reflectasm的invoke耗時接近、61ms, 最慢的是java反射class.getDeclaredMethod + method.invoke、需要1126ms。

我們可以得出幾個階段性結論:

普通對象方法調(diào)用最快如果僅測試method.invoke的話,那么java自己的反射方法調(diào)用跟reflectasm的invoke性能差不多所謂java反射性能不行,實際上我們看是慢在getDeclaredMethod上,也就是根據(jù)Class對象到方法區(qū)里邊查找類的方法定義的過程,找到方法定義之后真正method.invoke方法調(diào)用其實不算很慢。getDeclaredMethod非常慢、差300倍了,method.invoke跟普通的對象方法調(diào)用相比也慢了10幾倍差1個數(shù)量級的樣子。

嘗試分析一波原因

Class.getDeclaredMethod方法:

@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    //接入校驗
    checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
    //方法查找
    Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
    if (method == null) {
        throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
    }
    return method;
}

checkMemberAccess校驗方法是否允許調(diào)用,可見性檢查。

privateGetDeclaredMethods方法查找先嘗試取緩存,沒找到就調(diào)用getDeclaredMethods0這個native方法,request value from VM 。使用緩存,這個JNI調(diào)用是個相對耗時的操作。

Method.invoke方法:

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) { //參數(shù)校驗
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

MethodAccessor的實現(xiàn)有java版本和native版本

public MethodAccessor newMethodAccessor(Method var1) {
    checkInitted();
    if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
        // 這里返回的是MethodAccessorImpl
        return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
    } else {
        //否則使用NativeMethodAccessorImpl
        NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
        DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
        var2.setParent(var3);
        return var3;
    }
}
//反射調(diào)用超過這個次數(shù)則使用MethodAccessorImpl,否則默認使用NativeMethodAccessorImpl
inflationThreshold = 15; 

Java 反射效率低主要原因

  1. Method#invoke 方法會對參數(shù)做封裝和解封操作
  2. 需要檢查方法可見性
  3. 需要校驗參數(shù)
  4. 反射方法難以內(nèi)聯(lián)
  5. JIT 無法優(yōu)化
  6. 請求jvm去查找其方法區(qū)中的方法定義,需要使用jni、開銷相對比較大。

所以cglib使用了FastClass機制來索引類的方法調(diào)用。也能實現(xiàn)Java反射的"運行時動態(tài)方法調(diào)用"的功能。

來源:
https://www.cnblogs.com/lyhero11/p/15558956.html

分享到:
標簽:Java
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定