簡介
java堆在java虛擬機啟動時創建,是java虛擬機所管理的內存中最大的一塊,它是被所有線程共享的一塊邏輯區域,在java虛擬機規范中,只要求其邏輯上是連續的即可,並不要求物理上的連續性(這可以結合操作系統內存管理的相關知識來理解)。java堆唯一的作用就是存儲對象實例和數組。
從內存回收角度來看,現在的虛擬機大多數采用分代收集算法,因此,java堆還可以細分為新生代和老年代,更細致一點還可以分為Eden空間, From Survivor空間及To Survivor空間。
從內存分配角度來看,線程共享的Java堆還可能分出多個線程私有的分配緩沖區(TLAB)。
java堆與對象
- 對象創建過程
- 當虛擬機遇見一個new命令時,首先簡稱new指令的參數是否可以在常量池中定位到一個相應的類的符號引用,並檢查這個類的符號引用所代表的類是否已經被加載,解析及初始化過,如果沒有則必須執行相應的類加載過程,如果有執行2.
- 確定類已經被加載之后,虛擬機將為對象分配相應的內存空間,這個內存空間的大小在類加載后即可完全確定。
- 分配內存空間的方式可以分為”指針碰撞“和”空閑列表“兩種,采用哪種方式由所采用的垃圾收集器是否帶有”壓縮整理功能“決定。
- 為保證在多線程情況下新對象創建的線程安全性,提供了兩種方案:
- 第一種是對分配內存空間的動作進行同步處理(虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性);
- 另一種是把內存分配的動作按照線程划分在不同的空間之中進行。每個線程在java堆中預先分配一小塊內存,稱為本地線程分配緩沖(TLAB)。是否采用TLAB,可使用-XX:+/-UseTLAB參數來指定。
- 內存分配之后,虛擬機會將分配的內存空間初始化為零值。
- 接下來,虛擬機會根據當前的運行狀態選擇不同的設置方式,根據對象頭,對對象進行必要的設置,例如這個對象是哪個類的實例,如何才能找到類的元數據信息,對象的哈希碼,對象的GC分代年齡等。
- 對象內存布局
- 對象頭: 對象頭又可以拆分成兩部分信息
- 存儲對象自身的運行時數據(Mark Word): 哈希碼,GC分代年齡,鎖狀態標志,線程持有的鎖,偏向線程ID,偏向時間戳等
- 類型指針:即對象指向它的類元數據的指針,虛擬機通過這個指針來確定對象是哪個類的實例。
- 當對象是數組時,對象頭中還要存儲一塊用於記錄數組長度的數據。
- 實例數據: 是對象真正存儲的有效數據,也是程序代碼段中定義的各種類型的字段內容。無論是父類,還是子類的數據都需要記錄下來。這部分的存儲順序會收到虛擬機分配策略參數及字段在java代碼中的定義順序的影響。
- 對齊填充: HotSpot VM的自動內存管理系統要求對象的起始地址必須為8字節的整數倍,即對象的內存大小必須為8的倍數。
- 對象頭: 對象頭又可以拆分成兩部分信息
- 對象的訪問定位: 在java中,我們的程序在堆上存儲對象實例,但是實際上我們要通過在虛擬機棧中的引用reference來操作這些對象。具體來說主流的訪問方式有兩種:
- 使用句柄:在內存中分出一塊區域用於存放句柄池,reference中存儲的值就是這塊句柄池中對應的句柄地址,而句柄中存放對象的實例數據(在堆中)與類型數據(在方法區中)各自的具體地址信息。
- 直接指針:reference直接指向對象地址(堆中),然后在對象實例數據中包含指向對象類型數據(方法區中)的指針。(HotSpot 采用此種方式)
java堆與垃圾收集器
對於線程私有的虛擬機棧、本地方法棧及程序計數器所占用的內存來說,他們會隨着線程的結束而被自動回收,而java堆因為是垃圾收集器的主要管理對象,常被稱為GC堆。從內存管理角度來看,java堆又可以細分為新生代和老年代,進一步可以分為Eden, From Survivor 及 To Survivor。
- 判斷對象是否存活的算法:
- 引用計數算法
- 算法思想:給對象添加一個引用計數器,每當有一個地方引用它,就將計數器加一;當引用失效計數器就減一。任何時刻當計數器為0時,就不可能再被引用。
- 優點:實現簡單,判定效率也很高
- 缺點:主流的java虛擬機沒有選用計數器算法的主要原因在於,它很難解決循環引用問題。
- 可達性計數算法: java, C#等語言所采用的判斷對象是否存活的算法。
- 算法思想: 通過一系列稱為”GC Roots"的對象作為起點,從這些節點開始向下搜索,搜索走過的路徑稱為引用鏈,當一個對象沒有任何引用鏈與GC roots相連時,則此對象是不可用對象,可被判定為可回收對象。
- 可作為GC Roots對象:
- 虛擬機棧中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區常量引用的對象
- 本地方法棧中JNI引用的對象
- 引用:
- 強引用
- 定義:Object obj = new Object();
- 特點:只要強引用還在,垃圾回收器永遠不會回收被引用的對象
- 軟引用
- 定義:使用SoftReference類來實現軟引用,軟引用對象最常用於實現內存敏感的緩存。
- 特點:在系統將要發生內存溢出異常之前,會把軟引用納入回收范圍中進行二次回收。
- 弱引用
- 定義:比軟引用更弱,WeakReference來實現,弱引用最常用於實現規范化的映射。可以使用弱引用的isEnQueued方法返回對象監控對象是否已經被垃圾回收器標記為即將回收的垃圾。
- 特點:被弱引用關聯的對象,只能生存到下一次垃圾收集發生之前。
- 虛引用
- 定義:也稱為幽靈引用或幻影引用,是最弱的一種引用關系。PhantomReference來實現。主要用於檢測對象是否已經從內存中刪除。
- 特點:一個對象時候有虛引用的存在,完全不會對其生存時間產生任何影響,也無法通過虛引用來取得一個對象實例。
- 強引用
- 引用計數算法
- 垃圾收集算法:
- 標記-清除算法:
- 復制算法: 目前商業虛擬機采用這種方法來回收新生代。
- 主要思想:在內存中按容量分為大小相等的兩塊,每次值使用其中一塊,當這一塊的內存用完后,將還存活的對象復制到另一塊,之后清空已使用過的這一塊內存。因為98%的對象都是創建之后很快失效,因此不需要兩塊相等比例的內存,進而將內存分為一塊較大的Eden區,和兩塊較小的Survivor區,每次使用Eden和一塊Survivor。當回收時將Eden和該塊Survivor的存活的對象復制到另一塊Survivor,之后清空Eden和上一塊Survivor。(Hotspot默認Eden:Survivor = 8 : 1)。
- 標記-整理算法: 復制算法在對象存活比例較高時效率將會降低,因此回收老年代所采用的算法是標記-整理算法。
- 分代收集算法: 當前商業虛擬機的垃圾回收器都是采用“分代收集”
- 根據對象存活周期的不同將內存划分為幾塊,一般是把java堆分為新生代和老年代,這樣就可以根據每個年代不同的特點選擇適當的收集方法。
- 總結:目前虛擬機均采用分代收集算法進行垃圾收集。將java對分為新生代和老年代。在新生代中,每次垃圾收集時都有大批對象已經死去,只有少量存活,故而選擇復制算法進行垃圾收集;而在老年代,因為對象存活率高,沒有額外空間對其進行分配擔保,必須使用“標記-清理”或者“標記-整理”進行回收。
- 垃圾收集器:HotSpot(有連線表示可搭配)
- Serial收集器:是一個單線程收集器,在它進行垃圾回收時,必須暫停掉所有線程。
- 缺點:由虛擬機自動發起,在用戶不可見的情況下停掉所有工作
- 優點:簡單高效,適合運行在Client模式下的虛擬機。
- ParNew收集器: Serial的多線程版本。因為它是除了Serial唯一能與CMS配合使用的收集器,是許多運行在Server模式下的虛擬機首選的新生代收集器。
- Parallel Scavenge收集器: 也是一個多線程的新生代的收集器,但是它的關注點在於達到一個可控制的吞吐量,可以自適應調節。
- Serial Old收集器:Serial的收集老年代的版本。
- Parrallel Old收集器: parallel Scavenge 的老年代版本。在注意吞吐量和CPU資源敏感的場合,均可以考慮采用Parallel Scavenge 加上Parallel Old的組合
- Cms收集器: 是一種以獲取最短回收停頓時間為目標的收集器,非常適合 應用集中在互聯網網站或者B/S系統的服務端上的 這些尤其注重響應時間的應用。基於“標記-清除”算法,大致可分為四個步驟:
- 初始標記
- 並發標記
- 重新標記
- 並發清除
- G1收集器: 是一款面向服務端應用的垃圾收集器。HotSpot希望它在未來能夠完全替換掉CMS。它有着如下特點:並行與並發,分代收集,空間整合,可預測停頓。
- 使用G1收集器時,java堆內存的布局有很大變化,它將整個java堆內存分為多個大小相等的獨立區域(Region),雖然仍舊有新生代和老年代的概念,但是兩者不再物理隔離,它們都是一部分Region的集合。
- Serial收集器:是一個單線程收集器,在它進行垃圾回收時,必須暫停掉所有線程。
- 內存分配與回收
上文涉及的重要補充知識點
常量池:
java引用:
- 軟引用SoftReference:(WeakReference與PhantomReference用法與之類似,詳細的可以看看api)
- 此類的直接實例可用於實現簡單緩存;該類或其派生的子類還可用於更大型的數據結構,以實現更復雜的緩存。只要軟引用的指示對象是強可到達對象,即正在實際使用的對象,就不會清除軟引用。例如,通過保持最近使用的項的強指示對象,並由垃圾回收器決定是否放棄剩余的項,復雜的緩存可以防止放棄最近使用的項。
-
Solution referent = new Solution(); SoftReference<Solution> s = new SoftReference<Solution>(referent); referent = null; s.get();
- 優秀的相關博客
Eden空間, From Survivor空間及To Survivor空間:
