8、JVM內(nèi)存結(jié)構(gòu)與Java內(nèi)存模型(JMM)

前言


Java內(nèi)存模型(Java Memory Model)簡稱JMM,是java語言的運行時內(nèi)存模型和規(guī)范, 是一種編程語言的規(guī)范;
JVM內(nèi)存模型,是虛擬機(jī)的內(nèi)存管理模型,是一種虛擬機(jī)工程規(guī)范;
JVM不僅僅是java語言的運行時容器,還可以運行多種其他語言,如Groovy、Scala等等, JVM的內(nèi)存模型跟Java語言本身是沒有關(guān)系的;

JVM 內(nèi)存模型


JDK1.7以前的內(nèi)存結(jié)構(gòu)

JDK-1.7-以前的結(jié)構(gòu)

JVM內(nèi)存結(jié)構(gòu)主要有三大塊:堆內(nèi)存、方法區(qū)和棧。

堆內(nèi)存是JVM中最大的一塊,由年輕代和老年代組成,而年輕代內(nèi)存又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,默認(rèn)情況下年輕代的這3種空間年輕代按照8:1:1的比例來分配;
方法區(qū)存儲類信息、常量、靜態(tài)變量等數(shù)據(jù),是線程共享的區(qū)域,為與Java堆區(qū)分,方法區(qū)還有一個別名Non-Heap(非堆);
棧又分為java虛擬機(jī)棧和本地方法棧主要用于方法的執(zhí)行;

JDK1.8以后的內(nèi)存結(jié)構(gòu)

JDK-1.8-開始的結(jié)構(gòu)

以前的方法區(qū)(或永久代),用來存放class,Method等元數(shù)據(jù)信息,但在JDK1.8已經(jīng)沒有了,取而代之的是MetaSpace(元空間),元空間不在虛擬機(jī)里面,而是直接使用本地內(nèi)存。

為什么要用元空間代替永久代?
(1) 類以及方法的信息比較難確定其大小,因此對于永久代的指定比較困難,太小容易導(dǎo)致永久代溢出,太大容易導(dǎo)致老年代溢出。
(2) 永久代會給GC帶來不需要的復(fù)雜度,并且回收效率偏低。

JVM架構(gòu)概覽

虛擬機(jī)運行架構(gòu)

1. Java堆(Heap)

對于大多數(shù)應(yīng)用來說,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。

Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做“GC堆”。如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,所以Java堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點的有Eden空間、From Survivor空間、To Survivor空間等。

根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實現(xiàn)時,既可以實現(xiàn)成固定大小的,也可以是可擴(kuò)展的,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實現(xiàn)的(通過-Xmx和-Xms控制)。

如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴(kuò)展時,將會拋出OutOfMemoryError異常。

2. 方法區(qū)(Method Area)

方法區(qū)

方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,在 Java 虛擬機(jī)規(guī)范中是這樣定義方法區(qū)的:它存儲了每個類的結(jié)構(gòu)信息,例如運行時常量池、字段、方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容,還包括一些在類、實例、接口初始化時用到的特殊方法。

HotSpot虛擬機(jī)上把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,僅僅是因為HotSpot虛擬機(jī)的設(shè)計團(tuán)隊選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實現(xiàn)方法區(qū)而已。

Java虛擬機(jī)規(guī)范對這個區(qū)域的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實現(xiàn)垃圾收集。這個區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載。

根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。

3. 程序計數(shù)器(Program Counter Register)

程序計數(shù)器

程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里(僅是概念模型,各種虛擬機(jī)可能會通過一些更高效的方式去實現(xiàn)),字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。

由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,獨立存儲,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。

如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法,這個計數(shù)器值則為空(Undefined)。

此內(nèi)存區(qū)域是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。

4. JVM棧(JVM Stacks)

JVM線程棧幀

與程序計數(shù)器一樣,Java虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。

  • 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,根據(jù)不同的虛擬機(jī)實現(xiàn),它可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苤赶蛞粋€代表對象的句柄或者其他與此對象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
  • 操作數(shù)棧,和局部變量區(qū)一樣,也被組織成一個以字長為單位的數(shù)組,但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的,可把操作數(shù)棧理解為存儲計算時,臨時數(shù)據(jù)的存儲區(qū)域。
  • 除了局部變量區(qū)和操作數(shù)棧外,java棧幀還需要一些數(shù)據(jù)來支持常量池解析、正常方法返回以及異常派發(fā)機(jī)制。這些數(shù)據(jù)都保存在java棧幀的幀數(shù)據(jù)區(qū)中。

在Java虛擬機(jī)規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動態(tài)擴(kuò)展,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),當(dāng)擴(kuò)展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。

5. 本地方法棧(Native Method Stacks)

本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。虛擬機(jī)規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實現(xiàn)它。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。

JVM 調(diào)參

image.png
jvm常用參數(shù) 含義
-XX:+PrintGC/-verbose:gc 打印GC的簡要信息
-XX:+PrintGCDetails 打印GC的詳細(xì)信息
-XX:+PrintGCDateStamps 打印GC發(fā)生的時間
-Xloggc:log/gc.log 指定GC的log位置,以文件輸出
-XX:+PrintHeapAtGC 每一次GC后都打印堆信息
-XX:+HeapDumpOnOutOfMemoryError 當(dāng)JVM發(fā)生OOM時,自動生成DUMP文件
-XX:HeapDumpPath=${目錄} 生成的DUMP文件的存放位置
-Xms 初始堆大小,默認(rèn)是物理內(nèi)存的1/64
-Xmx 最大堆大小 默認(rèn)是物理內(nèi)存的1/4
-Xmn 年輕代的大小,默認(rèn)整個堆的3/8
-Xss 設(shè)置每個線程的堆棧大小
-XX:MetaspaceSize 初始空間大小,達(dá)到該值就會觸發(fā)垃圾收集進(jìn)行類型卸載,同時GC會對該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當(dāng)提高該值
-XX:MaxMetaspaceSize 空間最大內(nèi)存,默認(rèn)是沒有限制的
-XX:MaxDirectMemorySize 限制堆外內(nèi)存的使用
-XX:+DisableExplicitGC 禁用 System.gc 顯式FullGC
-XX:ReservedCodeCacheSize 調(diào)整JIT編譯代碼緩存
-XX:PretenureSizeThreshold 大于這個值的對象直接在老年代分配
-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中斷的執(zhí)行時間??膳c上面混合使用。輸出形式: Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期間程序暫停的時間

JVM堆外內(nèi)存

堆外內(nèi)存就是把內(nèi)存對象分配在Java虛擬機(jī)的堆以外的內(nèi)存,這些內(nèi)存直接受操作系統(tǒng)管理(而不是虛擬機(jī)),這樣做的結(jié)果就是能夠在一定程度上減少垃圾回收對應(yīng)用程序造成的影響。

堆外內(nèi)存主要包含:

  1. Meta Space 元空間
  2. Code Cache JIT編譯代碼緩存
  3. JVM線程棧
  4. Native方法棧
  5. 程序計數(shù)器
  6. Class Compress Space 類壓縮空間

作為JAVA開發(fā)者我們經(jīng)常用java.nio.DirectByteBuffer對象進(jìn)行堆外內(nèi)存的管理和使用,它會在對象創(chuàng)建的時候就分配堆外內(nèi)存。

java.nio.DirectByteBuffer對象在創(chuàng)建過程中會先通過Unsafe接口直接通過os::malloc來分配內(nèi)存,然后將內(nèi)存的起始地址和大小存到j(luò)ava.nio.DirectByteBuffer對象里,這樣就可以直接操作這些內(nèi)存。這些內(nèi)存只有在DirectByteBuffer回收掉之后才有機(jī)會被回收,因此如果這些對象大部分都移到了old,但是一直沒有觸發(fā)CMS GC或者Full GC,那么悲劇將會發(fā)生,因為你的物理內(nèi)存被他們耗盡了,因此為了避免這種悲劇的發(fā)生,通過-XX:MaxDirectMemorySize來指定最大的堆外內(nèi)存大小,當(dāng)使用達(dá)到了閾值的時候?qū)⒄{(diào)用System.gc來做一次full gc,以此來回收掉沒有被使用的堆外內(nèi)存

堆外內(nèi)存的好處:
1、減少了垃圾回收,因為堆內(nèi)存垃圾回收會Stop The World。
2、加快了復(fù)制的速度,堆內(nèi)數(shù)據(jù)在flush到遠(yuǎn)程時,會先復(fù)制到直接內(nèi)存(非堆內(nèi)存),然后在發(fā)送,而堆外內(nèi)存相當(dāng)于省略掉了這個工作。

堆外內(nèi)存的缺點:
1、堆外內(nèi)存的缺點就是內(nèi)存難以控制,使用了堆外內(nèi)存就間接失去了JVM管理內(nèi)存的可行性,改由自己來管理,當(dāng)發(fā)生內(nèi)存溢出時排查起來非常困難。

有許多單機(jī)緩存框架是用的堆外內(nèi)存,比如EHCache。

Java內(nèi)存模型(JMM)


Java的共享內(nèi)存

線程內(nèi)存模型

由上述對JVM內(nèi)存結(jié)構(gòu)的描述中,我們知道了堆和方法區(qū)是線程共享的。方法調(diào)用的局部變量就不會在線程之間共享,它們不會有內(nèi)存可見性問題,也不受內(nèi)存模型的影響。

CPU的處理速度和主存的讀寫速度不是一個量級的,為了平衡這種巨大的差距,每個CPU都會有緩存。共享變量會先放在主存中,每個線程都有屬于自己的工作內(nèi)存,并且會把位于主存中的共享變量拷貝到自己的工作內(nèi)存,之后的讀寫操作均使用位于工作內(nèi)存的變量副本,并在某個時刻將工作內(nèi)存的變量副本寫回到主存中去。

Java線程之間的通信由Java內(nèi)存模型控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。

從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,它涵蓋了各種CPU緩存、寄存器以及其他的硬件和編譯器優(yōu)化。

Java的重排序

Class Reordering {
  int x = 0, y = 0;
  public void writer() {
    x = 1;
    y = 2;
  }
  public void reader() {
    int r1 = y;
    int r2 = x;
  }
}

在執(zhí)行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。

這里說的重排序可以發(fā)生在好幾個地方:編譯器、運行時、JIT等,比如編譯器會覺得把一個變量的寫操作放在最后會更有效率,編譯后,這個指令就在最后了(前提是只要不改變程序的語義,編譯器、執(zhí)行器就可以這樣自由的隨意優(yōu)化),一旦編譯器對某個變量的寫操作進(jìn)行優(yōu)化(放到最后),那么在執(zhí)行之前,另一個線程將不會看到這個執(zhí)行結(jié)果。

JMM 語義

由于Java共享內(nèi)存以及Java重排序的存在,會導(dǎo)致多線程環(huán)境下存在操作可見性的問題。

為了方便程序員進(jìn)行并發(fā)編程,Java定義了一些規(guī)則,這規(guī)則稱為happens-before規(guī)則,從JDK 5 開始,JMM就使用happens-before的概念來闡述多線程編程時的操作可見性。

happends-before含義:cpu在某個時間片執(zhí)行一個操作后,cpu按時間輪片到后續(xù)任意線程執(zhí)行時,都能觀察到這個操作的效果;

在JMM中,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須存在happens-before關(guān)系。

happens-before原則定義如下:
1、程序次序規(guī)則:一個線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作;
2、鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖額lock操作;
3、volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作;
4、傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C;
5、線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個一個動作;
6、線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生;
7、線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行;
8、對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始;

JMM的底層實現(xiàn)

從java編程的角度來看,JMM是通過synchronizevolatile以及并發(fā)包里的鎖來實現(xiàn)JMM語義;

volatile 實現(xiàn)原理

volatile通過插入內(nèi)存屏障保證線程可見性和禁止重排序。

volatile可見性實現(xiàn)原理

從JVM編譯器的字節(jié)碼角度來看:
volatile在JVM編譯時是采用“內(nèi)存屏障”來實現(xiàn)的。觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時,會多出一個lock前綴指令。lock前綴指令其實就相當(dāng)于一個內(nèi)存屏障。內(nèi)存屏障是一組處理指令,用來實現(xiàn)對內(nèi)存操作的順序限制。

從操作系統(tǒng)角度看:
加了內(nèi)存屏障的字節(jié)碼在執(zhí)行時,操作系統(tǒng)解決了緩存一致性;
操作系統(tǒng)解決緩存一致性方案有兩種:
1、 通過在總線加LOCK的方式
2、通過緩存一致性協(xié)議
但是方案1存在一個問題,它是采用一種獨占的方式來實現(xiàn)的,即總線加LOCK#鎖的話,只能有一個CPU能夠運行,其他CPU都得阻塞,效率較為低下。 第二種方案,緩存一致性協(xié)議(MESI協(xié)議)它確保每個緩存中使用的共享變量的副本是一致的。其核心思想如下:當(dāng)某個CPU在寫數(shù)據(jù)時,如果發(fā)現(xiàn)操作的變量是共享變量,則會通知其他CPU告知該變量的緩存行是無效的,因此其他CPU在讀取該變量時,發(fā)現(xiàn)其無效會重新從主存中加載數(shù)據(jù)。

volatile 禁止重排序?qū)崿F(xiàn)原理

從JVM編譯器的字節(jié)碼角度來看:
首先是插入內(nèi)存屏障;其次會禁止一些特定類型的編譯器重排序;

從操作系統(tǒng)的角度看:
處理器重排序,是指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序;
針對處理器重排序,編譯器在生成指令序列的時候會通過插入內(nèi)存屏障指令來禁止某些特殊的處理器重排序。

synchronize 實現(xiàn)原理

synchronize 比 volatile更重一些,映射到操作系統(tǒng)的底層原理基本一致,在JVM層級的實現(xiàn)會更復(fù)雜,synchronize的JVM實現(xiàn)做過很多優(yōu)化,不僅保證可見性、有序性,還能保證代碼塊的原子性;

synchronize是通過java對象頭的Mark區(qū)來輔助實現(xiàn),分為無鎖、偏向鎖、輕量級鎖(自旋)、重量級鎖(系統(tǒng)調(diào)用)等狀態(tài);

image.png
ObjectMonitor() {
    _count        = 0; //用來記錄該對象被線程獲取鎖的次數(shù)
    _waiters      = 0;
    _recursions   = 0; //鎖的重入次數(shù)
    _owner        = NULL; //指向持有ObjectMonitor對象的線程 
    _WaitSet      = NULL; //處于wait狀態(tài)的線程,會被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _EntryList    = NULL ; //處于等待鎖block狀態(tài)的線程,會被加入到該列表
}

JMM實現(xiàn)了更友好的并發(fā)編程

總的來說,在多線程開發(fā)時需要從原子性,有序性,可見性三個方面進(jìn)行考慮;

as-if-serialhappens-before

  • as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變,happens-before關(guān)系保證正確同步的多線程程序的執(zhí)行結(jié)果不被改變。
  • as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個幻境:單線程程序是按程序的順序來執(zhí)行的。
  • happens-before關(guān)系給編寫正確同步的多線程程序的程序員創(chuàng)造了一個幻境:正確同步的多線程程序是按happens-before指定的順序來執(zhí)行的。
  • as-if-serial語義和happens-before這么做的目的,都是為了在不改變程序執(zhí)行結(jié)果的前提下,盡可能地提高程序執(zhí)行的并行度。

站在JMM設(shè)計者的角度,在設(shè)計JMM時需要考慮兩個關(guān)鍵因素:

1、程序員對內(nèi)存模型的使用

  • 程序員希望內(nèi)存模型易于理解、易于編程。
  • 程序員希望基于一個強(qiáng)內(nèi)存模型來編寫代碼。

2、編譯器和處理器對內(nèi)存模型的實現(xiàn)

  • 編譯器和處理器希望內(nèi)存模型對它們的束縛越少越好,這樣它們就可以做盡可能多的優(yōu)化來提高性能。
  • 編譯器和處理器希望實現(xiàn)一個弱內(nèi)存模型。

JMM其實是在遵循一個基本原則:只要不改變程序的執(zhí)行結(jié)果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎么優(yōu)化都行。
例如,如果編譯器經(jīng)過細(xì)致的分析后,認(rèn)定一個鎖只會被單個線程訪問,那么這個鎖可以被消除。再如,如果編譯器經(jīng)過細(xì)致的分析后,認(rèn)定一個volatile變量只會被單個線程訪問,那么編譯器可以把這個volatile變量當(dāng)作一個普通變量來對待。這些優(yōu)化既不會改變程序的執(zhí)行結(jié)果,又能提高程序的執(zhí)行效率。

image.png

一個happens-before規(guī)則對應(yīng)于一個或多個編譯器和處理器重排序規(guī)則。對于Java程序員來說,happens-before規(guī)則簡單易懂,它避免Java程序員為了理解JMM提供的可見性保證而去學(xué)習(xí)復(fù)雜的重排序規(guī)則以及這些規(guī)則的具體實現(xiàn)方法.

參考資料

synchronize鎖升級

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容