JVM(複習)垃圾收集器和內存分配策略
文章目錄
一,垃圾回收
關於垃圾回收我們需要總結的點有三個:
- 哪些內存需要回收
- 什麼時候回收
- 怎樣回收
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對象,數組或者長字符串)將直接進入老年代
長期存活的對象將進入老年代
每個對象都有一個年齡計數器,對象在Eden區經過第一次minor gc後仍然存活,並且能被Survivor容納的話,將移動到Survivor分區,並且將該對象年齡置爲1,以後,在survivor每經歷一次minor gc後仍能存活,年齡 + 1;當年齡到達一定程度(默認15歲)則進入老年代,但也不一定說必須達到這個年齡纔可以進入老年代,如果,survivor空間中相同年齡的所有對象大小的總和大於survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代