偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Java volatile 關(guān)鍵字到底是什么

開(kāi)發(fā) 前端
由于 C2 編譯器的激進(jìn)優(yōu)化,編譯后的機(jī)器碼不再判斷 running 變量,從而產(chǎn)生了內(nèi)存可見(jiàn)性問(wèn)題。而即使 C2 編譯后的機(jī)器指令依然會(huì)執(zhí)行安全點(diǎn)檢查。想想是不是可以利用安全點(diǎn)檢查機(jī)制,用一些操作來(lái)讓進(jìn)程停止?比如提交執(zhí)行一次 GC、打個(gè)斷點(diǎn)之類(lèi)的。

一、前言

volatile 作為 Java 的基礎(chǔ)關(guān)鍵字,一直是個(gè)熟悉又神秘的存在。我們?cè)谌粘W霾l(fā)編程的過(guò)程中經(jīng)常用到,我們知道在什么場(chǎng)景下需要用到,但卻始終不清楚底層究竟做了什么?;ヂ?lián)網(wǎng)上搜出來(lái)的大多數(shù)博客都在解釋 volatile 關(guān)鍵字是為了解決指令重排序、內(nèi)存可見(jiàn)性問(wèn)題,或是什么內(nèi)存屏障、緩存一致性協(xié)議一類(lèi)“形而上的詞匯”。而究竟什么是指令重排序,為什么要重新排序,什么是可見(jiàn)性問(wèn)題,底層原理是什么,volatile 又是如何解決的卻鮮有提及。引得 Java 開(kāi)發(fā)者們?nèi)珈F里看花,線(xiàn)上線(xiàn)下充滿(mǎn)了疑惑的空氣。

本文將淺淺探究一下這一切的底層原理,一起來(lái)學(xué)習(xí)“沒(méi)有用”的知識(shí),各位看官看懂了可以出去和面試官對(duì)線(xiàn)。

二、指令重排序

在了解指令重排序問(wèn)題之前,我們先來(lái)看一個(gè)由指令重排序造成并發(fā)問(wèn)題的例子:

static int x = 0, y = 0;
static int a = 0, b = 0;


public static void main(String[] args) throws InterruptedException {
    for (int i = 0; true; i++) {
        x = 0; y = 0; a = 0; b = 0;
        Thread one = new Thread(new Runnable() {
            public void run() {
                a = 1;
                x = b;
            }
        });
        Thread other = new Thread(new Runnable() {
            public void run() {
                b = 1;
                y = a;
            }
        });
        one.start();other.start();
        one.join();other.join();
        if (x == 0 && y == 0) {
            System.err.println("bingo!i: " + i);
            break;
        }
    }
}

happens-before 八條原則

  • 程序次序規(guī)則:在一個(gè)線(xiàn)程內(nèi),按照控制流順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作。
  • 管程鎖定規(guī)則:一個(gè) unlock 操作先行發(fā)生于后面對(duì)同一個(gè)鎖的 lock 操作。
  • volatile 變量規(guī)則:對(duì)一個(gè) volatile 變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。
  • 線(xiàn)程啟動(dòng)規(guī)則:Thread 對(duì)象 start()方法先行發(fā)生于此線(xiàn)程的每一個(gè)動(dòng)作。
  • 線(xiàn)程終止規(guī)則:線(xiàn)程A等待線(xiàn)程B完成,在線(xiàn)程A中調(diào)用線(xiàn)程B的join()方法實(shí)現(xiàn)),當(dāng)線(xiàn)程B完成后(線(xiàn)程A調(diào)用線(xiàn)程B的join()方法返回),則線(xiàn)程A能夠訪(fǎng)問(wèn)到線(xiàn)程B對(duì)共享變量的操作。
  • 線(xiàn)程中斷規(guī)則:對(duì)線(xiàn)程 interrupt() 方法的調(diào)用先行,發(fā)生于被中斷線(xiàn)程的代碼,檢測(cè)到中斷事件的發(fā)生,可以通過(guò) Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生。
  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)結(jié)束)先行發(fā)生于它的 finalize()方法的開(kāi)始。
  • 傳遞性:如果操作 A 先行發(fā)生于操作 B,操作 B 先行發(fā)生于操作 C,那就可以得出操作 A 先行發(fā)生于操作 C 的結(jié)論。

hanpens-beofre 是 JVM 對(duì)開(kāi)發(fā)者的保證,即不管 JVM 如何優(yōu)化(JIT 編譯),都會(huì)保證上述原則一定成立。而對(duì)于開(kāi)發(fā)者來(lái)說(shuō),只要了解上述原則,無(wú)需硬件交互的復(fù)雜性,也能夠?qū)懗隹深A(yù)測(cè)的代碼,從而保證線(xiàn)程安全。

從 hanpens-beofre 中 程序次序規(guī)則 和 線(xiàn)程終止規(guī)則 可得,上述代碼最終運(yùn)行結(jié)果的可能性會(huì)有以下幾種:

圖片圖片

可以明顯看出,理論上不會(huì)存在 x =0 && y = 0 的運(yùn)行結(jié)果,然而實(shí)際上程序在執(zhí)行了一段時(shí)間后,最終的確產(chǎn)生了 x = 0 && y = 0 的結(jié)果!

圖片圖片

這就引出了 volatile 解決的第一個(gè)問(wèn)題:避免指令重排序。指令重排序在編譯器和 CPU 層面(亂序執(zhí)行)都會(huì)發(fā)生。

CPU 的亂序執(zhí)行

我們知道,CPU 運(yùn)算的本質(zhì)就是不斷獲取下一條指令然后執(zhí)行,編譯器給它什么指令它就執(zhí)行什么,何來(lái)的亂序執(zhí)行呢?

這還要從計(jì)算機(jī)的誕生之初講起。

內(nèi)存拖后腿

計(jì)算機(jī)誕生之初,CPU 和內(nèi)存之間的速度差異并不明顯,一切相安無(wú)事。隨著科學(xué)的進(jìn)步,CPU 的運(yùn)算速度越來(lái)快,根據(jù)摩爾定律計(jì)算,相當(dāng)于 CPU 的性能每年增長(zhǎng) 60%,相比之下,內(nèi)存性能的增長(zhǎng)卻相對(duì)緩慢,每年約為 7%。到今天,CPU 運(yùn)算和內(nèi)存訪(fǎng)問(wèn)的速度產(chǎn)生了巨大鴻溝,已經(jīng)達(dá)到了 120 倍之多。這時(shí)如果 CPU 還以傳統(tǒng)計(jì)算機(jī)架構(gòu),數(shù)據(jù)從內(nèi)存中讀取的話(huà),將會(huì)嚴(yán)重拖慢 CPU 的運(yùn)行速度。

圖片圖片

※ 局部性原理

在程序運(yùn)行過(guò)程中,芯片工程師總結(jié)了兩條存在局部性原理:時(shí)間局部性、空間局部性。

  • 時(shí)間局部性:由于在代碼中循環(huán)操作的普遍存在,因此當(dāng)某部分?jǐn)?shù)據(jù)被訪(fǎng)問(wèn)時(shí),不久后該數(shù)據(jù)很可能會(huì)再次被訪(fǎng)問(wèn),基于此原理誕生了 CPU 的高速緩存。
  • 空間局部性:由于代碼是順序執(zhí)行的,因此當(dāng)某一份數(shù)據(jù)被訪(fǎng)問(wèn)時(shí),后續(xù)的數(shù)據(jù)也將很快被訪(fǎng)問(wèn),基于此原理誕生了緩存行。
※ CPU 內(nèi)的高速緩存

為了彌補(bǔ) CPU 運(yùn)行速度與內(nèi)存訪(fǎng)問(wèn)速度之間的巨大差異,提升 CPU 執(zhí)行效率,CPU 在內(nèi)部封裝了高速緩存。

高速緩存是一種靜態(tài)隨機(jī)訪(fǎng)問(wèn)存儲(chǔ)器(SRAM),相對(duì)于使用電容存儲(chǔ)的內(nèi)存(DRAM)來(lái)說(shuō),速度快得多,訪(fǎng)問(wèn)速度在納秒級(jí)別,終于能勉強(qiáng)不再拖 CPU 后腿了。

圖片圖片

CPU 緩存共分為三級(jí):

  • 按訪(fǎng)問(wèn)速度從大到小排序?yàn)椋篖1 > L2 > L3
  • 按容量從大到小排序?yàn)椋篖3 > L2 > L1

其中 L3 緩存 CPU 共享,L1、L2 緩存為各 CPU 獨(dú)占。CPU 在訪(fǎng)問(wèn)內(nèi)存數(shù)據(jù)時(shí),會(huì)優(yōu)先從高速緩存中訪(fǎng)問(wèn),訪(fǎng)問(wèn)順序依次為 L1、L2、L3,若高速緩存中都不存在,則再訪(fǎng)問(wèn)內(nèi)存。

緩存的引入,降低了 CPU 直接訪(fǎng)問(wèn)內(nèi)存的頻率,大大提升了 CPU 的執(zhí)行效率。

※ 緩存行

根據(jù)空間局部性原理,當(dāng) CPU 訪(fǎng)問(wèn)了一塊數(shù)據(jù)時(shí),相鄰的數(shù)據(jù)很可能也即將被訪(fǎng)問(wèn)。那么是否可以通過(guò)預(yù)加載相鄰的數(shù)據(jù)到高速緩存中,提升高速緩存的命中率呢?

當(dāng)然可以,我們把預(yù)加載的這部分內(nèi)存數(shù)據(jù)叫做緩存行。

圖片圖片

由圖所示,內(nèi)存被劃分為若干緩存行的塊,緩存行的大小是固定的,通常為 64 字節(jié),高速緩存數(shù)據(jù)塊最小粒度就是緩存行(換句話(huà)說(shuō),高速緩存內(nèi)的數(shù)據(jù)就是由一個(gè)個(gè)緩存行構(gòu)成的)。當(dāng) CPU 需要訪(fǎng)問(wèn)位于內(nèi)存的數(shù)據(jù) X 時(shí),會(huì)將整個(gè)緩存行同時(shí)加載到高速緩存中,以提升程序后續(xù)執(zhí)行時(shí)的緩存命中率。

CPU 內(nèi)的“分布式”問(wèn)題

高速緩存是把雙刃劍,在大幅提升 CPU 運(yùn)行效率的同時(shí),也引來(lái)了一個(gè) “分布式” 問(wèn)題。

圖片圖片

記得我們前面說(shuō)過(guò),CPU 的 L1、L2 緩存是各核心獨(dú)占的,在兩個(gè) CPU 的 L2 緩存同時(shí)加載了同一個(gè)緩存行的情況下,當(dāng) CPU 0 數(shù)據(jù) X 做了寫(xiě)操作(X = 1),其他 CPU 對(duì)這一修改是不可見(jiàn)的,這時(shí) CPU 1 如果依然訪(fǎng)問(wèn)自己高速緩存中的數(shù)據(jù),勢(shì)必會(huì)產(chǎn)生數(shù)據(jù)不一致。

為了解決這個(gè)問(wèn)題,緩存一致性協(xié)議便誕生了。

※  MESI 協(xié)議

緩存一致性協(xié)議有多種,最出名的就是 MESI 協(xié)議。

MESI 是 Modified   Exclusive Shared   Invalid 四個(gè)單詞的縮寫(xiě),分別表示緩存行的四種狀態(tài):

  • Modified:表示緩存行中數(shù)據(jù)已經(jīng)被 CPU 修改了。
  • Exclusive:緩存行處于獨(dú)占但尚未修改的狀態(tài),該狀態(tài)表示其他 CPU 不可以預(yù)讀取這個(gè)緩存行到自己的高速緩存中。
  • Shared:表示緩存行數(shù)據(jù)已經(jīng)被多個(gè) CPU 預(yù)加載到緩存中,且各 CPU 均未對(duì)該緩存行做修改。
  • Invalid:表示有其他 CPU 修改了該緩存行,緩存行數(shù)據(jù)已經(jīng)失效。

圖片

  • CPU 0 需要修改緩存行中 X 的數(shù)據(jù)時(shí),將當(dāng)前緩存行標(biāo)記為 Modified ,并向總線(xiàn)發(fā)送一條消息,表明緩存行 CPU 0 已經(jīng)修改。
  • CPU 1 接收到該緩存行已失效的消息后,會(huì)將本地緩存行標(biāo)記為 Invalid ,并 ACK 給 CPU 0。
  • CPU 0 收到其他 CPU 已經(jīng)將本地緩存行標(biāo)記失效消息后,修改 X 的值。
  • 此時(shí) CPU 0 高速緩存中緩存了 X 的最新值,其他 CPU 如果需要訪(fǎng)問(wèn) X ,將會(huì)通過(guò)總線(xiàn)從CPU 0中獲取。

MESI 協(xié)議非常復(fù)雜,比如各 CPU 之間是如何通信的、多個(gè) CPU 同時(shí)發(fā)送失效事件怎么辦等等。

篇幅所限僅做本文用的著的部分介紹。有興趣了解具體實(shí)現(xiàn)可以點(diǎn)擊 https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESI.htm 查看動(dòng)畫(huà)演示。

緩存一致性協(xié)議有效解決了各 CPU 間數(shù)據(jù)一致性問(wèn)題。那么,代價(jià)是什么呢?

禁止 CPU 摸魚(yú)

上圖可以看出 CPU 0 在執(zhí)行修改 X 的值之前,需要與其他 CPU 進(jìn)行通訊,收到其他 CPU 將本地消息修改完成后,才可修改本地緩存行的數(shù)據(jù)。在這期間 CPU 0 一直無(wú)事可做。而不管是前面提到過(guò)的編譯器指令重排序還是超線(xiàn)程、指令流水線(xiàn)等技術(shù),目的都是在提升 CPU 的運(yùn)行效率,減少 CPU 空跑時(shí)間。如果由于緩存一致性協(xié)議造成 CPU 空閑的話(huà),這對(duì)于我們來(lái)說(shuō)顯然是不可接受的!

圖片圖片

為了讓 CPU 滿(mǎn)負(fù)荷運(yùn)轉(zhuǎn),芯片工程師在 CPU 與 L1 緩存之間又加了一層——store buffer。

引入了 store buffer 后,CPU 寫(xiě)緩存行不再需要等待其他 CPU 回復(fù)消息,而是直接讀寫(xiě) store buffer,等到特定時(shí)刻,再將 store buffer 中的數(shù)據(jù) flush 到高速緩存中

圖片圖片

  1. CPU 0 需要修改緩存行中 X 的數(shù)據(jù)時(shí),將當(dāng)前緩存行標(biāo)記為 Modified ,并向總線(xiàn)發(fā)送一條消息。
  2. CPU 0 不再等待 CPU 1 回復(fù),而是直接將修改后的數(shù)據(jù)寫(xiě)入 store buffer 中。
  3. CPU 0 收到 CPU 1 標(biāo)記緩存已經(jīng)失效的回復(fù)消息后,將 store buffer 中的值 flush 到高速緩存中。

問(wèn)題會(huì)這么完美的解決嗎?

亂序執(zhí)行

現(xiàn)在我們將 store buffer 納入考量,再來(lái)回頭看本節(jié)開(kāi)始的這段代碼:

圖片圖片

最終由于 store buffer 中數(shù)據(jù)的 flush 時(shí)間晚于 CPU 直接寫(xiě)高速緩存中數(shù)據(jù)的時(shí)間,客觀(guān)上產(chǎn)生了 CPU 執(zhí)行的指令順序與實(shí)際代碼中不一致的現(xiàn)象(X = B 早于 A = 1 執(zhí)行,Y = A 早于 B = 1 執(zhí)行),即亂序執(zhí)行。最終得到了(A = 1,B = 1,X = 0,Y = 0)的結(jié)果。

既然發(fā)現(xiàn)了問(wèn)題,那么要如何解決呢?這就要提到另一項(xiàng)技術(shù):內(nèi)存屏障。

隨著 store buffer 技術(shù)的引入引起的問(wèn)題還有很多,于是牽扯出一系列其他技術(shù),如 Store Forwarding、Invalidate Queues 等技術(shù)。由于與本文涉及到的內(nèi)容無(wú)關(guān),這里就不做贅述了,各位有興趣可以自行了解相關(guān)內(nèi)容。

內(nèi)存屏障

內(nèi)存屏障聽(tīng)起來(lái)比較高大上,實(shí)際總結(jié)起來(lái)非常簡(jiǎn)單,就一句話(huà):

去告訴 CPU,我在此處定義了一個(gè)內(nèi)存屏障,自這里開(kāi)始,后續(xù)所有針對(duì)高速緩存的寫(xiě)入,都必須先把 store buffer 中的數(shù)據(jù)全部 flush 回高速緩存中!

圖片圖片

圖片圖片

在 Java 中 volatile 關(guān)鍵字避免 CPU 亂序執(zhí)行的原理其實(shí)就是在訪(fǎng)問(wèn) volatile 變量時(shí)添加了內(nèi)存屏障。限制后續(xù)數(shù)據(jù)的寫(xiě)入操作一定把當(dāng)前 store buffer 中的數(shù)據(jù) flush 到高速緩存中,再通過(guò)緩存一致性協(xié)議保證數(shù)據(jù)一致性。

回到本節(jié)一開(kāi)始的代碼,你一定想到了要如何讓這段程序永遠(yuǎn)執(zhí)行下去的辦法了?

static int x = 0, y = 0;
static volatile int a = 0, b = 0;

沒(méi)錯(cuò),我們只需要限制針對(duì)數(shù)據(jù) X、Y 的寫(xiě)操作之前,位于 store buffer 中的數(shù)據(jù) A、B 全部 flush 到高速緩存即可。所以最終的解決方案就是給變量 A、B 添加 volatile 關(guān)鍵字即可!

想想為什么在變量 X、Y 上加 volatile 不可以?說(shuō)加 4 個(gè) volatile 的那位同學(xué),課后把內(nèi)存屏障這一章節(jié)抄寫(xiě) 3 遍!

前面說(shuō)過(guò),指令重排序問(wèn)題在 CPU 和編譯器層面都存在,CPU 層面說(shuō)完了,那編譯器層面呢?

編譯器重排序

由于 JIT 編譯后的指令不好扒,我們以 C 語(yǔ)言為例,先來(lái)看看下面的 C 語(yǔ)言例子:

int func(int a, int b, int c, int d) {
  return a + b + c + d;
}

如果編譯器不做優(yōu)化,如果完全順從我們代碼語(yǔ)義,以上函數(shù)生成的匯編偽代碼如下:

圖片圖片

圖片圖片

而現(xiàn)代 CPU 會(huì)有多個(gè)執(zhí)行單元,例如讀寫(xiě)單元、運(yùn)算單元,這些執(zhí)行單元之間可以獨(dú)立工作。在執(zhí)行上面的指令時(shí),只能順序執(zhí)行,不能并行執(zhí)行。要想發(fā)揮兩個(gè)執(zhí)行單元的效率,只需調(diào)整一下順序即可:

圖片圖片

圖片圖片

可以看出,雖然指令的數(shù)量一樣,但在對(duì)指令做簡(jiǎn)單的重新排序后,優(yōu)化一下指令的提交順序,就可以更快的完成任務(wù)。

在 JVM 中,JIT 編譯同樣也會(huì)遵循這一原則,在不改變?cè)创a語(yǔ)義的情況下,改變 CPU 指令的執(zhí)行順序,就可以更快的完成運(yùn)算任務(wù),提升執(zhí)行效率。這在單線(xiàn)程情況下運(yùn)行良好,但多線(xiàn)程運(yùn)行時(shí),就可能會(huì)存在一些意料之外的問(wèn)題。

三、可見(jiàn)性問(wèn)題

再來(lái)看另一個(gè)示例:

private static boolean running = true;


public static void main(String[] args) {
    int i = 0;
    Thread thread = new Thread(() -> {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
        }
        running = false;
    });
    thread.setDaemon(true);
    thread.start();
    while (running) {
        i++;
    }
    System.err.println(i);
}

上面的程序,并不會(huì)按照我們預(yù)期的那樣正常輸出程序結(jié)束后退出,而是會(huì)永遠(yuǎn)的執(zhí)行下去(不要嘗試用前文中的 store buffer 來(lái)強(qiáng)行解釋這個(gè)問(wèn)題,store buffer 本質(zhì)上也是個(gè) buffer,在某一時(shí)刻數(shù)據(jù)依然會(huì) flush 到高速緩存中,從而讓其他線(xiàn)程感知到最新的值)。

這就引出 volatile 關(guān)鍵字解決的另一個(gè)問(wèn)題:內(nèi)存可見(jiàn)性問(wèn)題。

分層編譯

我們知道,Java 是跨平臺(tái)的。一個(gè) Java 源碼文件的執(zhí)行需要兩個(gè)過(guò)程:

  1. AOT 編譯:源代碼文件編譯為 class 文件。
  2. JIT 編譯:JVM 加載 class 文件,將 class 文件中字節(jié)碼轉(zhuǎn)換為計(jì)算器可執(zhí)行的機(jī)器指令。

字節(jié)碼的執(zhí)行也有兩種方式:

  1. 解釋執(zhí)行:優(yōu)點(diǎn)是啟動(dòng)速度快,缺點(diǎn)是需要逐條將字節(jié)碼解釋為機(jī)器指令,開(kāi)銷(xiāo)大,性能低。
  2. 編譯執(zhí)行(JIT 編譯):優(yōu)點(diǎn)是執(zhí)行效率高,與本地編譯性能基本沒(méi)差別,缺點(diǎn)是編譯本身需要消耗 CPU 資源,以及編譯后的指令數(shù)據(jù)需要存儲(chǔ),需要消耗內(nèi)存空間。

而 JIT 編譯器又分為兩種,Client Compiler、Server Compiler。之所以叫 client,server 是因?yàn)樵谝婚_(kāi)始設(shè)計(jì)這倆編譯器的時(shí)候,前者是設(shè)計(jì)給客戶(hù)端程序用的,就比如像 idea 這種運(yùn)行在個(gè)人電腦上的 Java 程序,不會(huì)長(zhǎng)時(shí)間使用,反而更注重應(yīng)用的啟動(dòng)速度以及快速達(dá)到相對(duì)較優(yōu)性能。而后者則是設(shè)計(jì)給服務(wù)端程序用的。

HotSpot 虛擬機(jī)帶有一個(gè) Client Compiler —— C1 編譯器。這種編譯器啟動(dòng)速度快,但是性能相對(duì)Server Compiler 來(lái)說(shuō)會(huì)差一些。Server Compiler 則更為激進(jìn),優(yōu)化后的性能要比 Client Compiler 高30%以上。HotSpot 虛擬機(jī)則帶有兩個(gè) Server Compiler,默認(rèn)的 C2 以及 Graal。

在 Java 7 之前,需要開(kāi)發(fā)者根據(jù)服務(wù)的性質(zhì)手動(dòng)選擇編譯器。自 Java 7 開(kāi)始,則引入了分層編譯(Tiered Compilation)。

0:由解釋器解釋執(zhí)行

1:C1 NO profiling:執(zhí)行不帶 profiling 的 C1 代碼。

2:C1 LIMITED profiling:執(zhí)行僅帶方法調(diào)用次數(shù)以及循環(huán)回邊執(zhí)行次數(shù) profiling 的 C1 代碼。

3:C1 FULL profiling:執(zhí)行帶所有 profiling 的 C1 代碼。

4:C2:執(zhí)行 C2 代碼。

誰(shuí)動(dòng)了我的代碼

運(yùn)行時(shí)編譯有那么多層級(jí),到底是哪層影響了代碼的?

想要探究個(gè)問(wèn)題很簡(jiǎn)單,只需要在JVM啟動(dòng)參數(shù)里增加 -XX:TieredStopAtLevel=XX 參數(shù)即可。TieredStopAtLevel 是控制 JVM 最大分層編譯級(jí)別的參數(shù),當(dāng)我們配置的值 < 4 時(shí),前文的代碼均可以正常終止,那么結(jié)論很明顯了:C2 編譯器全責(zé)!

C2 你在干什么?!

為了探究 C2 編譯對(duì)我們代碼做了什么,我們決定使用一款工具:JITWatch。JITWatch 專(zhuān)門(mén)用來(lái)探究 JIT 編譯后的代碼對(duì)應(yīng)匯編指令。

關(guān)于 JITWatch 使用的流程這里就不做贅述了,網(wǎng)上有大量說(shuō)明。本節(jié)的案例也很好復(fù)現(xiàn),大家可以動(dòng)手試一試。

我們將前文的源碼文件編譯后,提交以下命令執(zhí)行:

# Ubuntu 22.04 下
java -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:LogFile=~/Desktop/jit.log -XX:+PrintAssembly -XX:+TraceClassLoading Main

接著啟動(dòng) JITWatch,加載 jit.log 文件:

圖片圖片

雙擊右側(cè) main 方法,查看 C2 編譯后的結(jié)果:

記得選 OSR(棧上替換) 那個(gè) C2,因?yàn)榇a屬于死循環(huán),代碼塊不會(huì)退出,無(wú)法完整替換 C2 編譯后的機(jī)器指令,只能通過(guò)棧上替換技術(shù)來(lái)進(jìn)行。

圖片圖片

綠色框中為我們的核心代碼,我在此處將它放大并增加了注釋?zhuān)缦拢?/p>

圖片圖片

什么?無(wú)條件跳轉(zhuǎn)?!

可以看出,由于 C2 編譯器的激進(jìn)優(yōu)化,編譯后的機(jī)器碼不再判斷 running 變量,從而產(chǎn)生了內(nèi)存可見(jiàn)性問(wèn)題。而即使 C2 編譯后的機(jī)器指令依然會(huì)執(zhí)行安全點(diǎn)檢查。想想是不是可以利用安全點(diǎn)檢查機(jī)制,用一些操作來(lái)讓進(jìn)程停止?比如提交執(zhí)行一次 GC、打個(gè)斷點(diǎn)之類(lèi)的。

四、總結(jié)

說(shuō)了那么多,能不能說(shuō)點(diǎn)有用的?

有的有的,我們?cè)诙嗑€(xiàn)程開(kāi)發(fā)中,只要變量被多個(gè)線(xiàn)程共享,且是可變的,加上 volatile 準(zhǔn)沒(méi)錯(cuò):)

責(zé)任編輯:武曉燕 來(lái)源: 得物技術(shù)
相關(guān)推薦

2023-10-11 08:29:54

volatileJava原子性

2025-06-13 08:00:00

Java并發(fā)編程volatile

2022-06-29 08:05:25

Volatile關(guān)鍵字類(lèi)型

2011-06-14 13:26:27

volatile

2019-09-04 14:14:52

Java編程數(shù)據(jù)

2011-06-21 09:50:51

volatile

2009-06-29 18:14:23

Java多線(xiàn)程volatile關(guān)鍵字

2022-08-17 07:53:10

Volatile關(guān)鍵字原子性

2018-01-19 10:43:06

Java面試官volatile關(guān)鍵字

2020-07-17 20:15:03

架構(gòu)JMMvolatile

2011-04-27 09:30:48

企業(yè)架構(gòu)

2020-09-27 06:53:57

MavenCDNwrapper

2020-10-14 06:22:14

UWB技術(shù)感知

2010-11-01 01:25:36

Windows NT

2020-09-22 08:22:28

快充

2023-06-26 08:02:34

JSR重排序volatile

2020-11-11 08:45:48

Java

2021-09-03 09:12:09

Linux中斷軟件

2013-06-09 09:47:31

.NetPDBPDB文件

2020-08-04 14:20:20

數(shù)據(jù)湖Hadoop數(shù)據(jù)倉(cāng)庫(kù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)