1. 程式人生 > >關於JVM記憶體區域、記憶體溢位異常與垃圾回收策略的一點理解

關於JVM記憶體區域、記憶體溢位異常與垃圾回收策略的一點理解

JVM記憶體區域

程式計數器(Program Counter Register)

程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。每個執行緒都需要一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立儲存,即:執行緒私有。

虛擬機器棧(Virtual Machine Stacks)

java虛擬機器棧也是執行緒私有的,他的生命週期與執行緒相同。虛擬機器棧描述的是java方法執行的記憶體模型,每個方法在執行的同時,會建立一個棧楨,用於儲存區域性變數、運算元棧、動態連結和方法出入口等資訊。即:虛擬機器棧是針對於java方法執行過程中所涉及到的記憶體區域。

本地方法棧(Native Method Stack)

本地方法棧與虛擬機器棧所發揮的作用相似,區別在於,虛擬機器棧為虛擬機器執行JAVA方法服務,而本地方法棧則為虛擬機器使用到的Native方法服務。

JAVA堆(Java Heap)

Java堆是虛擬機器所管理的記憶體中最大的一塊,Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動的時候建立,此記憶體區域的唯一目的是存放物件例項。即:所有的物件例項以及陣列,都要在堆上分配記憶體。Java堆按照不同的角度還可以分為:新生代、老年代或者 Eden空間區域、From Survivor空間、To Survivor空間。

方法區(Method Area)

方法區是各個執行緒共享的記憶體區域,用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數和即時編譯器編譯後的程式碼等資料。

執行時常量池(RunTime Constant Pool)

方法區的一部分,編譯器生成的各種字面量和符號引用,在類載入後進入方法區的執行時常量池中。

直接記憶體(Direct Memory)

它並不是虛擬機器執行時資料區的一部分,但這部分記憶體會被頻繁的使用,也可能會導致OutOfMemoryError異常。在進行IO操作時,可以使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作,避免在Java堆和Native堆中來回複製資料。

記憶體溢位異常

  1. Java堆溢位。異常程式碼形如:OutOfMemoryError:Java heap space。
    不停的建立物件,而GC Roots 到這些物件之間有可達路徑,那麼在物件數量到達最大堆的容量限制後就會產生記憶體溢位異常。
    可能原因:如果是記憶體洩漏,洩露的物件與GC Roots相關聯,導致垃圾收集器無法回收,通過工具檢視洩露物件到GC Roots 的引用鏈,掌握洩露物件資訊和引用鏈資訊,就能夠找到引發記憶體洩露的程式碼位置。如果不是記憶體洩露,那說明物件依舊存活,因為無法回收,積累到一定量之後會引發記憶體溢位,可以通過調整虛擬機器的堆引數(-Xmx和-Xms),或者從程式碼的角度檢查是否有物件生命週期過長,持有狀態時間過長等問題。

  2. 虛擬機器棧和本地方法棧溢位
    如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverFlowError異常。
    如果虛擬機器在擴充套件棧深度是無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

  3. 方法區和執行時常量池溢位,異常程式碼形如:OutOfMemoryError:PermGen Space
    方法區用於存放Class的相關資訊,如類名、訪問修飾符、常量池、欄位描述和方法描述等。所以在使用反射、動態代理和CGlib等技術,執行時生成大量的動態類時,可能會出現記憶體溢位現象。

  4. 本機直接記憶體溢位
    DirectMemory容量可以通過 -XX:MaxDirectMemorySize指定,如果不指定,則預設與Java堆的最大值一樣,因為直接記憶體並不是虛擬機器的記憶體區域,所以當虛擬機器記憶體區域+直接記憶體所使用的區域大於本機記憶體時,無法申請到本機記憶體,則丟擲異常,除OutOfMemoryError字樣外,無其餘明顯報錯資訊。

垃圾收集演算法

標記-清除 演算法

該演算法分為“標記”和“清除”兩個階段,首先,標記所有需要回收的物件,在標記完成後,同意回收所有被標記的物件。
缺點
1. 標記和清除兩個過程效率都不高。
2. 標記清除後會產生大量不連續的記憶體碎片。

複製演算法

將可用的記憶體按照容量劃分為大小相等的兩塊,每次只使用一塊,再回收的時候,將還存貨的物件,複製到另外一塊上面,然後,再把已使用的記憶體空間一次性清理掉。簡單,高效。
缺點:可用記憶體區域變為原來的一半。物件存活率較高時,會進行較多的複製操作,效率降低(在Eden區域中較為合適,不宜在老年代中使用該演算法)。
優化:因為大部分的物件都是“朝生夕死”,所以,並不需要按照1:1來劃分記憶體區域,按照8:1來劃分區域比較合理,即:Eden區域佔80%,From Survivor區域佔10%,To Survivor區域佔10%。

標記-整理演算法

標記整理演算法的過程與標記清除演算法相似,但後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件直接都想一端移動,然後直接清理掉端邊界意外的記憶體。

分代收集演算法

分代收集演算法並不是一種新的演算法,可以理解為一種思想,即:根據物件存活週期的不同,劃分為幾塊(新生代和老年代)。新生代中每次垃圾收集,都有大批的物件死去,只有少量存活,適宜選用複製演算法,而老年代中因為物件的存活率較高,所以使用標記-清除或者標記-整理演算法來進行回收。