1. 程式人生 > >Java記憶體分配及垃圾回收機制

Java記憶體分配及垃圾回收機制

Java記憶體區域

1、記憶體區域

  • jvm執行時資料區域

    • 程式計數器
    • Java虛擬機器棧
    • 本地方法棧
    • 方法區
    • Java堆
  • 大圖
    jvm2.png

2、概念解釋

  • 程式計數器
      執行緒私有的一塊很小的記憶體空間,它是當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。每個執行緒都對應一個獨立的程式計數器, 記錄著執行緒執行指令,保障了執行緒間的切換後能恢復到正確的執行位置,從而保障了Java虛擬機器多執行緒能有條不紊地輪流切換執行。

  • Java虛擬機器棧
      執行緒私有

    , 它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。

  • 本地方法棧
      為虛擬機器使用到的native方法服務, 其作用類似於Java虛擬機器棧, 只不過虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務。

  • 方法區
      各個執行緒共享的記憶體區域, 用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。方法區也是我們通常所說的永久區,它的大小可通過引數-XX:PermSize、-XX:MaxPermSize進行設定

  • Java堆
      Java堆是Java虛擬機器所管理的記憶體中最大的一塊, 被所有執行緒共享的一塊記憶體區域, 在虛擬機器啟動時建立,是儲存Java物件例項的地方。Java堆細分為:新生代和老年代,而新生代又可細分為Eden空間、from survivor空間、to survivor空間等。
      根據JVM規範,Java堆可以處於物理上不連續的記憶體空間中。只要邏輯上是連續的即可。可通過-Xmx設定最大Java堆的大小,-Xms設定初始化時Java堆大小。

3、為了更好理解堆、棧、方法區, 以下舉個栗子

來,先看下一段程式碼

import java.text.SimpleDateFormat;
import
java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * mvp * @author yuanmeng * @create 2017-06-18 下午8:44 **/ public class MVP { private static Logger LOG = LoggerFactory.getLogger(MVP.class); public void winMVP(String name) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String today = sdf.format(new Date()); LOG.info("威少 mvp"); } }

這段程式的資料資訊在記憶體中存放如下圖所示:
image

垃圾收集器

1)檢測垃圾機制
  Java執行時所載入的資料, 如類資訊、例項物件資訊等會佔用系統記憶體, 所幸的是Java有個強大的垃圾收集器, 在記憶體不夠分配物件的時候會觸發GC。
  檢測垃圾的方法常見的有兩種:1. 引用計數法;2. 可達性分析演算法。

  • 引用計數法
      給物件附加一個引用計數器,只要有一個地方引用它,計數器值加1。當引用失效時就減1。任何時刻計數器都為0的物件就是不可能再被使用的,將其判定為可回收的物件。這種檢測機制的優點是很簡單, 但它有個很致命的缺點,它無法解決物件間迴圈引用問題。如hashMap在高併發的時候會出現迴圈連結串列問題。

  • 可達性分析演算法
      主流的JVM基本都使用可達性分析演算法來判斷物件是否存活,通過一系列“GC Roots”的物件作為起始點向下搜尋,搜尋所走過的路徑為引用鏈,當一個物件沒有任何引用鏈與GC Roots相連,代表該物件不再被使用,將其判定為可回收的物件。

看下圖,Object5 、Object6、Object7是從跟節點出發無法可達到的物件, 可判定為回收物件。

jvm4.png

2)回收垃圾機制

  • 標記-清除演算法
      先標記待回收的物件,然後再對標記的物件進行清除。圖解
    jvm5.png
    這種演算法缺點 :

    • 標記和清除兩個過程, 效率不高
    • 標記清除後會產生大量不連續記憶體碎片, 多次進行標記和清除回收後可能會導致以後程式在執行過程中需要分配大物件時,無法找到足夠的連續記憶體而不得不提前出發GC,而GC需要耗時間。
  • 複製演算法
      複製演算法是將記憶體分成兩塊,一塊儲存程式執行分配的物件,一塊是空閒區域。當儲存物件的記憶體區域用完了,會將此區域存活的物件複製到另一塊空閒區域,然後再把已使用的記憶體空間一次清理掉。

jvm6.png

複製演算法實現簡單、高效。但代價有點大了,可用記憶體縮小為原來的一半,以“空間換取時間”。

  • 標記-整理演算法

      標記-整理演算法是對原有標記-清除演算法進行的改造,不是直接對可回收物件進行清理,而是讓所有存活物件都向另一端移動,然後直接清理掉端邊界以外的記憶體。

jvm7.png

  • 分生代演算法

   前面也介紹過了,Java堆記憶體可以細分為新生代、老年代。新生代生存的生命週期比較短,每次經過GC後仍存活的物件年齡會加1, 多次GC後仍存活的物件會直接晉升為老年代。而老年代的生命週期比較長。換句話說,每次GC後新生代生存下來的物件很少,老年代存活物件多。前面分析過,生存物件少的新生代更適合用複製演算法,生存物件多的老年代適合用標記-清除或者標記-整理演算法

參考文獻

《深入理解Java虛擬機器》