成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

關于偏向鎖,安全點,JIT的一些暗坑.

JeOam / 3545人閱讀

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

前言

本文是一篇簡短的雜糅.

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

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

偏向鎖的疑問

首先是偏向鎖的移除:

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

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

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

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

偏向鎖的存在意義:

我們知道,偏向鎖的目標是減少昂貴的原子指令cas等的使用以及互斥量的開銷;輕量鎖的目標是減少互斥量的開銷.偏向鎖在不考慮重偏向這種情況下,似乎只有第一次加鎖才起作用,那么這個問題似乎有些多余,我們會對沒有競爭的代碼加上同步嗎?

答案是會的.大體有以下場景:

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

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

3.默認的情況下在jvm啟動的前幾秒偏向鎖是不可用的,可以使用-XX:BiasedLockingStartupDelay=0進行配置.

以上情況可參考問題:偏向鎖的設計.

偏向鎖的設計疑問,為什么只在對象頭中保存線程id?

可以參考:偏向鎖與輕量鎖的設計不同.

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

Unlocked:

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

重偏向問題:

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

偏向鎖可以重偏向的一點細節(jié):

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

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

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

4.使用-XX:+PrintSafepointStatistics可打印安全點事件,與偏向鎖有關的可重點可關注EnableBiasedLocking,RevokeBias和BulkRevokeBias.選項-XX:+TraceBiasedLocking可以幫助生成一個詳細描述jvm做出的偏向鎖決策的日志.

參考:單個偏向鎖的重偏向.

安全點和JIT

關于安全點和JIT本身此處不再綴述,此處簡單回憶若干前提.

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

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

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

老外寫的一個代碼例子,非常像我們項目碰到的停頓現(xiàn)象,我們的代碼也類似,確實有大量的同步操作(必然涉及偏向鎖和移除,同時也涉及到JIT的棧上替換和計數(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

顯然中間那一個13秒多的等待時間就像我們項目中的40秒暫停一樣突兀,這也一度讓作者認為找對了答案.

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

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

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

正常情況下,每一個JAVA線程會輪詢一個安全點標記(safepoint?flag)來詢問是否要進入安全點,當觀察到去安全點標記(go?to?safepoint?flag)時,會趕去最近的安全點.但是,大量地進行安全點標記的輪詢是耗費性能的,因此C1C2編譯器做了相應的優(yōu)化,消除了過于頻繁的安全點輪詢,因此安全點輪詢主要有以下幾種情況:

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

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

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

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

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

解決方案:

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

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

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

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

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

小結

許多技術多帶帶來看都很好,偏向鎖,JIT,安全點.多帶帶看來都很完美,JIT的時間開銷也相對較少,但是結合在OSR真的是一大暗坑.

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

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

關于安全點的三個術語:

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

安全點輪詢:java線程詢問是否需要進入安全點狀態(tài)的機制.

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

最后上一張非常有代表性的圖,出自安全點有關的一個博客.

簡單介紹這個圖表的含義,它描述了安全點操作的若干開銷.

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

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

c.恢復被掛起的線程的開銷.

上述問題分析的一些幫助:

a.過長的TTSP導致的停頓時間:這包含頁錯誤,cpu過載,過長的計數(shù)循環(huán)等.

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

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

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75509.html

相關文章

  • 54個JAVA官方文檔重要術語

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

    longmon 評論0 收藏0
  • JAVA運行時簡述(HotSpot)

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

    hosition 評論0 收藏0
  • Java 虛擬機總結給面試你(下)

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

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

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

    Charlie_Jade 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<