欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從字節(jié)碼角度解析synchronized和反射實現(xiàn)原理

 更新時間:2023年08月15日 09:01:01   作者:悅  
這篇文章主要介紹了從字節(jié)碼角度解析synchronized和反射的實現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

前幾天,關(guān)于字節(jié)碼技術(shù),我們講了字節(jié)碼的基礎(chǔ), 常見的字節(jié)碼框架以及在軟件破解和APM鏈路監(jiān)控方面的一些應(yīng)用.

今天我們回到Java本身, 看下我們常用的synchronized關(guān)鍵字和反射在字節(jié)碼層面是如何實現(xiàn)的.

synchronized

代碼塊級別的 synchronized

如下方法的內(nèi)部使用了synchronized關(guān)鍵字

private Object lock = new Object();
public void foo() {
    synchronized (lock) {
        bar();
    }
}
public void bar() { }

編譯成字節(jié)碼如下

public void foo();
    Code:
       0: aload_0
       1: getfield      #3                  // Field lock:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: aload_0
       8: invokevirtual #4                  // Method bar:()V
      11: aload_1
      12: monitorexit
      13: goto          21
      16: astore_2
      17: aload_1
      18: monitorexit
      19: aload_2
      20: athrow
      21: return
    Exception table:
       from    to  target type
           7    13    16   any
          16    19    16   any

Java 虛擬機中代碼塊的同步是通過 monitorenter 和 monitorexit 兩個支持 synchronized 關(guān)鍵字語意的。比如上面的字節(jié)碼

  • 0 ~ 5:將 lock 對象入棧,使用 dup 指令復(fù)制棧頂元素,并將它存入局部變量表位置 1 的地方,現(xiàn)在棧上還剩下一個 lock 對象
  • 6:以棧頂元素 lock 做為鎖,使用 monitorenter 開始同步
  • 7 ~ 8:調(diào)用 bar() 方法
  • 11 ~ 12:將 lock 對象入棧,調(diào)用 monitorexit 釋放鎖

monitorenter 對操作數(shù)棧的影響如下

  • 16 ~ 20:執(zhí)行異常處理,我們代碼中本來沒有 try-catch 的代碼,為什么字節(jié)碼會幫忙加上這段邏輯呢?
    因為編譯器必須保證,無論同步代碼塊中的代碼以何種方式結(jié)束(正常 return 或者異常退出),代碼中每次調(diào)用 monitorenter 必須執(zhí)行對應(yīng)的 monitorexit 指令。為了保證這一點,編譯器會自動生成一個異常處理器,這個異常處理器的目的就是為了同步代碼塊拋出異常時能執(zhí)行 monitorexit。這也是字節(jié)碼中,只有一個 monitorenter 卻有兩個 monitorexit 的原因

可理解為這樣的一段 Java 代碼

public void _foo() throws Throwable {
    monitorenter(lock);
    try {
        bar();
    } finally {
        monitorexit(lock);
    }
}

根據(jù)我們之前介紹的 try-catch-finally 的字節(jié)碼實現(xiàn)原理,復(fù)制 finally 語句塊到所有可能函數(shù)退出的地方,上面的代碼等價于

public void _foo() throws Throwable {
    monitorenter(lock);
    try {
        bar();
        monitorexit(lock);
    } catch (Throwable e) {
        monitorexit(lock);
        throw e;
    }
}

方法級的 synchronized

方法級的同步與上述有所不同,它是由常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來隱式實現(xiàn)的。

synchronized public void testMe() {
}
對應(yīng)字節(jié)碼
public synchronized void testMe();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED

JVM 不會使用特殊的字節(jié)碼來調(diào)用同步方法,當(dāng) JVM 解析方法的符號引用時,它會判斷方法是不是同步的(檢查方法 ACC_SYNCHRONIZED 是否被設(shè)置)。如果是,執(zhí)行線程會先嘗試獲取鎖。如果是實例方法,JVM 會嘗試獲取實例對象的鎖,如果是類方法,JVM 會嘗試獲取類鎖。在同步方法完成以后,不管是正常返回還是異常返回,都會釋放鎖.

反射

在 Java 中反射隨處可見,它底層的原也比較有意思,這篇文章來詳細(xì)介紹反射背后的原理。

先來看下面這個例子:

public class ReflectionTest {
    private static int count = 0;
    public static void foo() {
        new Exception("test#" + (count++)).printStackTrace();
    }
    public static void main(String[] args) throws Exception {
        Class<?> clz = Class.forName("ReflectionTest");
        Method method = clz.getMethod("foo");
        for (int i = 0; i < 20; i++) {
            method.invoke(null);
        }
    }
}

運行結(jié)果如下

可以看到同一段代碼,運行的堆棧結(jié)果與執(zhí)行次數(shù)有關(guān)系,在 0 ~ 15 次調(diào)用方式為sun.reflect.NativeMethodAccessorImpl.invoke0,從第 16 次開始調(diào)用方式變?yōu)榱?code>sun.reflect.GeneratedMethodAccessor1.invoke。原因是什么呢?繼續(xù)往下看。

反射方法源碼分析

Method.invoke 源碼如下:

可以最終調(diào)用了MethodAccessor.invoke方法,MethodAccessor 是一個接口

public interface MethodAccessor {
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException;
}

從輸出的堆??梢钥吹?MethodAccessor 的實現(xiàn)類是委托類DelegatingMethodAccessorImpl,它的 invoke 函數(shù)非常簡單,就是把調(diào)用委托給了真正的實現(xiàn)類。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

通過堆棧可以看到在第 0 ~ 15 次調(diào)用中,實現(xiàn)類是 NativeMethodAccessorImpl,從第 16 次調(diào)用開始實現(xiàn)類是 GeneratedMethodAccessor1,為什么是這樣呢?玄機就在 NativeMethodAccessorImpl 的 invoke 方法中

前 0 ~ 15 次都會調(diào)用到invoke0,這是一個 native 的函數(shù)。

private static native Object invoke0(Method m, Object obj, Object[] args);

有興趣的同學(xué)可以去看一下 Hotspot 的源碼,依次跟蹤下面的代碼和函數(shù):

./jdk/src/share/native/sun/reflect/NativeAccessors.c
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
./hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
./hotspot/src/share/vm/runtime/reflection.cpp
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS)

這里不詳細(xì)展開 native 實現(xiàn)的細(xì)節(jié)。
15 次以后會走新的邏輯,使用 GeneratedMethodAccessor1 來調(diào)用反射的方法。MethodAccessorGenerator 的作用是通過 ASM 生成新的類 sun.reflect.GeneratedMethodAccessor1。為了查看整個類的內(nèi)容,可以使用阿里的 arthas 工具。修改上面的代碼,在 main 函數(shù)的最后加上System.in.read();讓 JVM 進(jìn)程不要退出。 執(zhí)行 arthas 工具中的./as.sh,會要求輸入 JVM 進(jìn)程

選擇在運行的 ReflectionTest 進(jìn)程號 7 就進(jìn)入到了 arthas 交互性界面。執(zhí)行 dump sun.reflect.GeneratedMethodAccessor1文件就保存到了本地。

來看下這個類的字節(jié)碼

翻譯一下這個字節(jié)碼,忽略掉異常處理以后的代碼如下

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
    @Override
    public Object invoke(Object obj, Object[] args)
            throws IllegalArgumentException, InvocationTargetException {
        ReflectionTest.foo();
        return null;
    }
}

那為什么要采用 0 ~ 15 次使用 native 方式來調(diào)用,15 次以后使用 ASM 新生成的類來處理反射的調(diào)用呢?

一切都是基于性能的考慮。JNI native 調(diào)用的方式要比動態(tài)生成類調(diào)用的方式慢 20 倍,但是又由于第一次字節(jié)碼生成的過程比較慢。如果反射僅調(diào)用一次的話,采用生成字節(jié)碼的方式反而比 native 調(diào)用的方式慢 3 ~ 4 倍。

inflation 機制

因為很多情況下,反射只會調(diào)用一次,因此 JVM 想了一招,設(shè)置了 15 這個 sun.reflect.inflationThreshold 閾值,反射方法調(diào)用超過 15 次時(從 0 開始),采用 ASM 生成新的類,保證后面的調(diào)用比 native 要快。如果小于 15 次的情況下,還不如生成直接 native 來的簡單直接,還不造成額外類的生成、校驗、加載。這種方式被稱為 「inflation 機制」。inflation 這個單詞也比較有意思,它的字面意思是「膨脹;通貨膨脹」。

JVM 與 inflation 相關(guān)的屬性有兩個,一個是剛提到的閾值 sun.reflect.inflationThreshold,還有一個是是否禁用 inflation的屬性 sun.reflect.noInflation,默認(rèn)值為 false。如果把這個值設(shè)置成true 的話,從第 0 次開始就使用動態(tài)生成類的方式來調(diào)用反射方法了,不會使用 native 的方式。

增加 noInflation 屬性重新執(zhí)行上述 Java 代碼

java -cp . -Dsun.reflect.noInflation=true ReflectionTest

輸出結(jié)果為

java.lang.Exception: test#0
        at ReflectionTest.foo(ReflectionTest.java:10)
        at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at ReflectionTest.main(ReflectionTest.java:18)
java.lang.Exception: test#1
        at ReflectionTest.foo(ReflectionTest.java:10)
        at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at ReflectionTest.main(ReflectionTest.java:18)

可以看到,從第 0 次開始就已經(jīng)沒有使用 native 方法來調(diào)用反射方法了。

小結(jié)

這篇文章主要從字節(jié)碼角度看了Java中的synchronized和射調(diào)用底層的原理,當(dāng)然還有一些其他比較有意思的語法比如lambda, switch等, 感興趣的小伙伴也可以從字節(jié)碼角度去了解一下, 相信你會有很多不一樣的收獲,更多關(guān)于字節(jié)碼解析synchronized反射的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringMVC集成Swagger實例代碼

    SpringMVC集成Swagger實例代碼

    本篇文章主要介紹了SpringMVC集成Swagger實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • 解決JPA @OneToMany及懶加載無效的問題

    解決JPA @OneToMany及懶加載無效的問題

    這篇文章主要介紹了解決JPA @OneToMany及懶加載無效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • request如何獲取body的json數(shù)據(jù)

    request如何獲取body的json數(shù)據(jù)

    這篇文章主要介紹了request如何獲取body的json數(shù)據(jù)操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringBoot使用Caffeine實現(xiàn)內(nèi)存緩存示例詳解

    SpringBoot使用Caffeine實現(xiàn)內(nèi)存緩存示例詳解

    caffeine提供了四種緩存策略:分別為手動加載、自動加載、異步手動加載、異步自動加載,這篇文章主要介紹了SpringBoot使用Caffeine實現(xiàn)內(nèi)存緩存,需要的朋友可以參考下
    2023-06-06
  • Java自定義標(biāo)簽用法實例分析

    Java自定義標(biāo)簽用法實例分析

    這篇文章主要介紹了Java自定義標(biāo)簽用法,結(jié)合實例形式分析了java自定義標(biāo)簽的定義、使用方法與相關(guān)注意事項,需要的朋友可以參考下
    2017-11-11
  • Spring中Bean的作用域與生命周期詳解

    Spring中Bean的作用域與生命周期詳解

    這篇文章主要給大家介紹了Spring中Bean的生命周期和作用域及實現(xiàn)方式的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考價值,需要的朋友們下面來一起看看吧
    2021-08-08
  • springboot整合mybatisplus的方法詳解

    springboot整合mybatisplus的方法詳解

    這篇文章主要為大家詳細(xì)介紹了springboot整合mybatisplus的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • springIoc及注解的使用實例詳解

    springIoc及注解的使用實例詳解

    注解(Annotation)是一種在 Java 程序中以元數(shù)據(jù)的形式對代碼進(jìn)行標(biāo)記和說明的機制,它可以被添加到類、方法、字段、參數(shù)等程序元素上,用于提供額外的信息和指示,本文給大家介紹springIoc及注解的使用,感興趣的朋友一起看看吧
    2024-02-02
  • Java中實現(xiàn)在一個方法中調(diào)用另一個方法

    Java中實現(xiàn)在一個方法中調(diào)用另一個方法

    下面小編就為大家分享一篇Java中實現(xiàn)在一個方法中調(diào)用另一個方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • Java ZooKeeper分布式鎖實現(xiàn)圖解

    Java ZooKeeper分布式鎖實現(xiàn)圖解

    ZooKeeper是一個分布式的,開放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個開源的實現(xiàn),是Hadoop和Hbase的重要組件。它是一個為分布式應(yīng)用提供一致性服務(wù)的軟件,提供的功能包括:配置維護(hù)、域名服務(wù)、分布式同步、組服務(wù)等
    2022-03-03

最新評論