1. 程式人生 > >JVM記憶體區域劃分及垃圾回收

JVM記憶體區域劃分及垃圾回收

   第一部分、閒扯+概述

        近來在研讀《深入理解java虛擬機器》一書,讀完之後做個小結,算是記錄一下自己的學習所得,在成長的路上,只能死磕。

要理解JVM,就要先從其記憶體區域劃分開始,知道其由幾部分構成,再瞭解各部分的功能,這樣就能對其整體有一個瞭解。

話不多說,總體圖先呈上:

    可以看到,執行緒私有的記憶體區域有虛擬機器棧、本地方法棧、程式計數器,這些區域都不會出現執行緒安全問題;而執行緒共享的區域有堆、方法區。

下面對其各個分割槽域進行介紹。

第二部分:JVM記憶體區域劃分

    1、首先是最簡單的程式計數器,每個執行緒都有一個獨立的計數器,用來記錄執行緒執行的虛擬機器位元組碼指令。另外,如果是Native方法,則計數器中的

值為undifined。

   2、本地方法棧:類似於虛擬機器棧,只是此處執行的是native方法

   3、虛擬機器棧:執行java方法的地方,基本單位是棧幀,一個方法從開始執行到結束的過程,就是一個棧幀在虛擬機器棧中入棧到出棧的過程。(本來

認真準備了兩張棧幀跟區域性變量表的邏輯示意圖,不知道怎麼的傳不上來,只能省去,<手動擦汗>)

   3.1、區域性變量表:顧名思義,是存返方法中變數的地方。

       區域性變量表存放變數的單位是Slot 槽,每個槽都能存放一個32位的變數,64位的由連續的兩個槽來存放。如果執行的是非static方法,則第一個槽中

存放的是當前物件的引用,即this。具體存放的引用,是直接或間接能定位到這個物件在堆中的位置的一個值,比如物件的起始地址。

   3.2、運算元棧:是LIFO(後入先出)棧,虛擬機器就是一個基於運算元棧的執行引擎。在處理資料中處於重要位置。一個32位的資料佔據棧中一個容量。比如執行a+b時,位元組碼

指令會先往棧中壓入a跟b,然後iadd指令執行時,將棧頂的兩個值a跟b彈出,相加,再壓入棧中,完成計算。

   3.3、動態連結:存放的是方法區的執行時常量池中此棧幀對應的方法的引用

   3.4、返回地址:記錄方法執行完之後返回的位置,回到這個位置程式才能正確的繼續執行下去。

   4、方法區:最主要的功能,便是存放位元組碼檔案、Class物件、常量等

   5、堆區:此部分是虛擬機器中上連棧下連方法區的中樞之地,也是垃圾收集的重地,可以說基本所有的物件都存放在此區域。

   物件在堆區中,由三部分組成:物件頭、實體資料、對齊填充

        物件頭:一般由兩部分組成,一部分是用於存放hash值、分代年齡、鎖標識的稱為Mark Word的區域,另一部分用於存放指向方法區中物件型別的指標(直接指標定位時有);

        實體資料:存放正常的物件資料;

        對齊填充:物件的記憶體佔用大小為8位元組的整數倍,如果不夠,用此部分來填充。

        堆區按照垃圾收集區域,劃分為兩部分:新生代、老年代,一般預設比例是1:2,而在新生代中,又分成了三部分,分別為Eden、Survivor From 、Survivor To ,預設比例

為8:1:1。

第三部分、垃圾回收

        觸發條件:當記憶體中可用空間不夠時才會觸發垃圾回收

        什麼樣的物件會被回收:通過可達性分析演算法,如果發現某個物件與GC ROOTS沒有連線,則此物件滿足被回收的條件。

        GC ROOTS定義:1) 虛擬機器棧中引用的物件;2)本地方法棧中引用的物件;3) 類靜態屬性引用的物件;4) 常量引用的物件

        如何回收:標記清除演算法、複製演算法、標記整理演算法、分代蒐集演算法,目前主流虛擬機器中使用的就是分代蒐集演算法

        分代蒐集演算法的過程:JVM開發團隊發現,對於新生代中大部分的物件,存活時間都很短,所以新生代使用複製演算法,且工作空間與空閒空間之比為9:1(9即上面所說的一個

Eden與Survivor From之和),因為新生代絕大部分物件會被清理,清理之後還剩下的物件就放在那一份的Survivor To中,如果空間不夠,再往老年代放,另外經歷過一定次數的

新生代處理還沒被回收的物件,也會轉移到老年代中;而對於老年代,用標記整理演算法或者標記清除演算法,因為老年代物件數量龐雜,且大多數不滿足清理條件,所以老年代的

垃圾回收耗時長效果相對較差。新生代中Eden區域專門用於存放新物件,如果此區域記憶體空間不足,則觸發新生代GC,此之謂Minor GC;同樣老年代空間不足,觸發Full GC(亦

稱之為Major GC),耗時長,要儘量避免,一般來說Full GC次數一天不能超過一次。

        垃圾收集器:針對新生代跟老年代都有不同的垃圾蒐集器,例如針對新生代的有Serial、ParNew等,針對老年代的有CMS、Serial Old等,不同的蒐集器之間只能有特定的

幾個組合(不能隨意地組合使用,會有不相容),具體看專案實際情況而定。目前BZ所在公司用的是ParNew與CMS的組合,雖然CMS用的是標記清除演算法,會使垃圾收集後的記憶體

碎片較多,但是萬能的前輩們早就想到了這些,提供了-XX:+UseCMSCompactAtFullCollection指令壓縮記憶體中的碎片,以及-XX:CMSFullGCsBeforeCompaction指令設定多少次

Full GC後整理記憶體碎片。 

       針對垃圾收集這一塊,BZ還沒有實際動手通過相關知識解決過專案中的問題,目前只是紙上談兵,後面會摸索一下如何通過工具監控專案中的記憶體狀況,並整理出來。學習之

路,還是要多給自己打打雞血~