最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

引言

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

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

synchronized

代碼塊級(jí)別的 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 虛擬機(jī)中代碼塊的同步是通過 monitorenter 和 monitorexit 兩個(gè)支持 synchronized 關(guān)鍵字語意的。比如上面的字節(jié)碼

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

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

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

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

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

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

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

方法級(jí)的 synchronized

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

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

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

反射

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

先來看下面這個(gè)例子:

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);
        }
    }
}

運(yùn)行結(jié)果如下

可以看到同一段代碼,運(yùn)行的堆棧結(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 是一個(gè)接口

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

從輸出的堆??梢钥吹?MethodAccessor 的實(shí)現(xiàn)類是委托類DelegatingMethodAccessorImpl,它的 invoke 函數(shù)非常簡(jiǎn)單,就是把調(diào)用委托給了真正的實(shí)現(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)用中,實(shí)現(xiàn)類是 NativeMethodAccessorImpl,從第 16 次調(diào)用開始實(shí)現(xiàn)類是 GeneratedMethodAccessor1,為什么是這樣呢?玄機(jī)就在 NativeMethodAccessorImpl 的 invoke 方法中

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

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

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

翻譯一下這個(gè)字節(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)用的方式要比動(dòng)態(tài)生成類調(diào)用的方式慢 20 倍,但是又由于第一次字節(jié)碼生成的過程比較慢。如果反射僅調(diào)用一次的話,采用生成字節(jié)碼的方式反而比 native 調(diào)用的方式慢 3 ~ 4 倍。

inflation 機(jī)制

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

JVM 與 inflation 相關(guān)的屬性有兩個(gè),一個(gè)是剛提到的閾值 sun.reflect.inflationThreshold,還有一個(gè)是是否禁用 inflation的屬性 sun.reflect.noInflation,默認(rèn)值為 false。如果把這個(gè)值設(shè)置成true 的話,從第 0 次開始就使用動(dòng)態(tài)生成類的方式來調(diào)用反射方法了,不會(huì)使用 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é)碼角度去了解一下, 相信你會(huì)有很多不一樣的收獲,更多關(guān)于字節(jié)碼解析synchronized反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一篇文章帶你深入了解Java對(duì)象與Java類

    一篇文章帶你深入了解Java對(duì)象與Java類

    這篇文章主要給大家介紹了關(guān)于java中類和對(duì)象的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • Java中List轉(zhuǎn)Array的兩種主要方式

    Java中List轉(zhuǎn)Array的兩種主要方式

    在Java中經(jīng)常遇到需要List與數(shù)組互相轉(zhuǎn)換的場(chǎng)景,這篇文章主要給大家介紹了關(guān)于Java中List轉(zhuǎn)Array的兩種主要方式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • springboot創(chuàng)建攔截器過程圖解

    springboot創(chuàng)建攔截器過程圖解

    這篇文章主要介紹了springboot創(chuàng)建攔截器過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • SpringBoot中啟用和測(cè)試HTTP/2的幾種方法

    SpringBoot中啟用和測(cè)試HTTP/2的幾種方法

    HTTP/2即超文本傳輸協(xié)議第二版,使用于萬維網(wǎng),HTTP/2主要基于SPDY協(xié)議,通過對(duì)HTTP頭字段進(jìn)行數(shù)據(jù)壓縮、對(duì)數(shù)據(jù)傳輸采用多路復(fù)用和增加服務(wù)端推送等舉措,本文給大家介紹了SpringBoot中啟用和測(cè)試HTTP/2的幾種方法,需要的朋友可以參考下
    2025-10-10
  • SpringBoot服務(wù)設(shè)置禁止server.point端口的使用

    SpringBoot服務(wù)設(shè)置禁止server.point端口的使用

    本文主要介紹了SpringBoot服務(wù)設(shè)置禁止server.point端口的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • java抓取網(wǎng)頁數(shù)據(jù)示例

    java抓取網(wǎng)頁數(shù)據(jù)示例

    要通java獲取整個(gè)網(wǎng)頁的html內(nèi)容,或者某個(gè)網(wǎng)絡(luò)文件的內(nèi)容,可以使用java提供的HttpURLConnection類來實(shí)現(xiàn)對(duì)網(wǎng)頁內(nèi)容的抓取
    2014-03-03
  • java HashMap詳解及實(shí)例代碼

    java HashMap詳解及實(shí)例代碼

    這篇文章主要介紹了java HashMap詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • 利用JWT如何實(shí)現(xiàn)對(duì)API的授權(quán)訪問詳解

    利用JWT如何實(shí)現(xiàn)對(duì)API的授權(quán)訪問詳解

    這篇文章主要給大家介紹了關(guān)于利用JWT如何實(shí)現(xiàn)對(duì)API的授權(quán)訪問的相關(guān)資料,需要的朋友可以參考下
    2018-09-09
  • SpringBoot2.0如何啟用https協(xié)議

    SpringBoot2.0如何啟用https協(xié)議

    這篇文章主要介紹了SpringBoot2.0如何啟用https協(xié)議,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-06-06
  • Java編譯錯(cuò)誤java.lang.NoSuchFieldError的解決方案詳析

    Java編譯錯(cuò)誤java.lang.NoSuchFieldError的解決方案詳析

    java.lang.NoSuchFieldError是 Java 中的一種運(yùn)行時(shí)錯(cuò)誤,這篇文章主要介紹了Java編譯錯(cuò)誤java.lang.NoSuchFieldError的解決方案,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-11-11

最新評(píng)論

泸州市| 平南县| 马龙县| 贵德县| 阳高县| 宿州市| 瑞金市| 广昌县| 临武县| 封丘县| 泰州市| 深州市| 贡觉县| 甘孜县| 华蓥市| 桂林市| 西华县| 九江市| 宝清县| 弥渡县| 永州市| 武强县| 元阳县| 上犹县| 延长县| 金塔县| 溆浦县| 阿克| 确山县| 五台县| 宁明县| 化德县| 彝良县| 汽车| 万年县| 湘西| 龙南县| 工布江达县| 桐乡市| 西充县| 台湾省|