JVM(複習)垃圾收集器和記憶體配置設定政策
文章目錄
- JVM(複習)垃圾收集器和記憶體配置設定政策
- 一,垃圾回收
- 1.1哪些記憶體需要回收
- 1.2什麼時候回收
- 1.2.1引用計數算法
- 1.2.2可達性分析算法
- 1.3怎樣回收
- 1.3.1标記-清除算法
- 1.3.2複制算法
- 1.3.3标記-整理算法
- 1.3.4分代收集算法
- 1.4垃圾收集器
- 二,記憶體配置設定
一,垃圾回收
關于垃圾回收我們需要總結的點有三個:
- 哪些記憶體需要回收
- 什麼時候回收
- 怎樣回收
1.1哪些記憶體需要回收
程式計數器,虛拟機棧,本地方法棧3個區域随線程而生,随線程而滅
- 棧中的棧桢随着方法的進入和退出而有條不紊的執行者出棧和入棧操作,每一個棧桢中配置設定多少記憶體基本可以在編譯期确定,随着方法或線程結束,該部分記憶體也會随着被回收
java堆和方法區則不一樣,一個接口的多個實作類需要的記憶體可能不一樣,需要在運作期才會知道會建立哪些對象,這部分記憶體配置設定和回收都是動态的
而且,堆裡幾乎存放着所有對象執行個體,是以這一部分才是垃圾收集關注的地方
1.2什麼時候回收
1.2.1引用計數算法
算法原理:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任何時刻,計數器為0的對象就是不可能再被使用的
但是該算法無法解決循環引用問題:
public class Test{
static class A{
private byte[] bytes = new byte[2 * 1024];
private Object object;
}
static class B{
private byte[] bytes = new byte[2 * 1024 * 1024];
private Object object;
}
public static void main(String[] args) {
A a = new A();
B b = new B();
//互相引用
a.object = b; //引用b,b的引用計數器加一
b.object = a; //引用a,a的引用計數器加一
//引用失效,引用計數置為0
a = null;
b = null;
//垃圾回收
System.gc();
}
}
可以看到,jvm并沒有因為兩個對象互相引用就不回收他們,可以看出jvm判斷對象是否可以回收采取的不是引用計數法
1.2.2可達性分析算法
判斷對象是否存活都是與引用有關
java中的引用
-
強引用:
強引用非常常見,比如:
Object a = new Object();
這類引用就是強引用
隻要強引用還在,垃圾收集器永遠不會回收他們,哪怕記憶體不足仍不會回收,但會抛出OOM
-
軟引用:
軟引用用來描述一些還有用但并非必需的對象,對于軟引用關聯的對象,在記憶體充足時,不會回收,但是在記憶體不足時會回收,如果回收了軟引用還是收集不到足夠的記憶體進行配置設定,則會抛出OOM,利用SoftReference實作弱引用
//軟引用實作
String string= "";
//構造方法傳入引用
SoftReference<String> softReference = new SoftReference<String>(string);
//此時string便是一個軟引用
string = softReference.get();
-
弱引用:
描述非必需對象,無論記憶體是否足夠,隻要gc,便會回收
//弱引用實作
String s = "";
WeakReference<String> weakReference = new WeakReference<>(s);
s = weakReference.get();
-
虛引用:
一個對象是否有虛引用的存在完全不會對其生存時間構成影響,無法通過虛引用來擷取一個對象的執行個體,為一個對象設定虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知
再談gc root
其實可達性分析算法是基于gc root引用鍊實作的,通過GC Root對象作為起始點,從這些節點開始往下搜尋,搜尋走過的路徑稱為引用鍊,當一個對象到GC Root沒有任何引用鍊和GC Root相連,則此對象無用,該對象可以回收。
哪些可作為GC Root:
- 虛拟機棧中引用的對象
- 方法區中類靜态屬性引用的對象
- 方法區常量引用的對象
- 本地方法棧引用的對象
凡是被常量,靜态變量,全局變量,運作時方法中的變量直接引用的對象,原則上不能被gc
即使在可達性分析算法中不可達的對象,也并非是非死不可。的,這時候它們暫時出于“緩刑”階段,一個對象的真正死亡至少要經曆兩次标記過程
- 如果對象在進行中可達性分析後發現沒有與 GC Roots 相連接配接的引用鍊,那他将會被第一次标記并且進行一次篩選,篩選條件是此對象是否有必要執行 finalize() 方法。當對象沒有覆寫 finalize() 方法,或者 finalize() 方法已經被虛拟機調用過,虛拟機将這兩種情況都視為“沒有必要執行”。
- 如果這個對象被判定為有必要執行 finalize() 方法,那麼這個對象竟會放置在一個叫做 F-Queue 的隊列中,并在稍後由一個由虛拟機自動建立的、低優先級的 Finalizer 線程去執行它。這裡所謂的“執行”是指虛拟機會出發這個方法,并不承諾或等待他運作結束。finalize() 方法是對象逃脫死亡命運的最後一次機會,稍後 GC 将對 F-Queue 中的對象進行第二次小規模的标記,如果對象要在 finalize() 中成功拯救自己 —— 隻要重新與引用鍊上的任何一個對象履歷關聯即可。finalize() 方法隻會被系統自動調用一次。
回收方法區(HotSpot中的永久代)
在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空間,而永久代的垃圾收集效率遠低于此。
永久代垃圾回收主要兩部分内容:廢棄的常量和無用的類。
判斷廢棄常量:一般是判斷沒有該常量的引用。
判斷無用的類:要以下三個條件都滿足
- 該類所有的執行個體都已經回收,也就是 Java 堆中不存在該類的任何執行個體
- 加載該類的 ClassLoader 已經被回收
- 該類對應的 java.lang.Class 對象沒有任何地方呗引用,無法在任何地方通過反射通路該類的方法
1.3怎樣回收
1.3.1标記-清除算法
先标記出所有需要回收的對象,标記完成後,統一回收是以被标記的對象
缺陷:
- 效率問題
- 标記和清除兩個過程的效率并不高
- 空間問題
- 标記清除之後會産生大量不連續的記憶體碎片,當需要配置設定一個大對象時,就無法找到足夠的連續記憶體而不得不觸發一次垃圾回收
1.3.2複制算法
複制算法解決了标記-清除算法的效率問題
他将可用記憶體按照容量劃分為大小相等的兩塊,每次隻使用其中的一塊,當這一塊記憶體用完了,就将還存活的對象複制到另一塊上,然後将已使用過的記憶體空間一次清理掉
這種算法的代價是将記憶體縮小為原來的一半,且在對象存活率較高時,需要進行多次複制操作,此時效率會降低
1.3.3标記-整理算法
标記整理算法和标記清除的第一階段标記相同,先标記出所有可回收的對象,接下來,标記整理不是對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理端邊界以外的記憶體。
1.3.4分代收集算法
根據對象存活周期的不用将記憶體劃分為幾塊,堆分為新生代和老年代,根據各個年代的特點采用最适當的算法
- 新生代:
- 新生代每次垃圾收集都有大批的對象死去,隻有少量存活,采用複制算法,隻需要付出少量存活對象的複制成本就可以完成收集
- 老年代:
- 老年代對象成活率較高,沒有額外的空間為其配置設定擔保,必須采用标記-清除或者标記-整理算法
1.4垃圾收集器
收集算法是記憶體回收的方法論,垃圾收集器就是記憶體回收的具體實作
自己弄的思維導圖:
二,記憶體配置設定
對象記憶體配置設定,大方向來說,就是在堆上如何配置設定
java堆分代:
對象優先在Eden配置設定
大多數情況下,對象在新生代Eden區配置設定,當Eden區沒有足夠的記憶體空間進行配置設定時,虛拟機将發起一次Minor GC
大對象直接進入老年代
大對象(需要大量連續記憶體空間的java對象,數組或者長字元串)将直接進入老年代