天天看點

JVM 和GC垃圾處理工作原理

JAVA GC垃圾處理工作原理

引用計數

每個對象都含有一個引用計數器,當有引用連接配接對象時+1,當引用離開作用域時-1,當成為null時,立即釋放對象。

引用鍊

對象之間的引用是樹形結構,從樹根作為起點進行搜尋,走過的鍊稱為引用鍊,當這個對象到樹根沒有任何引用鍊相連時,則表示這個對象是不可用的,系統就會回收。

— java虛拟機采用的自适應回收機制就是基于引用鍊

标記 - 清掃

周遊所有引用,找存活對象,對象存活對其标記,當周遊工作完成後,才會進行清理工作。清理過程中,未被标記的對象不會進行複制工作。剩餘的堆空間也不是連續的,如需得到連續的,必須重新整理。

java記憶體結構
JVM 和GC垃圾處理工作原理
JVM 和GC垃圾處理工作原理

1、 類加載子系統:負責從檔案系統或者網絡加載Class資訊,加載的資訊存放在一塊稱之方法區的記憶體空間。

2、 方法區:就是存放類的資訊、常量資訊、常量池資訊、包括字元串字面量和數字常量等。

3、 Java堆:在Java虛拟機啟動的時候建立Java堆,它是Java程式最主要的記憶體工作區域,幾乎所有的對象執行個體都存放到

Java堆中,堆空間是所有線程共享。

4、 直接記憶體:JavaNio庫允許Java程式直接記憶體,進而提高性能,通常直接記憶體速度會優于Java堆。讀寫頻繁的場合可能會考慮使用。

5、 每個虛拟機線程都有一個私有棧,一個線程的Java棧線上程建立的時候被建立,Java棧儲存着局部變量、方法參數、同僚Java的方法調用、

傳回值等。

6、 本地方法棧:最大不同為本地方法棧用于本地方法調用。Java虛拟機允許Java直接調用本地方法(通過使用C語言寫)

7、PC寄存器yes每個線程私有的空間,java虛拟機會為每個線程建立PC寄存器,在任意時刻,一個java線程總是在執行一個方法,這個方法稱為目前方法,如果目前方法不是本地方法,PC寄存器總會執行目前正在被執行的指令,

如果是本地方法,則PC寄存器值為Underfined,寄存器存放如果目前執行環境指針、程式技術器、操作棧指針、計算的變量指針等資訊。

8、虛拟機核心的元件就是執行引擎,它負責執行虛拟機的位元組碼,一般戶先進行編譯成機器碼後執行。

JAVA 堆

堆,就是來存放對象的。new出來的對象和數組,在堆中配置設定記憶體,由GC來管理。

最常見的JAVA堆分為新生代和老年代,剛建立的在新生代,經常使用的放在老年代。

新生代分為den區、S0區、S1區,s0和s1是兩塊相等并且可以互換的區,他們兩個隻能一個存活。

JAVA 棧

Java棧是一塊線程私有的空間,一個棧,一般由三部分組成:局部變量表、操作資料棧和幀資料區

局部變量表:用于報錯函數的參數及局部變量

操作數棧:主要儲存計算過程的中間結果,同時作為計算過程中的變量臨時的存儲空間。

幀資料區:除了局部變量表和操作資料棧以外,棧還需要一些資料來支援常量池的解析,這裡幀資料區儲存着

通路常量池的指針,友善計程式通路常量池,另外當函數傳回或出現異常時賣虛拟機子必須有一個異常處理表,友善發送異常

的時候找到異常的代碼,是以異常處理表也是幀資料區的一部分。

JAVA 方法區

Java方法區和堆一樣,方法區是一塊所有線程共享的記憶體區域,他儲存系統的類資訊。

比如類的字段、方法、常量池等。方法區的大小決定系統可以儲存多少個類。如果系統定義太多的類,導緻方法區溢出。虛拟機同樣會抛出記憶體溢出的錯誤。方法區可以了解為永久區。

虛拟機參數配置

JVM調優基本yes給堆的參數進行配置

-XX:+PrintGC 每次觸發GC的時候列印相關日志

-XX:+UseSerialGC 串行回收

-XX:+PrintGCDetails 更詳細的GC日志

-Xms 堆初始值

-Xmx 堆最大可用值

-Xmn 新生代堆最大可用值

-XX:SurvivorRatio 用來設定新生代中eden空間和from/to空間的比例.

含以-XX:SurvivorRatio=eden/from=den/to

總結:在實際工作中,我們可以直接将初始的堆大小與最大堆大小相等,

這樣的好處是可以減少程式運作時垃圾回收次數,進而提高效率。

-XX:SurvivorRatio 用來設定新生代中eden空間和from/to空間的比例.

參數: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

-Xmn 新生代大小,一般設為整個堆的1/3到1/4左右

-XX:SurvivorRatio 設定新生代中eden區和from/to空間的比例關系n/1

JVM總結

在JVM啟動參數中,可以設定跟記憶體、垃圾回收相關的一些參數設定,預設情況不做任何設定JVM會工作的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能獲得最佳性能。通過設定我們希望達到一些目标:

• GC的時間足夠的小

• GC的次數足夠的少

• 發生Full GC(新生代和老年代)的周期足夠的長

前兩個目前是相悖的,要想GC時間小必須要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,我們隻能取其平衡。

(1)針對JVM堆的設定,一般可以通過-Xms -Xmx限定其最小、最大值,為了防止垃圾收集器在最小、最大之間收縮堆而産生額外的時間,我們通常把最大、最小設定為相同的值

(2)年輕代和年老代将根據預設的比例(1:2)配置設定堆記憶體,可以通過調整二者之間的比率NewRadio來調整二者之間的大小,也可以針對回收代,比如年輕代,通過 -XX:newSize -XX:MaxNewSize來設定其絕對大小。同樣,為了防止年輕代的堆收縮,我們通常會把-XX:newSize -XX:MaxNewSize設定為同樣大小

(3)年輕代和年老代設定多大才算合理?這個我問題毫無疑問是沒有答案的,否則也就不會有調優。我們觀察一下二者大小變化有哪些影響

• 更大的年輕代必然導緻更小的年老代,大的年輕代會延長普通GC的周期,但會增加每次GC的時間;小的年老代會導緻更頻繁的Full GC

• 更小的年輕代必然導緻更大年老代,小的年輕代會導緻普通GC很頻繁,但每次的GC時間會更短;大的年老代會減少Full GC的頻率

• 如何選擇應該依賴應用程式對象生命周期的分布情況:如果應用存在大量的臨時對象,應該選擇更大的年輕代;如果存在相對較多的持久對象,年老代應該适當增大。但很多應用都沒有這樣明顯的特性,在抉擇時應該根據以下兩點:(A)本着Full GC盡量少的原則,讓年老代盡量緩存常用對象,JVM的預設比例1:2也是這個道理 (B)通過觀察應用一段時間,看其他在峰值時年老代會占多少記憶體,在不影響Full GC的前提下,根據實際情況加大年輕代,比如可以把比例控制在1:1。但應該給年老代至少預留1/3的增長空間

GC垃圾回收算法

引用計數法

每個對象都有一個引用計數器,每當有一個地方引用它時,計數器就加1;當引用失效時,計數器就減1。預設為15,當為0的時候表示該對象不再被使用,被垃圾回收

複制算法

s0和s1将可用記憶體按容量分成大小相等的兩塊,每次隻使用其中一塊,當這塊記憶體使用完了,就将還存活的對象複制到另一塊記憶體上去,然後把使用過的記憶體空間一次清理掉。這樣使得每次都是對其中一塊記憶體進行回收,記憶體配置設定時不用考慮記憶體碎片等複雜情況,隻需要移動堆頂指針,按順序配置設定記憶體即可,實作簡單,運作高效。

複制算法的缺點顯而易見,可使用的記憶體降為原來一半。

複制算法用于在新生代垃圾回收

标記清除算法

标記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動作,一個是标記,另一個就是清除。

标記就是根據特定的算法(如:引用計數算法,可達性分析算法等)标出記憶體中哪些對象可以回收,哪些對象還要繼續用。

标記訓示回收,那就直接收掉;标記訓示對象還能用,那就原地不動留下。

标記-壓縮算法

标記壓縮法在标記清除基礎之上做了優化,把存活的對象壓縮到記憶體一端,而後進行垃圾清理。(java中老年代使用的就是标記壓縮法)

分代收集算法

根據記憶體中對象的存活周期不同,将記憶體劃分為幾塊,java的虛拟機中一般把記憶體劃分為新生代和年老代,當新建立對象時一般在新生代中配置設定記憶體空間,當新生代垃圾收集器回收幾次之後仍然存活的對象會被移動到年老代記憶體中,當大對象在新生代中無法找到足夠的連續記憶體時也直接在年老代中建立。

對于新生代和老年代來說,新生代回收頻率很高,但是每次回收耗時很短,而老年代回收頻率較低,但是耗時會相對較長,是以應該盡量減少老年代的GC.