Java內存模型原理,你真的理解嗎?

2021-02-20 51CTO技術棧

最近重新學習了一遍《深入學習 Java 虛擬機》,把之前 Java 內存模型中模糊的知識重新梳理了一遍。

這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章後能對 Java 內存模型體系產生一個相對清晰的理解,知其然知其所以然。

在介紹 Java 內存模型之前,我們先了解一下物理計算機中的並發問題,理解這些問題可以搞清楚內存模型產生的背景。

物理機遇到的並發問題與虛擬機中的情況有不少相似之處,物理機的解決方案對虛擬機的實現有相當的參考意義。

硬體的效率問題

計算機處理器處理絕大多數運行任務都不可能只靠處理器「計算」就能完成,處理器至少需要與內存交互,如讀取運算數據、存儲運算結果,這個 I/O 操作很難消除(無法僅靠寄存器完成所有運算任務)。

由於計算機的存儲設備與處理器的運算速度有幾個數量級的差距,為了避免處理器等待緩慢的內存完成讀寫操作,現代計算機系統通過加入一層讀寫速度儘可能接近處理器運算速度的高速緩存。

緩存作為內存和處理器之間的緩衝:將運算需要使用到的數據複製到緩存中,讓運算能快速運行,當運算結束後再從緩存同步回內存之中。 

緩存一致性問題

基於高速緩存的存儲系統交互很好的解決了處理器與內存速度的矛盾,但是也為計算機系統帶來更高的複雜度,因為引入了一個新問題:緩存一致性。

在多處理器的系統中(或者單處理器多核的系統),每個處理器(每個核)都有自己的高速緩存,而它們有共享同一主內存(Main Memory)。

當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致。 

為此,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議進行操作,來維護緩存的一致性。

代碼亂序執行優化問題

為了使得處理器內部的運算單元儘量被充分利用,提高運算效率,處理器可能會對輸入的代碼進行亂序執行。

處理器會在計算之後將亂序執行的結果重組,亂序優化可以保證在單線程下該執行結果與順序執行的結果是一致的,但不保證程序中各個語句計算的先後順序與輸入代碼中的順序一致。

亂序執行技術是處理器為提高運算速度而做出違背代碼原有順序的優化。在單核時代,處理器保證做出的優化不會導致執行結果遠離預期目標,但在多核環境下卻並非如此。

在多核環境下, 如果存在一個核的計算任務依賴另一個核計算任務的中間結果。

而且對相關數據讀寫沒做任何防護措施,那麼其順序性並不能靠代碼的先後順序來保證,處理器最終得出的結果和我們邏輯得到的結果可能會大不相同。

以上圖為例進行說明,CPU 的 core2 中的邏輯 B 依賴 core1 中的邏輯 A 先執行:

為了更好解決上面提到的系列問題,內存模型被總結提出,我們可以把內存模型理解為在特定操作協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。

不同架構的物理計算機可以有不一樣的內存模型,Java 虛擬機也有自己的內存模型。

Java 虛擬機規範中試圖定義一種 Java 內存模型(Java Memory Model,簡稱 JMM)來屏蔽掉各種硬體和作業系統的內存訪問差異,以實現讓 Java 程序在各種平臺下都能達到一致的內存訪問效果,不必因為不同平臺上的物理機的內存模型的差異,對各平臺定製化開發程序。

更具體一點說,Java 內存模型提出目標在於,定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。

此處的變量(Variables)與 Java 編程中所說的變量有所區別,它包括了實例欄位、靜態欄位和構成數值對象的元素,但不包括局部變量與方法參數,因為後者是線程私有的。

註:如果局部變量是一個 reference 類型,它引用的對象在 Java 堆中可被各個線程共享,但是 reference 本身在 Java 棧的局部變量表中,它是線程私有的。

主內存

Java 內存模型規定了所有變量都存儲在主內存(Main Memory)中(此處的主內存與介紹物理硬體的主內存名字一樣,兩者可以互相類比,但此處僅是虛擬機內存的一部分)。

工作內存

每條線程都有自己的工作內存(Working Memory,又稱本地內存,可與前面介紹的處理器高速緩存類比),線程的工作內存中保存了該線程使用到的變量的主內存中的共享變量的副本拷貝。

工作內存是 JMM 的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬體和編譯器優化。

Java 內存模型抽象示意圖如下:

結合前面介紹的物理機的處理器處理內存的問題,可以類比總結出 JVM 內存操作的問題,下面介紹的 Java 內存模型的執行處理將圍繞解決這兩個問題展開。

工作內存數據一致性 

各個線程操作數據時會保存使用到的主內存中的共享變量副本,當多個線程的運算任務都涉及同一個共享變量時,將導致各自的共享變量副本不一致,如果真的發生這種情況,數據同步回主內存以誰的副本數據為準? 

Java 內存模型主要通過一系列的數據同步協議、規則來保證數據的一致性,後面再詳細介紹。

指令重排序優化 

Java 中重排序通常是編譯器或運行時環境為了優化程序性能而採取的對指令進行重新排序執行的一種手段。

重排序分為兩類:編譯期重排序和運行期重排序,分別對應編譯時和運行時環境。 

同樣的,指令重排序不是隨意重排序,它需要滿足以下兩個條件:

多線程環境下,如果線程處理邏輯之間存在依賴關係,有可能因為指令重排序導致運行結果與預期不同,後面再展開 Java 內存模型如何解決這種情況。

在理解 Java 內存模型的系列協議、特殊規則之前,我們先理解 Java 中內存間的交互操作。

為了更好理解內存的交互操作,以線程通信為例,我們看看具體如何進行線程間值的同步:

線程 1 和線程 2 都有主內存中共享變量 x 的副本,初始時,這 3 個內存中 x 的值都為 0。

線程 1 中更新 x 的值為 1 之後同步到線程 2 主要涉及兩個步驟: 

從整體上看,這兩個步驟是線程 1 在向線程 2 發消息,這個通信過程必須經過主內存。

JMM 通過控制主內存與每個線程本地內存之間的交互,來為各個線程提供共享變量的可見性。

關於主內存與工作內存之間的具體交互協議,即一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存之類的實現細節,Java 內存模型中定義了下面 8 種操作來完成。

虛擬機實現時必須保證下面介紹的每種操作都是原子的,不可再分的(對於 double 和 long 型的變量來說,load、store、read、和 write 操作在某些平臺上允許有例外)。

8 種基本操作,如下圖:

lock (鎖定) ,作用於主內存的變量,它把一個變量標識為一條線程獨佔的狀態。

unlock (解鎖) ,作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才可以被其他線程鎖定。

read (讀取) ,作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的 load 動作使用。

load (載入) ,作用於工作內存的變量,它把 read 操作從主內存中得到的變量值放入工作內存的變量副本中。

use (使用) ,作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時就會執行這個操作。

assign (賦值) ,作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。

store (存儲) ,作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨後 write 操作使用。

write (寫入) ,作用於主內存的變量,它把 Store 操作從工作內存中得到的變量的值放入主內存的變量中。

在介紹內存交互的具體的 8 種基本操作之前,有必要先介紹一下操作的 3 個特性。

Java 內存模型是圍繞著在並發過程中如何處理這 3 個特性來建立的,這裡先給出定義和基本實現的簡單介紹,後面會逐步展開分析。

原子性(Atomicity) 

原子性,即一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

即使在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程所幹擾。

可見性(Visibility) 

可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。 

正如上面「交互操作流程」中所說明的一樣,JMM 是通過在線程 1 變量工作內存修改後將新值同步回主內存,線程 2 在變量讀取前從主內存刷新變量值,這種依賴主內存作為傳遞媒介的方式來實現可見性。

有序性(Ordering) 

有序性規則表現在以下兩種場景:

線程內,從某個線程的角度看方法的執行,指令會按照一種叫「串行」(as-if-serial)的方式執行,此種方式已經應用於順序程式語言。

線程間,這個線程「觀察」到其他線程並發地執行非同步的代碼時,由於指令重排序優化,任何代碼都有可能交叉執行。

唯一起作用的約束是:對於同步方法,同步塊(synchronized 關鍵字修飾)以及 volatile 欄位的操作仍維持相對有序。

Java 內存模型的一系列運行規則看起來有點繁瑣,但總結起來,是圍繞原子性、可見性、有序性特徵建立。

歸根究底,是為實現共享變量的在多個線程的工作內存的數據一致性,多線程並發,指令重排序優化的環境中程序能如預期運行。

介紹系列規則之前,首先了解一下 happens-before 關係:用於描述下 2 個操作的內存可見性。如果操作 A happens-before 操作 B,那麼 A 的結果對 B 可見。

happens-before 關係的分析需要分為單線程和多線程的情況:

單線程下的 happens-before,字節碼的先後順序天然包含 happens-before 關係:因為單線程內共享一份工作內存,不存在數據一致性的問題。 

在程序控制流路徑中靠前的字節碼 happens-before 靠後的字節碼,即靠前的字節碼執行完之後操作結果對靠後的字節碼可見。

然而,這並不意味著前者一定在後者之前執行。實際上,如果後者不依賴前者的運行結果,那麼它們可能會被重排序。

多線程下的 happens-before,多線程由於每個線程有共享變量的副本,如果沒有對共享變量做同步處理,線程 1 更新執行操作 A 共享變量的值之後,線程 2 開始執行操作 B,此時操作 A 產生的結果對操作 B 不一定可見。

為了方便程序開發,Java 內存模型實現了下述支持 happens-before 關係的操作: 

程序次序規則,一個線程內,按照代碼順序,書寫在前面的操作 happens-before 書寫在後面的操作。

鎖定規則,一個 unLock 操作 happens-before 後面對同一個鎖的 lock 操作。

volatile 變量規則,對一個變量的寫操作 happens-before 後面對這個變量的讀操作。

傳遞規則,如果操作 A happens-before 操作 B,而操作 B 又 happens-before 操作 C,則可以得出操作 A happens-before 操作 C。

線程啟動規則,Thread 對象的 start() 方法 happens-before 此線程的每個一個動作。

線程中斷規則,對線程 interrupt() 方法的調用 happens-before 被中斷線程的代碼檢測到中斷事件的發生。

線程終結規則,線程中所有的操作都 happens-before 線程的終止檢測,我們可以通過 Thread.join() 方法結束、Thread.isAlive() 的返回值手段檢測到線程已經終止執行。

對象終結規則,一個對象的初始化完成 happens-before 它的 finalize() 方法的開始。

Java 中如何保證底層操作的有序性和可見性?可以通過內存屏障。

內存屏障是被插入兩個 CPU 指令之間的一種指令,用來禁止處理器指令發生重排序(像屏障一樣),從而保障有序性的。

另外,為了達到屏障的效果,它也會使處理器寫入、讀取值之前,將主內存的值寫入高速緩存,清空無效隊列,從而保障可見性。

舉個例子說明:

Store1; 
Store2;   
Load1;   
StoreLoad;  
Store3;   
Load2;   
Load3;

對於上面的一組 CPU 指令(Store 表示寫入指令,Load 表示讀取指令,StoreLoad 代表寫讀內存屏障),StoreLoad 屏障之前的 Store 指令無法與 StoreLoad 屏障之後的 Load 指令進行交換位置,即重排序。

但是 StoreLoad 屏障之前和之後的指令是可以互換位置的,即 Store1 可以和 Store2 互換,Load2 可以和 Load3 互換。

常見有 4 種屏障:

LoadLoad 屏障:對於這樣的語句 Load1;LoadLoad;Load2,在 Load2 及後續讀取操作要讀取的數據被訪問前,保證 Load1 要讀取的數據被讀取完畢。

StoreStore 屏障:對於這樣的語句 Store1;StoreStore;Store2,在 Store2 及後續寫入操作執行前,保證 Store1 的寫入操作對其他處理器可見。

LoadStore 屏障:對於這樣的語句 Load1;LoadStore;Store2,在 Store2 及後續寫入操作被執行前,保證 Load1 要讀取的數據被讀取完畢。

StoreLoad 屏障:對於這樣的語句 Store1;StoreLoad;Load2,在 Load2 及後續所有讀取操作執行前,保證 Store1 的寫入對所有處理器可見。它的開銷是四種屏障中最大的(衝刷寫緩衝器,清空無效化隊列)。

在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其他三種內存屏障的功能。

Java 中對內存屏障的使用在一般的代碼中不太容易見到,常見的有 volatile 和 synchronized 關鍵字修飾的代碼塊(後面再展開介紹),還可以通過 Unsafe 這個類來使用內存屏障。

JMM 在執行前面介紹 8 種基本操作時,為了保證內存間數據一致性,JMM 中規定需要滿足以下規則:

規則 1:如果要把一個變量從主內存中複製到工作內存,就需要按順序的執行 read 和 load 操作,如果把變量從工作內存中同步回主內存中,就要按順序的執行 store 和 write 操作。

但 Java 內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。

規則 2:不允許 read 和 load、store 和 write 操作之一單獨出現。

規則 3:不允許一個線程丟棄它的最近 assign 的操作,即變量在工作內存中改變了之後必須同步到主內存中。

規則 4:不允許一個線程無原因的(沒有發生過任何 assign 操作)把數據從工作內存同步回主內存中。

規則 5:一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load 或 assign )的變量。

即對一個變量實施 use 和 store 操作之前,必須先執行過了 load 或 assign 操作。

規則 6:一個變量在同一個時刻只允許一條線程對其進行 lock 操作,但 lock 操作可以被同一條線程重複執行多次,多次執行 lock 後,只有執行相同次數的 unlock 操作,變量才會被解鎖。所以 lock 和 unlock 必須成對出現。

規則 7:如果對一個變量執行 lock 操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行 load 或 assign 操作初始化變量的值。

規則 8:如果一個變量事先沒有被 lock 操作鎖定,則不允許對它執行 unlock 操作;也不允許去 unlock 一個被其他線程鎖定的變量。

規則 9:對一個變量執行 unlock 操作之前,必須先把此變量同步到主內存中(執行 store 和 write 操作)。

看起來這些規則有些繁瑣,其實也不難理解:

規則 1、規則 2,工作內存中的共享變量作為主內存的副本,主內存變量的值同步到工作內存需要 read 和 load 一起使用。

工作內存中的變量的值同步回主內存需要 store 和 write 一起使用,這 2 組操作各自都是一個固定的有序搭配,不允許單獨出現。

規則 3、規則 4,由於工作內存中的共享變量是主內存的副本,為保證數據一致性,當工作內存中的變量被字節碼引擎重新賦值,必須同步回主內存。如果工作內存的變量沒有被更新,不允許無原因同步回主內存。

規則 5,由於工作內存中的共享變量是主內存的副本,必須從主內存誕生。

規則 6、7、8、9,為了並發情況下安全使用變量,線程可以基於 lock 操作獨佔主內存中的變量,其他線程不允許使用或 unlock 該變量,直到變量被線程 unlock。

volatile 的中文意思是不穩定的,易變的,用 volatile 修飾變量是為了保證變量的可見性。

volatile 的語義

volatile 主要有下面 2 種語義:

保證可見性,保證了不同線程對該變量操作的內存可見性。這裡保證可見性不等同於 volatile 變量並發操作的安全性,保證可見性具體一點解釋:

但是如果多個線程同時把更新後的變量值同時刷新回主內存,可能導致得到的值不是預期結果。

舉個例子:定義 volatile int count = 0,2 個線程同時執行 count++ 操作,每個線程都執行 500 次,最終結果小於 1000。

原因是每個線程執行 count++ 需要以下 3 個步驟:

有可能某一時刻 2 個線程在步驟 1 讀取到的值都是 100,執行完步驟 2 得到的值都是 101,最後刷新了 2 次 101 保存到主內存。

禁止進行指令重排序,具體一點解釋,禁止重排序的規則如下:

普通的變量僅僅會保證該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證賦值操作的順序與程序代碼中的執行順序一致。

舉個例子:

volatile boolean initialized = false;



doSomethingReadConfg();
initialized = true;



while (!initialized) {
     sleep();
}

doSomethingWithConfig();

上面代碼中如果定義 initialized 變量時沒有使用 volatile 修飾,就有可能會由於指令重排序的優化,導致線程 A 中最後一句代碼 "initialized = true" 在 「doSomethingReadConfg()」 之前被執行。

這樣會導致線程 B 中使用配置信息的代碼可能出現錯誤,而 volatile 關鍵字就禁止重排序的語義可以避免此類情況發生。

volatile 型變量實現原理

具體實現方式是在編譯期生成字節碼時,會在指令序列中增加內存屏障來保證,下面是基於保守策略的 JMM 內存屏障插入策略:

在每個 volatile 寫操作的前面插入一個 StoreStore 屏障。該屏障除了保證了屏障之前的寫操作和該屏障之後的寫操作不能重排序,還會保證了 volatile 寫操作之前,任何的讀寫操作都會先於 volatile 被提交。

在每個 volatile 寫操作的後面插入一個 StoreLoad 屏障。該屏障除了使 volatile 寫操作不會與之後的讀操作重排序外,還會刷新處理器緩存,使 volatile 變量的寫更新對其他線程可見。

在每個 volatile 讀操作的後面插入一個 LoadLoad 屏障。該屏障除了使 volatile 讀操作不會與之前的寫操作發生重排序外,還會刷新處理器緩存,使 volatile 變量讀取的為最新值。

在每個 volatile 讀操作的後面插入一個 LoadStore 屏障。該屏障除了禁止了 volatile 讀操作與其之後的任何寫操作進行重排序,還會刷新處理器緩存,使其他線程 volatile 變量的寫更新對 volatile 讀操作的線程可見。

volatile 型變量使用場景

總結起來,就是「一次寫入,到處讀取」,某一線程負責更新變量,其他線程只讀取變量(不更新變量),並根據變量的新值執行相應邏輯。例如狀態標誌位更新,觀察者模型變量值發布。

我們知道,final 成員變量必須在聲明的時候初始化或者在構造器中初始化,否則就會報編譯錯誤。 

final 關鍵字的可見性是指:被 final 修飾的欄位在聲明時或者構造器中,一旦初始化完成,那麼在其他線程無須同步就能正確看見 final 欄位的值。這是因為一旦初始化完成,final 變量的值立刻回寫到主內存。

通過 synchronized 關鍵字包住的代碼區域,對數據的讀寫進行控制:

Java 內存模型要求 lock、unlock、read、load、assign、use、store、write 這 8 種操作都具有原子性。

但是對於 64 位的數據類型(long 和 double),在模型中特別定義相對寬鬆的規定:允許虛擬機將沒有被 volatile 修飾的 64 位數據的讀寫操作分為 2 次 32 位的操作來進行。

也就是說虛擬機可選擇不保證 64 位數據類型的 load、store、read 和 write 這 4 個操作的原子性。

由於這種非原子性,有可能導致其他線程讀到同步未完成的「32 位的半個變量」的值。

不過實際開發中,Java 內存模型強烈建議虛擬機把 64 位數據的讀寫實現為具有原子性。

目前各種平臺下的商用虛擬機都選擇把 64 位數據的讀寫操作作為原子操作來對待,因此我們在編寫代碼時一般不需要把用到的 long 和 double 變量專門聲明為 volatile。

由於 Java 內存模型涉及系列規則,網上的文章大部分就是對這些規則進行解析,但是很多沒有解釋為什麼需要這些規則,這些規則的作用。

其實這是不利於初學者學習的,容易繞進這些繁瑣規則不知所以然,下面談談我的一點學習知識的個人體會:

學習知識的過程不是等同於只是理解知識和記憶知識,而是要對知識解決的問題的輸入和輸出建立連接。

知識的本質是解決問題,所以在學習之前要理解問題,理解這個問題要的輸入和輸出,而知識就是輸入到輸出的一個關係映射。

知識的學習要結合大量的例子來理解這個映射關係,然後壓縮知識,華羅庚說過:「把一本書讀厚,然後再讀薄」,解釋的就是這個道理,先結合大量的例子理解知識,然後再壓縮知識。

以學習 Java 內存模型為例:

理解問題:明確輸入輸出,首先理解 Java 內存模型是什麼,有什麼用,解決什麼問題 。

理解內存模型系列協議:結合大量例子理解這些協議規則。

壓縮知識:大量規則其實就是通過數據同步協議,保證內存副本之間的數據一致性,同時防止重排序對程序的影響。

參考文章:

作者:陳彩華

編輯:陶家龍、孫淑娟

投稿:有投稿、尋求報導意向技術人請聯絡 editor@51cto.com

陳彩華(caison),從事服務端開發,善於系統設計、優化重構、線上問題排查工作,主要開發語言是 Java,微信號:hua1881375。

接手到垃圾代碼,要不要辭職?

面試了近百人,如何招到靠譜的程式設計師?

用大白話告訴你小白都能看懂的Hadoop架構原理

相關焦點

  • 簡述Java內存模型
    本章節主要介紹Java內存模型的設計原理,讓我們更清晰的認識數據在內存中的表現,使我們能夠可以更好的使用他們,也能讓我們在開發中避開很多問題。
  • Java內存模型與volatile關鍵字
    Java的內存模型大概樣子還是有必要了解下的,今天就學習了下,順便學習了一點volatile關鍵字!Java內存模型主內存中存儲一些可以共享的變量比如實例欄位、靜態欄位和構成數組對象的元素,但是不包括局部變量與方法參數,因為它們是線程私有的,不會被共享。
  • java線程前傳——jvm內存結構、內存模型和cpu結構
    ,這個過程是少不了的一個線程肯定是要運行在一個核上的,多個線程可以運行在不同的核上,這個時候,因為緩存的存在,如果沒有同步機制,那一個線程修改了緩存的數據,另一個線程也修改了緩存的數據,這個時候這兩個線程修改後的數據都需要寫入到內存當中,就會出現問題jvm為了方便,將這些緩存抽象出來,構造了自己的內存模型,即主內存和工作內存的數據交互,即java 內存模型(jmm)
  • Java 多線程 —— 深入理解 volatile 的原理以及應用
    推薦閱讀:《java 多線程—線程怎麼來的》這一篇主要講解一下volatile的原理以及應用,想必看完這一篇之後,你會對volatile的應用原理以及使用邊界會有更深刻的認知。本篇主要內容:volatile 讀寫同步原理volatile重排序原則volatile應用關鍵字volatile是jvm提供的輕量級的同步機制,但它並不容易理解,而且在多數情況下用不到,被多數開發者拋棄並採用synchronized代替,synchronized屬於重度鎖,如果你對性能有高的要求,那麼同等情況下,變量聲明volatile會減小更少的同步開銷
  • 筆記07 - Java內存模型
    )中的,這一過程如下所示:隨著CPU技術的發展,CPU的執行速度越來越快,但是內存技術並沒有太大的改變,這就導致內存中數據的讀寫速度和CPU處理數據的速度差距越來越大,CPU需要較長時間等待內存的讀寫,這就意味著CPU會出現空轉的情況。
  • 面試中常會問到的Java內存模型原理,看了這篇秒懂
    這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章後能對 Java 內存模型體系產生一個相對清晰的理解,知其然知其所以然。內存模型產生背景在介紹 Java 內存模型之前,我們先了解一下物理計算機中的並發問題,理解這些問題可以搞清楚內存模型產生的背景。
  • 淺析java內存管理機制
    中,內存管理由JVM完全負責,java中的「垃圾回收器」負責自動回收無用對象佔據的內存資源,這樣可以大大減少程序猿在內存管理上花費的時間,可以更集中於業務邏輯和具體功能實現;但這並不是說java有了垃圾回收器程序猿就可以高枕無憂,將內存管理拋之腦外了!
  • 求你了,再問你Java內存模型的時候別再給我講堆棧方法區了…
    我覺得主要有幾個原因:1、Java內存模型,這個詞聽著太像是關於內存分布的知識了。聽上去和並發編程沒有半毛錢關係。2、網上很多資料都是錯的。不信你去網上搜索一下"Java內存模型",你會發現,很多人打著內存模型的標題,介紹了JVM內存結構的知識。
  • 作為一個Java 程式設計師 你應該會什麼
    2、命令:必須熟悉JDK 帶的一些常用命令及其常用選項,命令至少需要熟悉:appletviewer、 HtmlConverter、jar、 java、 javac、javadoc、javap、javaw、native2ascii、serialver,如果這些命令你沒有全部使用過,那麼你對java 實際上還很不了解。
  • Java並發編程學習前期知識上篇
    Java並發包JUC(java.util.concurrent)有了解過哪些?並發包實現最重要的是什麼?其原理是什麼知道嗎?何為JMM的可見性?volatiile關鍵字是怎麼實現變量可見性的?如果想要學好並發,弄懂理解透徹的話,凱哥覺得以下計算機的知識還是要了解了解。本次《Java並發編程-前期準備知識》凱哥準備用兩篇來介紹,主要包括以下內容:簡單介紹內存之間可見性是什麼?
  • 你有真正理解 Java 的類加載機制嗎?|原力計劃
    作者 | 宜春責編 | Elle出品 | CSDN 博客你是否真的理解Java的類加載機制?點進文章的盆友不如先來做一道非常常見的面試題,如果你能做出來,可能你早已掌握並理解了Java的類加載機制,若結果出乎你的意料,那就很有必要來了解了解Java的類加載機制了。
  • 10分鐘看懂 Java NIO 底層原理
    寫在前面很多的小夥伴,被java IO 模型,搞得有點兒暈,一會兒是4種模型,一會兒又變成了5種模型。很多的小夥伴,也被nio這個名詞搞暈了,一會兒java 的nio 不叫 非阻塞io,一會兒java nio 又是非阻塞io,到底是啥呢?
  • jvm面試系列一:java內存模型你掌握了多少?
    今天我們就來聊一聊Java內存模型,面試中面試官會通過考察你對jvm的理解更深入得了解你的水平。
  • 你真的了解java類加載器嗎?
    創建ExtClassLoader時父加載器為null雙親委託模型委託加載模型雙親委派說明:每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關係,是一個組合包含的關係),虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器
  • Java面試的的時候你被提過哪些問題?
    OOM你遇到過哪些情況,SOF你遇到過哪些情況。16. Java面向對象的三個特徵與含義。17. Override和Overload的含義去區別。18. Interface與abstract類的區別。19. Static class 與non static class的區別。20. java多態的實現原理。21.
  • 真的嗎,Java 的 JSP 已經被淘汰了?
    你要是這輩子就吃java這碗飯,就不要去研究什麼css,js等等。把你的精力專注在java,jvm原理,spring原理,mysql鎖,事務,多線程,大並發,分布式架構,微服務,以及相關的項目管理等等,這樣你的核心競爭力才會越來越高,正所謂你往生活中投入什麼,生活就會反饋給你什麼。
  • Java典型面試題 ——談談你對Java平臺的理解?
    今天我要問你的問題是,談談你對 Java 平臺的理解?「Java 是解釋執行」,這句話正確嗎?,大部分情況下,程式設計師不需要自己操心內存的分配和回收。題目本身是非常開放的,往往考察的是多個方面,比如,基礎知識理解是否很清楚;是否掌握 Java 平臺主要模塊和運行原理等。很多面試者會在這種問題上吃虧,稍微緊張了一下,不知道從何說起,就給出個很簡略的回答。對於這類籠統的問題,你需要儘量表現出自己的思維深入並系統化,Java 知識理解得也比較全面,一定要避免讓面試官覺得你是個「知其然不知其所以然」的人。
  • 就是要你懂 Java 中 volatile 關鍵字實現原理
    本文詳細解讀一下volatile關鍵字如何保證變量在多線程之間的可見性,在此之前,有必要講解一下CPU緩存的相關知識,掌握這部分知識一定會讓我們更好地理解volatile的原理,從而更好、更正確地地使用volatile關鍵字。
  • 開發崗位這麼多,為什麼選Java?你學Java了嗎-開課吧
    不用懷疑,是真的有很多人在學Java。提到C++語言,很多人發現在使用過程中最容易出現的錯誤就是內存管理,而java有自動垃圾回收器,不用擔心內存。零基礎學Java市場需求決定你的學習方向,招聘平臺上招程式設計師java佔比很高。以上是許多人選擇Java語言系統的重要原因。
  • 你應該要理解的java並發關鍵字volatile
    我們可以在這裡簡單的說一下:1、原子性(Atomicity)原子性是指在一個操作中就是cpu不可以在中途暫停然後再調度,既不被中斷操作,要不執行完成,要不就不執行,就好比你做一件事,要麼不做,要麼做完。java提供了很多機制來保證原子性。我們舉一個例子,比如說常見的a++就不滿足原子性。