自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

誰說深入淺出虛擬機難?現(xiàn)在我讓他通俗易懂(JVM)

云計算 虛擬化
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規(guī)范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。

 [[328100]]

 1:什么是JVM

大家可以想想,JVM 是什么?JVM是用來干什么的?在這里我列出了三個概念,第一個是JVM,第二個是JDK,第三個是JRE。相信大家對這三個不會很陌生,相信你們都用過,但是,你們對這三個概念有清晰的知道么?我不知道你們會不會,知不知道。接下來你們看看我對JVM的理解。

(1):JVM

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規(guī)范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。

引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯。

Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,

使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),

就可以在多種平臺上不加修改地運行。

Java虛擬機在執(zhí)行字節(jié)碼時,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。

這就是Java的能夠“一次編譯,到處運行”的原因。

(2):JDK

JDK(Java Development Kit) 是 Java 語言的軟件開發(fā)工具包(SDK)。

JDK包含的基本組件包括:

  1. javac – 編譯器,將源程序轉成字節(jié)碼
  2. jar – 打包工具,將相關的類文件打包成一個文件
  3. javadoc – 文檔生成器,從源碼注釋中提取文檔
  4. jdb – debugger,查錯工具
  5. java – 運行編譯后的java程序(.class后綴的)
  6. appletviewer:小程序瀏覽器,一種執(zhí)行HTML文件上的Java小程序的Java瀏覽器。
  7. Javah:產(chǎn)生可以調(diào)用Java過程的C過程,或建立能被Java程序調(diào)用的C過程的頭文件。
  8. Javap:Java反匯編器,顯示編譯類文件中的可訪問功能和數(shù)據(jù),同時顯示字節(jié)代碼含義。
  9. Jconsole: Java進行系統(tǒng)調(diào)試和監(jiān)控的工具

(3):JRE

JRE(Java Runtime Environment,Java運行環(huán)境),運行JAVA程序所必須的環(huán)境的集合,包含JVM標準實現(xiàn)及Java核心類庫。

包括兩部分:

Java Runtime Environment:

  • 是可以在其上運行、測試和傳輸應用程序的Java平臺。
  • 它包括Java虛擬機(jvm)、Java核心類庫和支持文件。
  • 它不包含開發(fā)工具(JDK)--編譯器、調(diào)試器和其它工具。
  • JRE需要輔助軟件--Java Plug-in--以便在瀏覽器中運行applet。

Java Plug-in。

允許Java Applet和JavaBean組件在使用Sun的Java Runtime Environment(JRE)的瀏覽器中運行,

而不是在使用缺省的Java運行環(huán)境的瀏覽器中運行。

Java Plug-in可用于Netscape Navigator和Microsoft Internet Explorer。

2:JVM運行時數(shù)據(jù)區(qū)

Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,已經(jīng)創(chuàng)建和銷毀時間,有的區(qū)域隨著虛擬機進程的啟動而創(chuàng)建,有些區(qū)域則依賴用戶線程的啟動和結束而創(chuàng)建和銷毀。根據(jù)《Java虛擬機規(guī)范(Java SE 7)》的規(guī)定,Java虛擬機所管理的內(nèi)存將會包括以下幾個運行時數(shù)據(jù)區(qū)域,如下圖所示:

 

2.1、程序計數(shù)器

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

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

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

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

程序計數(shù)器是線程私有的,它的生命周期與線程相同(隨線程而生,隨線程而滅)。

2.2、Java虛擬機棧

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

在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常情況:

如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;

如果虛擬機??梢詣討B(tài)擴展(當前大部分的Java虛擬機都可以擴展),如果擴展時無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError異常。

與程序寄存器一樣,java虛擬機棧也是線程私有的,它的生命周期與線程相同。

2.3、本地方法棧

本地方法棧(Native Method Stack)與虛擬機棧所發(fā)揮的作用是非常類似,它們之間的區(qū)別在于虛擬機棧為虛擬機執(zhí)行Java方法服務,而本地方法棧則是為虛擬機使用到的Native方法服務。在虛擬機規(guī)范中對本地方法棧中方法使用的語言、使用方式與數(shù)據(jù)結構并沒有強制規(guī)定,因此具體的虛擬機可以自由的實現(xiàn)它。

與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。

與虛擬機棧一樣,本地方法棧也是線程私有的。

2.4、Java 堆(Java Heap)

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

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

根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實現(xiàn)時,既可以實現(xiàn)成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xms和-Xmx控制)。如果在堆中沒有內(nèi)存完成實例的分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。

2.5、方法區(qū)(Method Area)

方法區(qū)(Method Area)和Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存放已被虛擬機加載的類信息、常量、靜態(tài)變量、JIT編譯后的代碼等數(shù)據(jù)。方法區(qū)在虛擬機啟動的時候創(chuàng)建。

Java虛擬機規(guī)范對方法區(qū)的限制非常寬松,除了和堆一樣不需要不連續(xù)的內(nèi)存空間和可以固定大小或者可擴展外,還可以選擇不實現(xiàn)垃圾收集。

根據(jù)Java虛擬機規(guī)范的規(guī)定,如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配需要時,將拋出OutOfMemoryError異常。

2.6、運行時常量池

運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放。

2.7、直接內(nèi)存

直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁使用,而且也可能導致OutOfMemoryError異常出現(xiàn)。

在JDK 1.4 中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方法,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數(shù)據(jù)。

到這里我們大致知道了Java虛擬機的運行時區(qū)的概況,接下來會繼續(xù)介紹更多JVM相關信息。

 

3:JVM內(nèi)存模型

 

Java內(nèi)存模型即Java Memory Model,簡稱JMM。JMM定義了Java 虛擬機(JVM)在計算機內(nèi)存(RAM)中的工作方式。JVM是整個計算機虛擬模型,所以JMM是隸屬于JVM的。

如果我們要想深入了解Java并發(fā)編程,就要先理解好Java內(nèi)存模型。Java內(nèi)存模型定義了多線程之間共享變量的可見性以及如何在需要的時候?qū)蚕碜兞窟M行同步。原始的Java內(nèi)存模型效率并不是很理想,因此Java1.5版本對其進行了重構,現(xiàn)在的Java8仍沿用了Java1.5的版本。

關于并發(fā)編程

在并發(fā)編程領域,有兩個關鍵問題:線程之間的通信和同步。

線程之間的通信

線程的通信是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通信機制有兩種共享內(nèi)存和消息傳遞。

在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài),線程之間通過寫-讀內(nèi)存中的公共狀態(tài)來隱式進行通信,典型的共享內(nèi)存通信方式就是通過共享對象進行通信。

在消息傳遞的并發(fā)模型里,線程之間沒有公共狀態(tài),線程之間必須通過明確的發(fā)送消息來顯式進行通信,在java中典型的消息傳遞方式就是wait()和notify()。

關于Java線程之間的通信,可以參考線程之間的通信(thread signal)。

線程之間的同步

同步是指程序用于控制不同線程之間操作發(fā)生相對順序的機制。

在共享內(nèi)存并發(fā)模型里,同步是顯式進行的。程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執(zhí)行。

在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接收之前,因此同步是隱式進行的。

Java的并發(fā)采用的是共享內(nèi)存模型

Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制,很可能會遇到各種奇怪的內(nèi)存可見性問題。

Java內(nèi)存模型

上面講到了Java線程之間的通信采用的是過共享內(nèi)存模型,這里提到的共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。

 

從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:

1. 首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。

2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。

下面通過示意圖來說明這兩個步驟:

 

如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本。假設初始時,這三個內(nèi)存中的x值都為0。線程A在執(zhí)行時,把更新后的x值(假設值為1)臨時存放在自己的本地內(nèi)存A中。當線程A和線程B需要通信時,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時主內(nèi)存中的x值變?yōu)榱?。隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時線程B的本地內(nèi)存的x值也變?yōu)榱?。

從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序員提供內(nèi)存可見性保證。

上面也說到了,Java內(nèi)存模型只是一個抽象概念,那么它在Java中具體是怎么工作的呢?為了更好的理解上Java內(nèi)存模型工作方式,下面就JVM對Java內(nèi)存模型的實現(xiàn)、硬件內(nèi)存模型及它們之間的橋接做詳細介紹。

JVM對Java內(nèi)存模型的實現(xiàn)

在JVM內(nèi)部,Java內(nèi)存模型把內(nèi)存分成了兩部分:線程棧區(qū)和堆區(qū),下圖展示了Java內(nèi)存模型在JVM中的邏輯視圖:

 

JVM中運行的每個線程都擁有自己的線程棧,線程棧包含了當前線程執(zhí)行的方法調(diào)用相關信息,我們也把它稱作調(diào)用棧。隨著代碼的不斷執(zhí)行,調(diào)用棧會不斷變化。

線程棧還包含了當前方法的所有本地變量信息。一個線程只能讀取自己的線程棧,也就是說,線程中的本地變量對其它線程是不可見的。即使兩個線程執(zhí)行的是同一段代碼,它們也會各自在自己的線程棧中創(chuàng)建本地變量,因此,每個線程中的本地變量都會有自己的版本。

所有原始類型(boolean,byte,short,char,int,long,float,double)的本地變量都直接保存在線程棧當中,對于它們的值各個線程之間都是獨立的。對于原始類型的本地變量,一個線程可以傳遞一個副本給另一個線程,當它們之間是無法共享的。

堆區(qū)包含了Java應用創(chuàng)建的所有對象信息,不管對象是哪個線程創(chuàng)建的,其中的對象包括原始類型的封裝類(如Byte、Integer、Long等等)。不管對象是屬于一個成員變量還是方法中的本地變量,它都會被存儲在堆區(qū)。

下圖展示了調(diào)用棧和本地變量都存儲在棧區(qū),對象都存儲在堆區(qū):

 

一個本地變量如果是原始類型,那么它會被完全存儲到棧區(qū)。

一個本地變量也有可能是一個對象的引用,這種情況下,這個本地引用會被存儲到棧中,但是對象本身仍然存儲在堆區(qū)。

對于一個對象的成員方法,這些方法中包含本地變量,仍需要存儲在棧區(qū),即使它們所屬的對象在堆區(qū)。

對于一個對象的成員變量,不管它是原始類型還是包裝類型,都會被存儲到堆區(qū)。

Static類型的變量以及類本身相關信息都會隨著類本身存儲在堆區(qū)。

堆中的對象可以被多線程共享。如果一個線程獲得一個對象的應用,它便可訪問這個對象的成員變量。如果兩個線程同時調(diào)用了同一個對象的同一個方法,那么這兩個線程便可同時訪問這個對象的成員變量,但是對于本地變量,每個線程都會拷貝一份到自己的線程棧中。

下圖展示了上面描述的過程:

 

硬件內(nèi)存架構

不管是什么內(nèi)存模型,最終還是運行在計算機硬件上的,所以我們有必要了解計算機硬件內(nèi)存架構,下圖就簡單描述了當代計算機硬件內(nèi)存架構:

 

現(xiàn)代計算機一般都有2個以上CPU,而且每個CPU還有可能包含多個核心。因此,如果我們的應用是多線程的話,這些線程可能會在各個CPU核心中并行運行。

在CPU內(nèi)部有一組CPU寄存器,也就是CPU的儲存器。CPU操作寄存器的速度要比操作計算機主存快的多。在主存和CPU寄存器之間還存在一個CPU緩存,CPU操作CPU緩存的速度快于主存但慢于CPU寄存器。某些CPU可能有多個緩存層(一級緩存和二級緩存)。計算機的主存也稱作RAM,所有的CPU都能夠訪問主存,而且主存比上面提到的緩存和寄存器大很多。

當一個CPU需要訪問主存時,會先讀取一部分主存數(shù)據(jù)到CPU緩存,進而在讀取CPU緩存到寄存器。當CPU需要寫數(shù)據(jù)到主存時,同樣會先flush寄存器到CPU緩存,然后再在某些節(jié)點把緩存數(shù)據(jù)flush到主存。

Java內(nèi)存模型和硬件架構之間的橋接

正如上面講到的,Java內(nèi)存模型和硬件內(nèi)存架構并不一致。硬件內(nèi)存架構中并沒有區(qū)分棧和堆,從硬件上看,不管是棧還是堆,大部分數(shù)據(jù)都會存到主存中,當然一部分棧和堆的數(shù)據(jù)也有可能會存到CPU寄存器中,如下圖所示,Java內(nèi)存模型和計算機硬件內(nèi)存架構是一個交叉關系:

 

當對象和變量存儲到計算機的各個內(nèi)存區(qū)域時,必然會面臨一些問題,其中最主要的兩個問題是:

1. 共享對象對各個線程的可見性

2. 共享對象的競爭現(xiàn)象123

共享對象的可見性

當多個線程同時操作同一個共享對象時,如果沒有合理的使用volatile和synchronization關鍵字,一個線程對共享對象的更新有可能導致其它線程不可見。

想象一下我們的共享對象存儲在主存,一個CPU中的線程讀取主存數(shù)據(jù)到CPU緩存,然后對共享對象做了更改,但CPU緩存中的更改后的對象還沒有flush到主存,此時線程對共享對象的更改對其它CPU中的線程是不可見的。最終就是每個線程最終都會拷貝共享對象,而且拷貝的對象位于不同的CPU緩存中。

下圖展示了上面描述的過程。左邊CPU中運行的線程從主存中拷貝共享對象obj到它的CPU緩存,把對象obj的count變量改為2。但這個變更對運行在右邊CPU中的線程不可見,因為這個更改還沒有flush到主存中:

 

要解決共享對象可見性這個問題,我們可以使用java volatile關鍵字。 Java’s volatile keyword. volatile 關鍵字可以保證變量會直接從主存讀取,而對變量的更新也會直接寫到主存。volatile原理是基于CPU內(nèi)存屏障指令實現(xiàn)的,后面會講到。

競爭現(xiàn)象

如果多個線程共享一個對象,如果它們同時修改這個共享對象,這就產(chǎn)生了競爭現(xiàn)象。

如下圖所示,線程A和線程B共享一個對象obj。假設線程A從主存讀取Obj.count變量到自己的CPU緩存,同時,線程B也讀取了Obj.count變量到它的CPU緩存,并且這兩個線程都對Obj.count做了加1操作。此時,Obj.count加1操作被執(zhí)行了兩次,不過都在不同的CPU緩存中。

如果這兩個加1操作是串行執(zhí)行的,那么Obj.count變量便會在原始值上加2,最終主存中的Obj.count的值會是3。然而下圖中兩個加1操作是并行的,不管是線程A還是線程B先flush計算結果到主存,最終主存中的Obj.count只會增加1次變成2,盡管一共有兩次加1操作。

 

要解決上面的問題我們可以使用java synchronized代碼塊。synchronized代碼塊可以保證同一個時刻只能有一個線程進入代碼競爭區(qū),synchronized代碼塊也能保證代碼塊中所有變量都將會從主存中讀,當線程退出代碼塊時,對所有變量的更新將會flush到主存,不管這些變量是不是volatile類型的。

volatile和 synchronized區(qū)別

  1. volatile本質(zhì)是在告訴jvm當前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
  2. volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
  3. volatile僅能實現(xiàn)變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
  4. volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
  5. volatile標記的變量不會被編譯器優(yōu)化;synchronized標記的變量可以被編譯器優(yōu)化

支撐Java內(nèi)存模型的基礎原理

指令重排序

在執(zhí)行程序時,為了提高性能,編譯器和處理器會對指令做重排序。但是,JMM確保在不同的編譯器和不同的處理器平臺之上,通過插入特定類型的Memory Barrier來禁止特定類型的編譯器重排序和處理器重排序,為上層提供一致的內(nèi)存可見性保證。

  1. 編譯器優(yōu)化重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
  2. 指令級并行的重排序:如果不存l在數(shù)據(jù)依賴性,處理器可以改變語句對應機器指令的執(zhí)行順序。
  3. 內(nèi)存系統(tǒng)的重排序:處理器使用緩存和讀寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

數(shù)據(jù)依賴性

如果兩個操作訪問同一個變量,其中一個為寫操作,此時這兩個操作之間存在數(shù)據(jù)依賴性。

編譯器和處理器不會改變存在數(shù)據(jù)依賴性關系的兩個操作的執(zhí)行順序,即不會重排序。

as-if-serial

不管怎么重排序,單線程下的執(zhí)行結果不能被改變,編譯器、runtime和處理器都必須遵守as-if-serial語義。

內(nèi)存屏障(Memory Barrier )

上面講到了,通過內(nèi)存屏障可以禁止特定類型處理器的重排序,從而讓程序按我們預想的流程去執(zhí)行。內(nèi)存屏障,又稱內(nèi)存柵欄,是一個CPU指令,基本上它是一條這樣的指令:

  1. 保證特定操作的執(zhí)行順序。
  2. 影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結果)的內(nèi)存可見性。

編譯器和CPU能夠重排序指令,保證最終相同的結果,嘗試優(yōu)化性能。插入一條Memory Barrier會告訴編譯器和CPU:不管什么指令都不能和這條Memory Barrier指令重排序。

Memory Barrier所做的另外一件事是強制刷出各種CPU cache,如一個Write-Barrier(寫入屏障)將刷出所有在Barrier之前寫入 cache 的數(shù)據(jù),因此,任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本。

這和java有什么關系?上面java內(nèi)存模型中講到的volatile是基于Memory Barrier實現(xiàn)的。

如果一個變量是volatile修飾的,JMM會在寫入這個字段之后插進一個Write-Barrier指令,并在讀這個字段之前插入一個Read-Barrier指令。這意味著,如果寫入一個volatile變量,就可以保證:

  1. 一個線程寫入變量a后,任何線程訪問該變量都會拿到最新值。
  2. 在寫入變量a之前的寫入操作,其更新的數(shù)據(jù)對于其他線程也是可見的。因為Memory Barrier會刷出cache中的所有先前的寫入。

happens-before

從jdk5開始,java使用新的JSR-133內(nèi)存模型,基于happens-before的概念來闡述操作之間的內(nèi)存可見性。

在JMM中,如果一個操作的執(zhí)行結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系,這個的兩個操作既可以在同一個線程,也可以在不同的兩個線程中。

與程序員密切相關的happens-before規(guī)則如下:

  1. 程序順序規(guī)則:一個線程中的每個操作,happens-before于該線程中任意的后續(xù)操作。
  2. 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖操作,happens-before于隨后對這個鎖的加鎖操作。
  3. volatile域規(guī)則:對一個volatile域的寫操作,happens-before于任意線程后續(xù)對這個volatile域的讀。
  4. 傳遞性規(guī)則:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

注意:兩個操作之間具有happens-before關系,并不意味前一個操作必須要在后一個操作之前執(zhí)行!僅僅要求前一個操作的執(zhí)行結果,對于后一個操作是可見的,且前一個操作按順序排在后一個操作之前。

 

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2021-10-05 20:29:55

JVM垃圾回收器

2023-05-05 18:33:15

2011-07-04 10:39:57

Web

2019-02-13 16:22:53

網(wǎng)絡虛擬化大二層

2021-03-16 08:54:35

AQSAbstractQueJava

2019-10-10 16:25:02

JVM數(shù)據(jù)多線程

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構調(diào)度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2022-05-06 07:19:11

DOMDiff算法

2009-11-17 17:31:58

Oracle COMM

2021-07-19 11:54:15

MySQL優(yōu)先隊列

2023-12-04 13:22:00

JavaScript異步編程

2010-07-26 12:57:12

OPhone游戲開發(fā)

2016-10-14 13:53:05

JavascriptDOMWeb

2016-10-14 14:32:58

JavascriptDOMWeb

2010-07-16 09:11:40

JavaScript內(nèi)存泄漏

2024-01-09 12:05:24

SSH協(xié)議端口
點贊
收藏

51CTO技術棧公眾號