<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【107期】談?wù)劽嬖嚤貑柕腏ava內(nèi)存區(qū)域(運(yùn)行時(shí)數(shù)據(jù)區(qū)域)和內(nèi)存模型(JMM)

          共 7549字,需瀏覽 16分鐘

           ·

          2020-12-31 00:37

          程序員的成長(zhǎng)之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 12?分鐘。

          來自:www.cnblogs.com/czwbig/p/11127124.html

          Java 內(nèi)存區(qū)域和內(nèi)存模型是不一樣的東西,內(nèi)存區(qū)域是指 Jvm 運(yùn)行時(shí)將數(shù)據(jù)分區(qū)域存儲(chǔ),強(qiáng)調(diào)對(duì)內(nèi)存空間的劃分。
          而內(nèi)存模型(Java Memory Model,簡(jiǎn)稱 JMM )是定義了線程和主內(nèi)存之間的抽象關(guān)系,即 JMM 定義了 JVM 在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式,如果我們要想深入了解Java并發(fā)編程,就要先理解好Java內(nèi)存模型。

          Java運(yùn)行時(shí)數(shù)據(jù)區(qū)域

          眾所周知,Java 虛擬機(jī)有自動(dòng)內(nèi)存管理機(jī)制,如果出現(xiàn)內(nèi)存泄漏和溢出方面的問題,排查錯(cuò)誤就必須要了解虛擬機(jī)是怎樣使用內(nèi)存的。
          下圖是 JDK8 之后的 JVM 內(nèi)存布局。
          JDK8 之前的內(nèi)存區(qū)域圖如下:
          在 HotSpot JVM 中,永久代中用于存放類和方法的元數(shù)據(jù)以及常量池,比如Class和Method。每當(dāng)一個(gè)類初次被加載的時(shí)候,它的元數(shù)據(jù)都會(huì)放到永久代中。
          永久代是有大小限制的,因此如果加載的類太多,很有可能導(dǎo)致永久代內(nèi)存溢出,即萬惡的 java.lang.OutOfMemoryError: PermGen ,為此我們不得不對(duì)虛擬機(jī)做調(diào)優(yōu)。
          那么,Java 8 中 PermGen 為什么被移出 HotSpot JVM 了?我總結(jié)了兩個(gè)主要原因:
          • 由于 PermGen 內(nèi)存經(jīng)常會(huì)溢出,引發(fā)惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開發(fā)者希望這一塊內(nèi)存可以更靈活地被管理,不要再經(jīng)常出現(xiàn)這樣的 OOM
            移除 PermGen 可以促進(jìn) HotSpot JVM 與 JRockit VM 的融合,因?yàn)?JRockit 沒有永久代。

          • 根據(jù)上面的各種原因,PermGen 最終被移除,方法區(qū)移至 Metaspace,字符串常量移至 Java Heap。

          引用自https://www.sczyh30.com/posts/Java/jvm-metaspace/

          程序計(jì)數(shù)器

          程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
          由于 Java 虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器內(nèi)核都只會(huì)執(zhí)行一條線程中的指令。
          因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存。
          如果線程正在執(zhí)行的是一個(gè) Java 方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是 Native 方法,這個(gè)計(jì)數(shù)器值則為空(Undefined)。此內(nèi)存區(qū)域是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。

          Java虛擬機(jī)棧

          與程序計(jì)數(shù)器一樣,Java 虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。
          虛擬機(jī)棧描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame,是方法運(yùn)行時(shí)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu))用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。
          在活動(dòng)線程中,只有位千棧頂?shù)膸攀怯行У模Q為當(dāng)前棧幀。正在執(zhí)行的方法稱為當(dāng)前方法,棧幀是方法運(yùn)行的基本結(jié)構(gòu)。在執(zhí)行引擎運(yùn)行時(shí),所有指令都只能針對(duì)當(dāng)前棧幀進(jìn)行操作。

          1. 局部變量表

          局部變量表是存放方法參數(shù)和局部變量的區(qū)域。局部變量沒有準(zhǔn)備階段, 必須顯式初始化。如果是非靜態(tài)方法,則在 index[0] 位置上存儲(chǔ)的是方法所屬對(duì)象的實(shí)例引用,一個(gè)引用變量占 4 個(gè)字節(jié),隨后存儲(chǔ)的是參數(shù)和局部變量。字節(jié)碼指令中的 STORE 指令就是將操作棧中計(jì)算完成的局部變呈寫回局部變量表的存儲(chǔ)空間內(nèi)。
          虛擬機(jī)棧規(guī)定了兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError 異常;如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的 Java 虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展),如果擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出 OutOfMemoryError 異常。

          2. 操作棧

          操作棧是個(gè)初始狀態(tài)為空的桶式結(jié)構(gòu)棧。在方法執(zhí)行過程中, 會(huì)有各種指令往
          棧中寫入和提取信息。JVM 的執(zhí)行引擎是基于棧的執(zhí)行引擎, 其中的棧指的就是操
          作棧。字節(jié)碼指令集的定義都是基于棧類型的,棧的深度在方法元信息的 stack 屬性中。
          i++ 和 ++i 的區(qū)別:
          • i++:從局部變量表取出 i 并壓入操作棧,然后對(duì)局部變量表中的 i 自增 1,將操作棧棧頂值取出使用,最后,使用棧頂值更新局部變量表,如此線程從操作棧讀到的是自增之前的值。

          • ++i:先對(duì)局部變量表的 i 自增 1,然后取出并壓入操作棧,再將操作棧棧頂值取出使用,最后,使用棧頂值更新局部變量表,線程從操作棧讀到的是自增之后的值。

          之前之所以說 i++ 不是原子操作,即使使用 volatile 修飾也不是線程安全,就是因?yàn)椋赡?i 被從局部變量表(內(nèi)存)取出,壓入操作棧(寄存器),操作棧中自增,使用棧頂值更新局部變量表(寄存器更新寫入內(nèi)存),其中分為 3 步,volatile 保證可見性,保證每次從局部變量表讀取的都是最新的值,但可能這 3 步可能被另一個(gè)線程的 3 步打斷,產(chǎn)生數(shù)據(jù)互相覆蓋問題,從而導(dǎo)致 i 的值比預(yù)期的小。

          3. 動(dòng)態(tài)鏈接

          每個(gè)棧幀中包含一個(gè)在常量池中對(duì)當(dāng)前方法的引用, 目的是支持方法調(diào)用過程的動(dòng)態(tài)連接。

          4.方法返回地址

          方法執(zhí)行時(shí)有兩種退出情況:
          • 正常退出,即正常執(zhí)行到任何方法的返回字節(jié)碼指令,如 RETURN、IRETURN、ARETURN 等;

          • 異常退出。

          無論何種退出情況,都將返回至方法當(dāng)前被調(diào)用的位置。方法退出的過程相當(dāng)于彈出當(dāng)前棧幀,退出可能有三種方式:
          • 返回值壓入上層調(diào)用棧幀。

          • 異常信息拋給能夠處理的棧幀。

          • PC計(jì)數(shù)器指向方法調(diào)用后的下一條指令。

          本地方法棧

          本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。Sun HotSpot 虛擬機(jī)直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出 StackOverflowError 和 OutOfMemoryError 異常。
          線程開始調(diào)用本地方法時(shí),會(huì)進(jìn)入 個(gè)不再受 JVM 約束的世界。本地方法可以通過 JNI(Java Native Interface)來訪問虛擬機(jī)運(yùn)行時(shí)的數(shù)據(jù)區(qū),甚至可以調(diào)用寄存器,具有和 JVM 相同的能力和權(quán)限。當(dāng)大量本地方法出現(xiàn)時(shí),勢(shì)必會(huì)削弱 JVM 對(duì)系統(tǒng)的控制力,因?yàn)樗某鲥e(cuò)信息都比較黑盒。對(duì)內(nèi)存不足的情況,本地方法棧還是會(huì)拋出 nativeheapOutOfMemory。
          JNI 類本地方法最著名的應(yīng)該是 System.currentTimeMillis() ,JNI使 Java 深度使用操作系統(tǒng)的特性功能,復(fù)用非 Java 代碼。但是在項(xiàng)目過程中, 如果大量使用其他語言來實(shí)現(xiàn) JNI , 就會(huì)喪失跨平臺(tái)特性。

          Java堆

          對(duì)于大多數(shù)應(yīng)用來說,Java 堆(Java Heap)是 Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。
          堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱做“GC堆”(Garbage Collected Heap)。從內(nèi)存回收的角度來看,由于現(xiàn)在收集器基本都采用分代收集算法,所以 Java 堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有 Eden 空間、From Survivor 空間、To Survivor 空間等。從內(nèi)存分配的角度來看,線程共享的 Java 堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)。
          Java 堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過 -Xmx 和 -Xms 控制)。如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時(shí),將會(huì)拋出 OutOfMemoryError 異常。

          方法區(qū)

          方法區(qū)(Method Area)與 Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做 Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開來。
          Java 虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制非常寬松,除了和 Java 堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。垃圾收集行為在這個(gè)區(qū)域是比較少出現(xiàn)的,其內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載。當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出 OutOfMemoryError 異常。
          JDK8 之前,Hotspot 中方法區(qū)的實(shí)現(xiàn)是永久代(Perm),JDK8 開始使用元空間(Metaspace),以前永久代所有內(nèi)容的字符串常量移至堆內(nèi)存,其他內(nèi)容移至元空間,元空間直接在本地內(nèi)存分配。
          為什么要使用元空間取代永久代的實(shí)現(xiàn)?
          1. 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。

          2. 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。

          3. 永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。

          4. 將 HotSpot 與 JRockit 合二為一。

          運(yùn)行時(shí)常量池

          運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
          一般來說,除了保存 Class 文件中描述的符號(hào)引用外,還會(huì)把翻譯出來的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中。
          運(yùn)行時(shí)常量池相對(duì)于 Class 文件常量池的另外一個(gè)重要特征是具備動(dòng)態(tài)性,Java 語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入 Class 文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是 String 類的 intern() 方法。
          既然運(yùn)行時(shí)常量池是方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError 異常。

          直接內(nèi)存

          直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是 Java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。
          在 JDK 1.4 中新加入了 NIO,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的 I/O 方式,它可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽?Java 堆和 Native 堆中來回復(fù)制數(shù)據(jù)。
          顯然,本機(jī)直接內(nèi)存的分配不會(huì)受到 Java 堆大小的限制,但是,既然是內(nèi)存,肯定還是會(huì)受到本機(jī)總內(nèi)存(包括 RAM 以及 SWAP 區(qū)或者分頁文件)大小以及處理器尋址空間的限制。服務(wù)器管理員在配置虛擬機(jī)參數(shù)時(shí),會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置 -Xmx 等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級(jí)的限制),從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn) OutOfMemoryError 異常。

          Java內(nèi)存模型

          Java內(nèi)存模型是共享內(nèi)存的并發(fā)模型,線程之間主要通過讀-寫共享變量(堆內(nèi)存中的實(shí)例域,靜態(tài)域和數(shù)組元素)來完成隱式通信。
          Java 內(nèi)存模型(JMM)控制 Java 線程之間的通信,決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見。

          計(jì)算機(jī)高速緩存和緩存一致性

          計(jì)算機(jī)在高速的 CPU 和相對(duì)低速的存儲(chǔ)設(shè)備之間使用高速緩存,作為內(nèi)存和處理器之間的緩沖。將運(yùn)算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速運(yùn)行,當(dāng)運(yùn)算結(jié)束后再從緩存同步回內(nèi)存之中。
          在多處理器的系統(tǒng)中(或者單處理器多核的系統(tǒng)),每個(gè)處理器內(nèi)核都有自己的高速緩存,它們有共享同一主內(nèi)存(Main Memory)。
          當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致。
          為此,需要各個(gè)處理器訪問緩存時(shí)都遵循一些協(xié)議,在讀寫時(shí)要根據(jù)協(xié)議進(jìn)行操作,來維護(hù)緩存的一致性。

          JVM主內(nèi)存與工作內(nèi)存

          Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量(線程共享的變量)存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)。
          Java內(nèi)存模型中規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。
          這里的工作內(nèi)存是 JMM 的一個(gè)抽象概念,也叫本地內(nèi)存,其存儲(chǔ)了該線程以讀 / 寫共享變量的副本。
          就像每個(gè)處理器內(nèi)核擁有私有的高速緩存,JMM 中每個(gè)線程擁有私有的本地內(nèi)存。
          不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間的通信一般有兩種方式進(jìn)行,一是通過消息傳遞,二是共享內(nèi)存。Java 線程間的通信采用的是共享內(nèi)存方式,線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示:
          這里所講的主內(nèi)存、工作內(nèi)存與 Java 內(nèi)存區(qū)域中的 Java 堆、棧、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分,這兩者基本上是沒有關(guān)系的,如果兩者一定要勉強(qiáng)對(duì)應(yīng)起來,那從變量、主內(nèi)存、工作內(nèi)存的定義來看,主內(nèi)存主要對(duì)應(yīng)于Java堆中的對(duì)象實(shí)例數(shù)據(jù)部分,而工作內(nèi)存則對(duì)應(yīng)于虛擬機(jī)棧中的部分區(qū)域。

          重排序和happens-before規(guī)則

          在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。重排序分三種類型:
          • 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

          • 指令級(jí)并行的重排序。現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。

          • 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀 / 寫緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

          從 java 源代碼到最終實(shí)際執(zhí)行的指令序列,會(huì)分別經(jīng)歷下面三種重排序:
          JMM 屬于語言級(jí)的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺(tái)之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證。
          java 編譯器禁止處理器重排序是通過在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障(重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置)指令來實(shí)現(xiàn)的。

          happens-before

          從 JDK5 開始,java 內(nèi)存模型提出了 happens-before 的概念,通過這個(gè)概念來闡述操作之間的內(nèi)存可見性。
          如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見,那么這兩個(gè)操作之間必須存在 happens-before 關(guān)系。這里提到的兩個(gè)操作既可以是在一個(gè)線程之內(nèi),也可以是在不同線程之間。
          這里的“可見性”是指當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來說是可以立即得知的。
          如果 A happens-before B,那么 Java 內(nèi)存模型將向程序員保證—— A 操作的結(jié)果將對(duì) B 可見,且 A 的執(zhí)行順序排在 B 之前。
          重要的 happens-before 規(guī)則如下:
          • 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens- before 于該線程中的任意后續(xù)操作。

          • 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器鎖的解鎖,happens- before 于隨后對(duì)這個(gè)監(jiān)視器鎖的加鎖。

          • volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫,happens- before 于任意后續(xù)對(duì)這個(gè) volatile 域的讀。

          • 傳遞性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。

          下圖是 happens-before 與 JMM 的關(guān)系

          volatile關(guān)鍵字

          volatile 可以說是 JVM 提供的最輕量級(jí)的同步機(jī)制,當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性:
          • 保證此變量對(duì)所有線程的可見性。而普通變量不能做到這一點(diǎn),普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成。

          注意,volatile 雖然保證了可見性,但是 Java 里面的運(yùn)算并非原子操作,導(dǎo)致 volatile 變量的運(yùn)算在并發(fā)下一樣是不安全的。而 synchronized 關(guān)鍵字則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”這條規(guī)則獲得線程安全的。
          • 禁止指令重排序優(yōu)化。普通的變量?jī)H僅會(huì)保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。

          推薦閱讀:

          【106期】面試官:Java中的finally一定會(huì)被執(zhí)行嗎?

          【105期】面試官:注冊(cè)中心全部宕掉后,Dubbo服務(wù)還能進(jìn)行調(diào)用嗎?

          【103期】史上最全的數(shù)據(jù)庫面試題,面試前刷一刷!

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號(hào)內(nèi)回復(fù)「2048」,即可免費(fèi)獲取!!

          微信掃描二維碼,關(guān)注我的公眾號(hào)

          朕已閱?

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码一区二区免费三区在线 | 午夜成人久久久 | 爱爱199极品 | 免费在线观看亚洲视频 | 91乱伦小说 |