亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

關(guān)于偏向鎖,安全點(diǎn),JIT的一些暗坑.

JeOam / 3635人閱讀

摘要:前言本文是一篇簡(jiǎn)短的雜糅本文源自于作者最近的一個(gè)疑問(wèn)為什么在舊版的中偏向鎖的移除一定要在全局安全點(diǎn)進(jìn)行同時(shí)在上個(gè)星期作者參與的一個(gè)項(xiàng)目發(fā)生了一件怪事一個(gè)服務(wù)莫名其妙地不接受任何請(qǐng)求了一切請(qǐng)求都是而查看日志發(fā)現(xiàn)出故障的服務(wù)本身去請(qǐng)求另一個(gè)服務(wù)

前言

本文是一篇簡(jiǎn)短的雜糅.

本文源自于作者最近的一個(gè)疑問(wèn):為什么在舊版的jdk中偏向鎖的移除一定要在全局安全點(diǎn)進(jìn)行?同時(shí)在上個(gè)星期,作者參與的一個(gè)項(xiàng)目發(fā)生了一件怪事:一個(gè)服務(wù)莫名其妙地不接受任何請(qǐng)求了,一切請(qǐng)求都是timeout,而查看日志,發(fā)現(xiàn)出故障的服務(wù)本身去請(qǐng)求另一個(gè)服務(wù),請(qǐng)求與響應(yīng)在幾十毫秒完成,卻原地停頓四十余秒,最后報(bào)出超時(shí)異常.

作者在調(diào)研這兩個(gè)問(wèn)題期間搜索了大量以"偏向鎖"和"安全點(diǎn)"為關(guān)鍵詞的介紹,盡管最終也沒(méi)能找出準(zhǔn)確的答案,但是在調(diào)研過(guò)程中還是有所得,值得記錄一個(gè)短篇了.

偏向鎖的疑問(wèn)

首先是偏向鎖的移除:

我們知道,從java6開始,自帶的synchronized鎖進(jìn)行了大量的優(yōu)化,有一個(gè)膨脹的過(guò)程,從無(wú)鎖-偏向鎖-輕量鎖-重量鎖依次膨脹,第一次加鎖時(shí),允許線程將該監(jiān)視器偏向自己,直到發(fā)生其他線程爭(zhēng)搶(偏向鎖持有線程在退出同步塊時(shí)不移除偏向,此種情況可以重偏向),此時(shí)偏向鎖被移除,并膨脹為輕量鎖.

這個(gè)過(guò)程可以簡(jiǎn)單理解為其他線程請(qǐng)求鎖,虛擬機(jī)要所有線程在最近的安全點(diǎn)阻塞,vm線程偽造一個(gè)displaced?mark?word到持有者線程的棧楨,更改監(jiān)視器的標(biāo)記位,然后讓所有線程繼續(xù)執(zhí)行.此時(shí)持有鎖的線程會(huì)因此自視為輕量鎖,競(jìng)爭(zhēng)者也將按照輕量鎖的規(guī)則去競(jìng)爭(zhēng).

作者查看了大量的貼子和資料,哪怕是在官方的文章中,甚至一些貼了官方原碼的注釋中,也只有大概這樣的描述:偏向鎖的移除需要在全局安全點(diǎn)執(zhí)行,就是不解釋為什么.

也許就沒(méi)有為什么吧,單純是官方的實(shí)現(xiàn)問(wèn)題,在前面的文章"54個(gè)JAVA官方文檔術(shù)語(yǔ)"和"JAVA9-12"中曾簡(jiǎn)單提過(guò),從JAVA10起出現(xiàn)了一個(gè)新的功能"線程局部握手",它能幫助我們做若干事情,其中一件就是由vm線程和java線程在多帶帶線程的安全點(diǎn)移除偏向鎖,而不需要等待全局安全點(diǎn),同時(shí)在握手期間,會(huì)阻止進(jìn)入全局安全點(diǎn).經(jīng)過(guò)這么久的資料查找,作者看來(lái)在java10之前必須全局安全點(diǎn)才能移除偏向鎖這件事本身就似乎沒(méi)有為什么,只是從前就這樣設(shè)計(jì)的.

偏向鎖的存在意義:

我們知道,偏向鎖的目標(biāo)是減少昂貴的原子指令cas等的使用以及互斥量的開銷;輕量鎖的目標(biāo)是減少互斥量的開銷.偏向鎖在不考慮重偏向這種情況下,似乎只有第一次加鎖才起作用,那么這個(gè)問(wèn)題似乎有些多余,我們會(huì)對(duì)沒(méi)有競(jìng)爭(zhēng)的代碼加上同步嗎?

答案是會(huì)的.大體有以下場(chǎng)景:

1.類加載其實(shí)是加鎖的,我們可以嘗試并發(fā)地進(jìn)行類加載,盡管大多情況下這由main線程完成.

2.一些舊版本的庫(kù),如使用Vector,使用HashTable,使用Collections.synchronize系列,在絕對(duì)不會(huì)出現(xiàn)線程逃逸的情況下使用StringBuffer拼接字符串,單線程使用了某些庫(kù)中加了同步的代碼等.

3.默認(rèn)的情況下在jvm啟動(dòng)的前幾秒偏向鎖是不可用的,可以使用-XX:BiasedLockingStartupDelay=0進(jìn)行配置.

以上情況可參考問(wèn)題:偏向鎖的設(shè)計(jì).

偏向鎖的設(shè)計(jì)疑問(wèn),為什么只在對(duì)象頭中保存線程id?

可以參考:偏向鎖與輕量鎖的設(shè)計(jì)不同.

偏向鎖退出同步塊其實(shí)是無(wú)操作的,偏向鎖標(biāo)記依舊存在,所以自然恢復(fù),規(guī)避了昂貴的原子指令和屏障的開銷,但是輕量鎖就不同了,需要在設(shè)置標(biāo)記時(shí)保存鎖記錄的指針,同時(shí)還要將原來(lái)的信息存放到棧楨.這樣在釋放時(shí),可以使用cas恢復(fù)原值.

Unlocked:

[ orig_header | 001 ]       | Stack frame |
                            |             |
Locked:                     |             |
[ stack_ptr   | 000 ]       |             |
     |                      |-------------|
      --------------------->| orig_header |
                            |-------------|
                            |             |
                            |             |
                             -------------

重偏向問(wèn)題:

偏向鎖的設(shè)計(jì)初衷是同一個(gè)線程一次或若干次往復(fù)地對(duì)同一個(gè)或幾個(gè)監(jiān)視器加鎖,顯然只有首次需要一個(gè)原子指令.而jvm足夠地聰明,它會(huì)發(fā)現(xiàn)當(dāng)前是否為值得偏向的無(wú)競(jìng)態(tài)同步.

偏向鎖可以重偏向的一點(diǎn)細(xì)節(jié):

1.HotSpot虛擬機(jī)僅支持"粗放"的重偏向(bulk rebias),用以在承受單隊(duì)列重偏向過(guò)程的開銷同時(shí)保留優(yōu)化的收益.

2.粗放的偏向鎖重偏向和移除這兩件事共享了同一個(gè)安全點(diǎn)操作名:RevokeBias.

3.如果滿足這幾個(gè)條件:偏向鎖撤消次數(shù)超過(guò)了BiasedLockingBulkRebiasThreshold并且小于BiasedLockingBulkRevokeThresholdand,且最后一次撤消偏向不晚于BiasedLockingDecayTime,且所有逃逸的變量都限定于jvm的屬性,則后續(xù)的偏向鎖粗放重偏向是可用的.

4.使用-XX:+PrintSafepointStatistics可打印安全點(diǎn)事件,與偏向鎖有關(guān)的可重點(diǎn)可關(guān)注EnableBiasedLocking,RevokeBias和BulkRevokeBias.選項(xiàng)-XX:+TraceBiasedLocking可以幫助生成一個(gè)詳細(xì)描述jvm做出的偏向鎖決策的日志.

參考:單個(gè)偏向鎖的重偏向.

安全點(diǎn)和JIT

關(guān)于安全點(diǎn)和JIT本身此處不再綴述,此處簡(jiǎn)單回憶若干前提.

JIT有client和server模式,其中server模式是高度優(yōu)化的,甚至于可以用"過(guò)度優(yōu)化"來(lái)形容,在"54個(gè)java官方文檔術(shù)語(yǔ)"這篇文章中甚至提過(guò)一個(gè)"不常見的陷阱",發(fā)生時(shí)會(huì)反優(yōu)化并退回解釋執(zhí)行.

JIT高度編譯優(yōu)化的代碼和字節(jié)碼解釋執(zhí)行不同,可能會(huì)進(jìn)行一些安全點(diǎn)的消除,并且編譯代碼要在全局安全點(diǎn)進(jìn)行一次"棧上替換"(OSR),然后才能生效.

參考:循環(huán)的線程奇怪地阻塞了其他線程?

老外寫的一個(gè)代碼例子,非常像我們項(xiàng)目碰到的停頓現(xiàn)象,我們的代碼也類似,確實(shí)有大量的同步操作(必然涉及偏向鎖和移除,同時(shí)也涉及到JIT的棧上替換和計(jì)數(shù)大循環(huán)):

//代碼
public class TestBlockingThread {
private static final Logger LOGGER = LoggerFactory.getLogger(TestBlockingThread.class);

public static final void main(String[] args) throws InterruptedException {
    Runnable task = () -> {
        int i = 0;
        while (true) {
            i++;
            if (i != 0) {
                boolean b = 1 % i == 0;
            }
        }
    };

    new Thread(new LogTimer()).start();
    Thread.sleep(2000);
    new Thread(task).start();
}

public static class LogTimer implements Runnable {
    @Override
    public void run() {
        while (true) {
            long start = System.currentTimeMillis();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // do nothing
            }
            LOGGER.info("timeElapsed={}", System.currentTimeMillis() - start);
        }
    }
}
}
//打印日志
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=13331
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1006
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004

顯然中間那一個(gè)13秒多的等待時(shí)間就像我們項(xiàng)目中的40秒暫停一樣突兀,這也一度讓作者認(rèn)為找對(duì)了答案.

該代碼中,每行日志的打印預(yù)期應(yīng)該是間隔一秒上下,可以看到除了13秒多的一次停頓以外,其他操作的差距都是3-6毫秒的級(jí)別.

為什么會(huì)發(fā)生這樣的情況?

注意前面提到的前提,JIT編譯的高度優(yōu)化代碼需要在全局安全點(diǎn)進(jìn)行棧上替換,也就是說(shuō),它需要要求所有線程到最近的一個(gè)安全點(diǎn)阻塞.

正常情況下,每一個(gè)JAVA線程會(huì)輪詢一個(gè)安全點(diǎn)標(biāo)記(safepoint?flag)來(lái)詢問(wèn)是否要進(jìn)入安全點(diǎn),當(dāng)觀察到去安全點(diǎn)標(biāo)記(go?to?safepoint?flag)時(shí),會(huì)趕去最近的安全點(diǎn).但是,大量地進(jìn)行安全點(diǎn)標(biāo)記的輪詢是耗費(fèi)性能的,因此C1C2編譯器做了相應(yīng)的優(yōu)化,消除了過(guò)于頻繁的安全點(diǎn)輪詢,因此安全點(diǎn)輪詢主要有以下幾種情況:

1.使用解釋器執(zhí)行時(shí)任意兩個(gè)字節(jié)碼之間.

2.C1C2編譯器生成的代碼的非計(jì)數(shù)循環(huán)的"回邊"(參考了深入理解java虛擬機(jī)的回邊計(jì)數(shù)器,方法調(diào)用計(jì)數(shù)器的翻譯).

3.在C1C2編譯器的方法的退出(OpenJDK虛擬機(jī))和進(jìn)入(Zing),但當(dāng)方法已經(jīng)被內(nèi)聯(lián)時(shí),編譯器將移除這個(gè)安全點(diǎn)的輪詢點(diǎn).

注意示例代碼的task線程,它進(jìn)行的是一個(gè)計(jì)數(shù)的循環(huán),因?yàn)橛?jì)數(shù)的循環(huán)會(huì)讓編譯器認(rèn)為是一個(gè)"有限"的循環(huán),因此每個(gè)回邊不會(huì)插入相應(yīng)的安全點(diǎn)輪詢.

故此,JIT在試圖將編譯優(yōu)化的代碼進(jìn)行OSR時(shí),其他線程已趕到安全點(diǎn)阻塞,但是task線程卻依舊未能及時(shí)到達(dá)安全點(diǎn),直到JIT最終放棄了等待并判定為無(wú)限循環(huán)為止.

解決方案:

1.增加選項(xiàng)-XX:+UseCountedLoopSafepoints?,可以看到問(wèn)題立即消失了,但要注意,它會(huì)造成全局性能的永久下降,并可能造成jvm崩盤.加上這個(gè)選項(xiàng)后,編譯器會(huì)在每輪循環(huán)回邊進(jìn)行安全點(diǎn)輪詢,問(wèn)題解決.
2.顯式禁用某方法的編譯:-XX:CompileCommand="exclude,binary/class/Name,methodName

3.手動(dòng)增加安全點(diǎn)輪詢,如在循環(huán)的結(jié)束處增加Thread.yield()或直接將計(jì)數(shù)器i改為long型(此時(shí)再回去翻doug大神的源碼,一定要思考yield和long型計(jì)數(shù)器),這樣循環(huán)會(huì)被編譯器認(rèn)為是非常大的一個(gè)(雖然還不是無(wú)限).

答主還對(duì)原作者的循環(huán)代碼做出了一些修改,并解決了問(wèn)題,而解決的原因就是利用了前面提過(guò)的"不常見的陷阱".

for (int i = OSR_value; i != 0; i++) {
    if (1 % i == 0) {
        uncommon_trap();
    }
}
uncommon_trap();

明顯的一個(gè)問(wèn)題,語(yǔ)義無(wú)變化,循環(huán)依舊是無(wú)限的.只不過(guò)在i自增到偶數(shù)時(shí),編譯器將會(huì)遇到"不常見的陷阱",原本做出的極端優(yōu)化將不得不退化為解釋器執(zhí)行,從而解決了安全點(diǎn)輪詢過(guò)稀少的問(wèn)題.

小結(jié)

許多技術(shù)多帶帶來(lái)看都很好,偏向鎖,JIT,安全點(diǎn).多帶帶看來(lái)都很完美,JIT的時(shí)間開銷也相對(duì)較少,但是結(jié)合在OSR真的是一大暗坑.

且不管偏向鎖為什么從前一定要在全局安全點(diǎn)移除了,作者后續(xù)會(huì)繼續(xù)查資料,總之,從JAVA10開始不用了.關(guān)于偏向鎖和OSR,建議閱讀此博客.

作者看來(lái),安全點(diǎn)的機(jī)制特別像java官方提供的同步器,如前面介紹過(guò)的CyclicBarrier,CountDownLatch,Semaphore,Phaser.一定要等待所有線程到達(dá)某個(gè)點(diǎn),然后再進(jìn)行一些操作,操作完畢后再釋放線程繼續(xù)執(zhí)行.

關(guān)于安全點(diǎn)的三個(gè)術(shù)語(yǔ):

安全點(diǎn)狀態(tài):java線程可以按相應(yīng)的輪詢機(jī)制輪詢是否進(jìn)入此狀態(tài),但一旦進(jìn)入,就只能在安全點(diǎn)操作結(jié)束后才可離開了.

安全點(diǎn)輪詢:java線程詢問(wèn)是否需要進(jìn)入安全點(diǎn)狀態(tài)的機(jī)制.

安全點(diǎn)操作:出于各種原因,但一定要等所有線程到達(dá)安全點(diǎn)才可以執(zhí)行的操作.

最后上一張非常有代表性的圖,出自安全點(diǎn)有關(guān)的一個(gè)博客.

簡(jiǎn)單介紹這個(gè)圖表的含義,它描述了安全點(diǎn)操作的若干開銷.

1.到達(dá)安全點(diǎn)的時(shí)間(Time to Safe Point?簡(jiǎn)寫TTSP):每個(gè)要進(jìn)入安全點(diǎn)的線程都能在命中安全點(diǎn)輪詢的情況下進(jìn)入,但到達(dá)一個(gè)安全點(diǎn)輪詢所需執(zhí)行的指令數(shù)是未知的,從圖上可以看到J1線程命中了一個(gè)安全點(diǎn)輪詢并掛起.J2和J3發(fā)生了對(duì)cpu時(shí)間的競(jìng)態(tài),J3提前獲取了cpu資源并使得J2壓入了運(yùn)行隊(duì)列,但J2此時(shí)并不在安全點(diǎn).J3到達(dá)了安全點(diǎn)并掛起,釋放了cpu資源,J2于是繼續(xù)執(zhí)行并最終進(jìn)行了安全點(diǎn)輪詢.J4和J5因?yàn)閳?zhí)行JNI代碼而早已處于安全點(diǎn),它們?cè)诖颂幉皇苡绊?但J5在安全點(diǎn)期間嘗試半路從JNI代碼回來(lái)而被掛起.所以我們可以看到,不同的線程到達(dá)安全點(diǎn)的時(shí)間變化很大,早到達(dá)的線程會(huì)停頓較長(zhǎng)時(shí)間.

b.安全點(diǎn)操作的開銷:這取決于操作的類型,獲取棧跡(GetStackTrace)將取決于棧的深度,如果采樣了所有的線程或過(guò)多的線程(如在JAVA9-12一文中介紹過(guò)的新工具JVMTI::GetAllStackTraces),則時(shí)間也嚴(yán)重取決于線程數(shù)量.如果時(shí)間充裕,jvm會(huì)借此機(jī)會(huì)執(zhí)行一些其他安全點(diǎn)操作.

c.恢復(fù)被掛起的線程的開銷.

上述問(wèn)題分析的一些幫助:

a.過(guò)長(zhǎng)的TTSP導(dǎo)致的停頓時(shí)間:這包含頁(yè)錯(cuò)誤,cpu過(guò)載,過(guò)長(zhǎng)的計(jì)數(shù)循環(huán)等.

b.線程的關(guān)閉與啟動(dòng)的開銷與線程總數(shù)有關(guān),總數(shù)越高則開銷越大,如果要計(jì)算總開銷,可以粗略使用非0的掛起/恢復(fù)線程開銷和TTSP乘以線程數(shù)量進(jìn)行估算.

c.虛擬機(jī)參數(shù)-XX:+PrintGCApplicationStoppedTime可以列出所有的停頓時(shí)間和TTSP.

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/75509.html

相關(guān)文章

  • 54個(gè)JAVA官方文檔重要術(shù)語(yǔ)

    摘要:近期在閱讀最新幾版的官方文檔過(guò)程中發(fā)現(xiàn)不少術(shù)語(yǔ)不清之處特發(fā)此文總結(jié)以下的術(shù)語(yǔ)大量在官方文檔中直接出現(xiàn)且直接如基本詞語(yǔ)一樣使用不理解它們會(huì)嚴(yán)重影響閱讀自適應(yīng)自旋鎖自適應(yīng)自旋鎖是一個(gè)允許線程在特定點(diǎn)自旋等待特定事件發(fā)生而不是直接進(jìn)行并等待該事件 近期在閱讀JAVA最新幾版的官方文檔過(guò)程中發(fā)現(xiàn)不少術(shù)語(yǔ)不清之處,特發(fā)此文總結(jié).以下的術(shù)語(yǔ)大量在官方文檔中直接出現(xiàn),且直接如基本詞語(yǔ)一樣使用,不理解...

    longmon 評(píng)論0 收藏0
  • JAVA運(yùn)行時(shí)簡(jiǎn)述(HotSpot)

    摘要:拆解虛擬機(jī)的基本步聚如下首先,要等待到自身成為唯一一個(gè)正在運(yùn)行的非守護(hù)線程時(shí),在整個(gè)等待過(guò)程中,虛擬機(jī)仍舊是可工作的。將相應(yīng)的事件發(fā)送給,禁用,并終止信號(hào)線程。 本文簡(jiǎn)單介紹HotSpot虛擬機(jī)運(yùn)行時(shí)子系統(tǒng),內(nèi)容來(lái)自不同的版本,因此可能會(huì)與最新版本之間(當(dāng)前為JDK12)存在一些誤差。 1.命令行參數(shù)處理HotSpot虛擬機(jī)中有大量的可影響性能的命令行屬性,可根據(jù)他們的消費(fèi)者進(jìn)行簡(jiǎn)...

    hosition 評(píng)論0 收藏0
  • Java 虛擬機(jī)總結(jié)給面試你(下)

    摘要:本篇博客主要針對(duì)虛擬機(jī)的晚期編譯優(yōu)化,內(nèi)存模型與線程,線程安全與鎖優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請(qǐng)點(diǎn)擊虛擬總結(jié)上篇,虛擬機(jī)總結(jié)中篇。 本篇博客主要針對(duì)Java虛擬機(jī)的晚期編譯優(yōu)化,Java內(nèi)存模型與線程,線程安全與鎖優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請(qǐng)點(diǎn)擊Java虛擬總結(jié)上篇 ,Java虛擬機(jī)總結(jié)中篇。 一.晚期運(yùn)行期優(yōu)化 即時(shí)編譯器JIT 即時(shí)編譯器JIT的作用就是熱點(diǎn)代碼轉(zhuǎn)換為平臺(tái)相關(guān)的機(jī)器碼...

    amc 評(píng)論0 收藏0
  • bat等大公司??糺ava多線程面試題

    摘要:典型地,和被用在等待另一個(gè)線程產(chǎn)生的結(jié)果的情形測(cè)試發(fā)現(xiàn)結(jié)果還沒(méi)有產(chǎn)生后,讓線程阻塞,另一個(gè)線程產(chǎn)生了結(jié)果后,調(diào)用使其恢復(fù)。使當(dāng)前線程放棄當(dāng)前已經(jīng)分得的時(shí)間,但不使當(dāng)前線程阻塞,即線程仍處于可執(zhí)行狀態(tài),隨時(shí)可能再次分得時(shí)間。 1、說(shuō)說(shuō)進(jìn)程,線程,協(xié)程之間的區(qū)別 簡(jiǎn)而言之,進(jìn)程是程序運(yùn)行和資源分配的基本單位,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元...

    Charlie_Jade 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<