1.JVM運行視角
程序計數器
Java虛擬機棧
本地方法棧
Java堆
方法區
?
? ? ? ? 1?.程序計數器
? ? ??? 程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的行號指示器。這個計數器記錄的是正在執行的虛擬機字節碼指令的地址。此內存區域是唯一一個在JAVA虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
? ? ? ? 2.Java虛擬機棧
? ? ? ??與程序計數器一樣,Java虛擬機棧也是線程私有的。Java虛擬機棧是描述Java方法運行過程的內存模型。Java虛擬機棧會為每個方法在執行的同時都會創建一個棧幀用于存儲局部變量(存放基本數據類型變量,引用類型的變量,返回類型的變量),操作數棧,動態鏈接,方法出口信息。
? ? ? ? 3.本地方法棧
? ? ? ??本地方法棧與虛擬機所發揮的作用是非常相似的(HotSpot虛擬機中,直接就把本地方法棧和虛擬機棧合二為一),它們的區別不過是虛擬機執行Java方法服務,而本地方法棧則為虛擬機使用到的Native方法服務。本地方法被執行的時候,在本地方法棧也會創建一個幀棧,用于存放該本地方法的局部變量表、操作數棧,動作鏈接,出口信息。方法執行完畢后相應的棧幀也會出棧并釋放內存空間。
? ? ? ?4.Java堆
? ? ???堆是用來存放內存對象的,是Java虛擬機所管理的內存中最大的一塊。所有的對象實例以及數組都要在堆上進行分配。
? ? ? 5.方法區
? ? ???方法區育Java堆一樣,是一個線程共享的內存區域。它用于存儲已被虛擬機加載的類信息,常量,靜態變量、即時編譯器后的代碼等數據。雖然Java虛擬機規范把方法區描述為難的一個邏輯部分,但是它卻有個別名叫做Non-Heap(非堆),目的是為了和Java堆區分開來。
? ? ? 6.直接內存
? ? ??直接內存并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域。但是這部分內存也被頻繁的使用。在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道和緩沖的IO方式。它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復復制數據。直接內存的大小不受Java虛擬機控制,但是當本機物理內存不足時就會拋出OutOfMemoryErrot錯誤。
圖二 線程共享與獨有的數據區
思考問題:為什么局部變量是線程安全的?答案見文末最后
2.JVM內存功能視角
從JVM內存可以分為三部分
Heap區(堆內存)
非Heap(非堆內存)
其他區
圖三JVM內存功能分區
Heap區:Eden Space(伊甸園),Survivor Space(幸存者區),Tenured Gen(老年代-養老區)
非Heap區:Code Cache(代碼緩沖區),Perm Gen(永久代),Java虛擬機棧,本地方法棧。
其他區:直接內存
3.線程運行視角
圖片四線程,工作內存和主內存之間的關系
Java內存模型規定了所有的變量都要存儲在主內存中,但是每個線程都有自己的工作內存,線程的工作內存保存了該線程使用的變量。這些變量實際上是主內存的副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不是直接讀取主內存的變量。】
思考問題:volatile變量?答案見最后
4.垃圾回收視角
JVM的垃圾回收主要針對的是堆內存。在垃圾回收過程,堆內存有以下特點:
堆內存劃分為新生代和老年代兩部分,新生代主要用于存放新創建的對象與存活時長小的對象,老年代則用來存放存活時間長的對象
新生代又進一步劃分為E,S1,S2三個區,其中,E代表Eden區;S1,S2則代表兩個類似的Survior。minorGC時候,Eden區不能被會回收的對象被放入到空的survior,Eden則肯定會被清空。另一個surivor里不能被GC回收的對象也會被放入這個surivor,始終保證一個surivor是空的
一個對象被minorGC回收了N次沒有被回收掉,則會被移除到老年區里(該次數通過設置-XX:InitialTenuringThresHold)
當老年代的空間被耗盡了,則觸發FullGC
?問題解答:
1.為什么局部變量是線程安全的?
JVM在執行Java程序時,會根據其數據用途把內存劃分為若干數據區域,包括方法區,堆,棧(JVM,本地方法棧),程序計數器,其中前兩者是所有線程共有的,后兩者是每個線程獨有的,因此,棧是線程私有的,一個線程一個棧,并且棧由棧幀組成,棧幀保存一個方法的局部變量表(包括參數和局部變量),操作數棧,常量池指針等,每一次方法的調用實際上是創建一個幀棧,并且壓棧。
所以方法調用實際是幀棧在入棧和出棧的操作,因為棧是線程私有的,所以每個棧之間是獨立的,所以幀棧對于多個線程棧來說不存在共享問題,也就不會存在線程安全的問題了
2.Volatile變量
根據JVM規范的規定,volatile變量依然有工作內存的拷貝,但是由于它特殊的操作順序規定,所有看起來如同直接在主內存中讀寫。
如果對申明了volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在工作所在內存的數據寫回到主內存,但是,就算寫回到主內存,如果其他線程工作內存的值還是舊的,再執行計算操作就會有問題,所以,在多處理器下為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,,每個處理器通過嗅探在總棧上傳播的數據來檢查自己緩存的值是不是過期了。當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器對這個數據進行修改操作的時候,會重新從主內存中把數據讀到處理器緩存中。