深刻理解JVM線程模型

一、 jvm內存模型
在描述jvm線程模型以前,咱們先深刻的理解下,jvm內存模型。在jvm1.8以前,jvm的邏輯結構和物理結構是對應的。即Jvm在初始化的時候,會爲堆(heap),棧(stack),元數據區(matespace)分配指定的內存大小,Jvm線程啓動的時候會向服務器申請指定的內存地址空間進行分配。在jdk1.8以後,使用了G1垃圾回收器,邏輯上依然存在堆,棧,元數據區。可是在物理結構上,G1採用了分區(Region)的思路,將整個堆空間分紅若干個大小相等的內存區域,每次分配對象空間將逐段地使用內存。所以,在堆的使用上,G1並不要求對象的存儲必定是物理上連續的,只要邏輯上連續便可;每一個分區也不會肯定地爲某個代服務,能夠按需在年輕代和老年代之間切換。啓動時能夠經過參數-XX:G1HeapRegionSize=n可指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分爲2048個分區。java

1.一、 jvm內存模型介紹編程

1.二、 Jvm堆
Java堆是和Java應用程序最密切的內存空間,幾乎全部的對象都放到堆中。而且堆徹底由Jvm管理,經過垃圾回收機制,垃圾對象會被自動清理,而不需顯式的釋放。
根據垃圾回收機制的不一樣,Java堆一般被分爲如下的集中不一樣的結構。數組

構成 描述
New Generation  由 Eden + Survivor (From Space + To Space)組成
Eden 因此的new出來的新對象都存放到Eden區
Survivor Space  Eden每次垃圾清理事後,任然沒又被清理的對象,會轉移到交換區中
Old Generation 在交換區中未被清理的對象(默認清理18次標記),將轉移到老年代。


1.三、 Jvm棧
Java棧是一塊線程私有的內存空間,Java棧和線程執行密切相關。線程的執行基本單位就是函數調用,每次函數調用的數據就會經過Java棧傳遞。
  Java棧與數據結構上的棧有着相似的含義,它是一塊先進後出的數據結構,只支持出棧和入棧的兩種操做。在Java棧中保存的主要內容爲棧幀。每次調用一個函數,都會有一個對應的棧幀被壓入Java棧。每個函數調用結束,都會有一個棧幀被彈出Java棧。例如:緩存

如圖所示,每次調用一個函數都會被當作棧幀壓入到棧中。其中每個棧幀對應一個函數。因爲每次調用函數都會生成一個棧幀,從而佔用必定的棧空間。若是線程中存在大量的遞歸操做,會頻繁的壓棧,致使棧的深刻過於深刻,當棧的空間被消耗殆盡的時候,會拋出StackOverflowError棧溢出錯誤。
當函數執行結束返回時,棧幀從Java棧中被彈出。Java方法有兩種返回的方式,一種是正常函數返回,即便用 return; 另一種是拋出異常。無論哪一種方式,都會致使棧幀被彈出。安全

1.3.一、 局部變量表
局部變量表示棧幀的重要組成部分之一。它用於保存函數已經局部變量。局部變量表中的變量只有在當前的函數中調用有效,當調用函數結束之後,隨着函數棧幀的銷燬,局部變量表也隨之銷燬。服務器

1.3.二、 操做數棧
操做數棧也是棧幀中重要的內容之一,它主要保存計算過程當中的結果,同事做爲計算過程臨時變量的存儲空間。
操做數棧也是一個先進後出的數據結構,只支持入棧和出棧的兩種操做,Java的不少字節碼指令都是經過操做數棧進行參數傳遞的。好比iadd指令,它就會在操做數棧中彈出兩個整數進行加法計算,計算結果會被入棧。入下圖所示:數據結構

1.3.三、 幀數據區
每一個棧幀都包含一個指向運行時常量池中該棧幀所屬性方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。在Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化爲直接引用,這種轉化稱爲靜態解析。另一部分將在每一次的運行期期間轉化爲直接引用,這部分稱爲動態鏈接併發

1.四、 Jvm方法區(jdk1.8元數據區)
它主要存放一些虛擬機加載的類信息,常量,靜態變量,即便編譯器後的代碼等數據。根據Java虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。jvm

1.4.一、 運行時常量池
運行時常量區是方法區的一部分。用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。還會有一些符號引用轉換的直接引用一保存在運行時常量池中。
運行時常量池具有動態性,也就是運行期間也能夠將新的常量放入池中,例如String.intern()方法。當常量池沒法再申請到內存時,會拋出OutOfMemoryError異常函數

二、 jvm線程模型
經過Jvm內存模型,咱們能夠發現,Jvm其實就是操做系統的一種鏡像。是軟件層次的虛擬機。其中咱們隊Jvm內存模型分析可知:堆,方法區是線程共有的;棧是每一個線程私有的。
討論Java內存模型和線程以前,先簡單介紹一下硬件的效率與一致性

因爲計算機的存儲設備與處理器的運算能力之間有幾個數量級的差距,因此現代計算機系統都不得不加入一層讀寫速度儘量接近處理器運算速度的高速緩存(cache)來做爲內存與處理器之間的緩衝:將運算須要使用到的數據複製到緩存中,讓運算能快速進行,當運算結束後再從緩存同步回內存之中沒這樣處理器就無需等待緩慢的內存讀寫了。
  
基於高速緩存的存儲交互很好地解決了處理器與內存的速度矛盾,可是引入了一個新的問題:緩存一致性(Cache Coherence)。在多處理器系統中,每一個處理器都有本身的高速緩存,而他們又共享同一主存,以下圖所示:多個處理器運算任務都涉及同一塊主存,須要一種協議能夠保障數據的一致性,這類協議有MSI、MESI、MOSI及Dragon Protocol等。Java虛擬機內存模型中定義的內存訪問操做與硬件的緩存訪問操做是具備可比性的,後續將介紹Java內存模型。
  

  除此以外,爲了使得處理器內部的運算單元能竟可能被充分利用,處理器可能會對輸入代碼進行亂起執行(Out-Of-Order Execution)優化,處理器會在計算以後將對亂序執行的代碼進行結果重組,保證結果準確性。與處理器的亂序執行優化相似,Java虛擬機的即時編譯器中也有相似的指令重排序(Instruction Recorder)優化。

2.一、 Java內存模型
定義Java內存模型並非一件容易的事情,這個模型必須定義得足夠嚴謹,才能讓Java的併發操做不會產生歧義;可是,也必須得足夠寬鬆,使得虛擬機的實現能有足夠的自由空間去利用硬件的各類特性(寄存器、高速緩存等)來獲取更好的執行速度。通過長時間的驗證和修補,在JDK1.5發佈後,Java內存模型就已經成熟和完善起來了。

2.1.一、 主內存與工做內存
Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣底層細節。此處的變量與Java編程時所說的變量不同,指包括了實例字段、靜態字段和構成數組對象的元素,可是不包括局部變量與方法參數,後者是線程私有的,不會被共享。
  Java內存模型中規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存(能夠與前面將的處理器的高速緩存類比),線程的工做內存中保存了該線程使用到的變量到主內存副本拷貝,線程對變量的全部操做(讀取、賦值)都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣線程之間沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要在主內存來完成,線程、主內存和工做內存的交互關係以下圖所示,和上圖很相似
  

2.1.二、 java內存變量的交互操做
主內存變量和工做內存變量之間的交互過程以下:
 

三、 線程安全
經過上面分析,能夠發現,每一個線程都擁有本身的工做內存,工做內存是線程私有的。因此每一個線程對堆中的共享變量進行修改對其餘的線程而言是不可見的。
  Java內存模型中,程序(進程)擁有一塊內存空間,能夠被全部的線程共享,即MainMemory(主內存:堆);而每一個線程又有一塊獨立的內存空間,即WorkingMemory(工做內存:棧)。普通狀況下,當線程須要對某一共享變量進行修改時,一般會進行以下的過程:
1.從主內存中拷貝變量的一份副本,並裝載到工做內存中;
2.在工做內存中執行代碼,修改副本的值;
3.用工做內存中的副本值更新主存中的相關變量值。
  所謂「線程安全」,即多個線程同時執行同一段代碼時,不會出現不肯定的或者與單線程條件下不一致的結果。一般,下列三種條件居其一的併發訪問被JVM認爲是線程安全的:

有final關鍵字修飾且已被賦值;有volatile關鍵字修飾;有鎖保護(synchronized、ReentrantLock等)。

相關文章
相關標籤/搜索