標籤:java 記憶體
JVM管理的記憶體
- 程式計數器(PC):每條線程都有獨立的PC
- JVM棧(Java棧):描述的是Java方法執行的記憶體模型,每個方法被執行的時候會建立一個棧幀用於儲存局部變數表、操作棧、動態連結、方法出口等資訊,也是線程私人的。
- 本地方法棧:與Native方法相關
- Java堆:線程共用的記憶體位址空間,用於存放對象執行個體,也是GC管理的主要區域。
- 直接記憶體:與Native堆相關
- 方法區:儲存已被JVM載入的類資訊、常量、靜態變數等資料,這個地區的記憶體回收目標主要針對常量池的回收和類的卸載。方法區也是線程共用的記憶體地區,雖然被描述為堆的一個邏輯部分,但別名為非堆(Non-Heap)。
- 運行時常量池:方法區的一部分,常量池存放編輯期產生的各種字面量和符號引用,當類載入後則存放到方法區的運行時常量池。
記憶體配置規則
- 對象優先在Eden區分配,Minor GC(新生代GC)後對象不足以放進Survivor區則進入老年代。
- 大對象直接進入老年代
- 長期存活的對象講進入老年代:通過年齡計數器來實現。
- Survivor同齡對象大小之和大於空間一半,大於這個年齡的對象進入老年代。
- 判斷晉陞老年代對象是否大於老年代所剩空間,是則進行Full GC(老年代GC)。
GC回收相關
PC、Java棧、本地方法棧隨線程和方法的生命週期,因此記憶體配置與回收具有確定性。
Java堆和方法區只有在運行期間才能知道需要建立哪裡對象,這部分記憶體配置和回收是動態,GC關注這部分的記憶體。通過根追溯(Root Tracing)法,即一條引用鏈來判斷對象存活,來確定是否回收對象(引用計數法不能解決對象相互引用等問題)。方法區主要回收廢棄常量和無用的類。
記憶體回收演算法:
- 標記清除演算法(Mark-Sweep):回收後會造成記憶體不連續
- 複製演算法(Copying):將記憶體分為一塊較大的Eden空間和兩塊較小Survivor空間,將Eden和其中一個Survivor存活對象複製到另外一塊Survivor空間,最後回收前面那兩個空間。當Survivor空間不夠時,由老年代空間進行分配擔保。新生代對象中大部分都是朝生夕死的,所以常採用這種演算法來回收新生代。
- 標記整理演算法(Mark-Compact):將所有存活對象移向一端,直接清理其他端邊界以外的對象。
- 分代收集:根據對象的存活周期將不同的記憶體劃分為幾塊,一般分為新生代(Young)和老年代(Old),對不同年代採取不同回收演算法。例如,對新生代採取複製演算法,對老年代採取標記清除或標記整理回收演算法。
一個虛擬機器可以有多個不同的GC,如HotSpot就有7種作用於不同年代的GC,另外GC關注的線程數目(單線程還是多線程)也可能是不同的。
Java記憶體模型
JVM試圖定義一種記憶體模型來屏蔽各種硬體和作業系統間記憶體訪問差異,以實現在各個平台都能達到一致的並發效果。
記憶體間的相互操作:
- lock
- unlock
- read
- load
- use
- assign
- store
- write
當一個變數被定義為volatile之後具備兩種特效:可視性和禁止重排序。可視性的意思可以粗略理解為,雖然進行了原子操作,但是並不保證另外的線程看到的是最新修改後的值,可視性則保證最新值已經重新整理到了主記憶體。重排序的意思是:不能保證變數賦值操作的順序與代碼的執行順序一致,如果在本線程內觀察,所有的操作都是有序的,即線程內有串列的語義;如果在一個線程觀察另外一個線程,由於指令重排序,操作可能是無序的。
參考:
《深入理解Java虛擬機器》
http://www.th7.cn/Program/java/201409/276272.shtml
Java:記憶體相關認識