1. 面試加分項(xiàng):JVM 鎖優(yōu)化和逃逸分析詳解!

        共 2582字,需瀏覽 6分鐘

         ·

        2022-01-24 00:30

        你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

        你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

        編輯:業(yè)余草

        juejin.cn/post/7055891706826194975

        推薦:https://www.xttblog.com/?p=5306

        鎖優(yōu)化

        jvm 在加鎖的過(guò)程中,會(huì)采用自旋、自適應(yīng)、鎖消除、鎖粗化等優(yōu)化手段來(lái)提升代碼執(zhí)行效率。

        自旋鎖和自適應(yīng)自旋

        現(xiàn)在大多的處理器都是多核處理器 ,如果在多核心處理器,有讓兩個(gè)或者以上的線程并行執(zhí)行,我們可以讓一個(gè)等待線程不放棄處理器的執(zhí)行時(shí)間。設(shè)置一個(gè)等待超時(shí)時(shí)間,看線程是否能夠很快的釋放鎖,在等等待的這段時(shí)間可以執(zhí)行一個(gè)空循環(huán),讓當(dāng)前線程繼續(xù)占用 CPU 的時(shí)間片。這就是所謂的「自旋鎖」。

        JVM 中可以通過(guò) +XX:UseSpinning來(lái)開(kāi)啟自旋鎖,在 JDK1.6 過(guò)后默認(rèn)為我們開(kāi)啟。由于自旋鎖的使用會(huì)讓鎖的競(jìng)爭(zhēng)者占用更多的處理器時(shí)間, JVM 規(guī)定了一個(gè)自旋次數(shù)的一個(gè)參數(shù)。我們可以通過(guò) -XX:PreBlockSping來(lái)進(jìn)行更改(默認(rèn)10次)。

        偏向鎖、輕量級(jí)鎖的狀態(tài)轉(zhuǎn)化及對(duì)象 Mark Word 的關(guān)系轉(zhuǎn)換入下圖所示:

        8bdd9e9a379909dddd107438f17a45a6.webp偏向鎖、輕量級(jí)鎖的狀態(tài)轉(zhuǎn)化及對(duì)象 Mark Word 的關(guān)系

        鎖消除

        鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)檢測(cè)到某段需要同步的代碼不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)而實(shí)施的一種對(duì)鎖進(jìn)行消除的優(yōu)化策略。鎖消除的主要判斷依據(jù)于逃逸分析。如果判斷一段代碼,在堆上所有的數(shù)據(jù)都不會(huì)逃逸出去被別的線程訪問(wèn)到,那就把它當(dāng)作棧上的數(shù)據(jù)對(duì)待,認(rèn)為它們是私有的,同步加鎖就無(wú)需進(jìn)行。

        下面是三個(gè)字符串 x, y, z 相加的例子,無(wú)論是從源代碼上還是邏輯上都沒(méi)有進(jìn)行同步

        public?String?concatStr(String?x,?String?y,?String?z)?{
        ????return??x?+?y?+?z;
        }

        String 是一個(gè)不可變的類,對(duì)字符的鏈接總是生成新的 String 對(duì)象來(lái)進(jìn)行的,因此 Javac 編譯器會(huì)對(duì) String 鏈接進(jìn)行自動(dòng)優(yōu)化,在 jdk5 之前字符串鏈接會(huì)轉(zhuǎn)換為 StringBuffer;在 jdk5 之后會(huì)轉(zhuǎn)換為 StringBuilder 對(duì)象連續(xù)的 append()操作,我們看看 javac 過(guò)后,反編譯的結(jié)果:

        public?String?concatStr(String?x,?String?y,?String?z)?{
        ????StringBuilder?sb?=?new?StringBuilder();
        ????sb.append(x);
        ????sb.append(y);
        ????sb.append(z);
        ????return??sb.toString();
        }

        我們?cè)賮?lái)看看 javap反編譯的結(jié)果:

        3a03c3b32b1b1b498ade8667ea508450.webpjavap反編譯的結(jié)果

        這里大家可能會(huì)擔(dān)心 StringBuilder不是線程安全的的操作會(huì)存在線程安全的問(wèn)題嗎?這里的答案是不會(huì),x + y + z操作的優(yōu)化「經(jīng)過(guò)逃逸分析」過(guò)后,他的動(dòng)態(tài)作用域被限制在了 concatStr方法內(nèi),就是說(shuō)當(dāng)前實(shí)際執(zhí)行的 StringBuilder 的操作在 concatStr 方法內(nèi)部,「其他的外部線程無(wú)法訪問(wèn)」到,所以這里「雖然有鎖,但是可以被安全的消除掉。所以當(dāng)我們進(jìn)行編譯過(guò)后,這段代碼就會(huì)忽略掉所有的同步措施直接執(zhí)行?!?/strong>

        鎖粗化

        原則上,我們?cè)趯懘a的時(shí)候,總是推薦將同步塊的作用范圍限制得盡可能的小--只在共享數(shù)據(jù)的實(shí)際操作作用域中才進(jìn)行同步,這樣也是為了使得需要同步的操作盡可能的變少,即使存在鎖的競(jìng)爭(zhēng),等待的鎖的線程也能很快的獲取到鎖。大多數(shù)情況下,上面的原則都是正確的,但是如果「一系列的連續(xù)操作都是對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,甚至加鎖操作時(shí)出現(xiàn)在循環(huán)體之中」的,那即使沒(méi)有線程的競(jìng)爭(zhēng),頻繁的進(jìn)行相互操作也會(huì)導(dǎo)致不必需要的性能損耗

        StringBuffer?buffer?=?new?StringBuffer();
        /**??鎖粗化?*/?
        public?void?append(){?
        ?buffer.append("aaa").append("?bbb").append("?ccc");?
        }

        上面的代碼每次調(diào)用 buffer.append 方法都需要加鎖和解鎖,如果 JVM 家冊(cè)到有一串連續(xù)的對(duì)同一個(gè)對(duì)象加鎖和解鎖的操作,就會(huì)將其合并成一次范圍更大的加鎖解鎖操作,即在第一個(gè) append 方法執(zhí)行的時(shí)候進(jìn)行加鎖,最后一個(gè) append 方法結(jié)束后進(jìn)行解鎖。

        逃逸分析

        逃逸分析(Escape Analysis),是一種可能減少有效 Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨全局函數(shù)數(shù)據(jù)流分析算法。通過(guò)逃逸分析, Java Hotspot 編譯器能夠分析出一個(gè)新的對(duì)象引用范圍從而決定是否要將這個(gè)對(duì)象分配到堆上,「逃逸分析的基本行為就是分析對(duì)象的動(dòng)態(tài)作用域?!?/strong>

        方法逃逸

        當(dāng)一個(gè)對(duì)象在方法里面被定義后,它可能被外部方法所引用,例如調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸。

        線程逃逸

        當(dāng)一個(gè)對(duì)象可能被外部線程訪問(wèn)到,比如:賦值給其他線程中訪問(wèn)的實(shí)例變量,這種稱為線程逃逸。

        通過(guò)逃逸分析,編譯器對(duì)代碼的優(yōu)化

        如果能夠證明一個(gè)對(duì)象不會(huì)逃逸到到方法外或線程外(其他線程方法或者線程無(wú)法通過(guò)任何方法訪問(wèn)該變量),或者逃逸程度比較低(只逃逸出方法而不逃逸出線程)則可以對(duì)這個(gè)對(duì)象采用不同程度的優(yōu)化:

        1、棧上分配(Stack Allocations)完全不會(huì)逃逸的局部變量和不會(huì)逃逸出線程的對(duì)象,采用棧上分配,對(duì)象就會(huì)跟隨方法的結(jié)束自動(dòng)銷毀。以減少垃圾回收器的壓力。

        2、標(biāo)量替換(Scalar Replacement)有個(gè)對(duì)象可能不需要作為一個(gè)連續(xù)的存儲(chǔ)結(jié)果存儲(chǔ)也能被訪問(wèn)到,那么對(duì)象的部分(或者全部)可以不存儲(chǔ)在內(nèi)存,而是存儲(chǔ)在 CPU 寄存器中。

        3、同步消除(Synchronization Elimination)如果一個(gè)對(duì)象發(fā)現(xiàn)只能在一個(gè)線程訪問(wèn)到,那么這個(gè)對(duì)象的操作可以考慮不同步。

        參考資料

        • https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

        瀏覽 48
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
            
            

                      • 小早川怜子绝顶狂潮 | 韩国黄色三级片视频 | 成人v精品秘 蜜桃久久一区 | 岳啊灬啊别停灬啊灬快点老番 | 女攻男受开发菊h女尊产乳 |