Java內存分配和垃圾回收

最近拜讀了周志明老師的深入理解Java虛擬機,也寫一點皮毛的Java垃圾回收機制,可能存在一些紕漏,本人是菜鳥,哈哈。
廢話不多說,直接進入正題。
Java提供了垃圾回收機制(GC)讓我們可以免去很多的像C++的內存釋放的問題,但是問題接着又來了,由於GC是隨機的,不可避免有一些內存會浪費,那麼就需要我們對Java的GC機制有着清晰的認識。
第一、怎麼判斷對象已經死了呢?
在Java中提供了一下兩種普遍的方法,在之前的Java虛擬機使用的是第一種,引用計數算法,顧名思義,就是一個對象每增加一個引用,便在自己的計數器上面增加1,當減少一個引用的時候,就在對象的計數器減1,這種算法是可行的,並且也具有很高的執行效率,但是存在缺陷的是引用計數算法無法處理當對象相互循環引用的時候帶來的問題,會造成對象其實已經可以回收但是卻還存在引用計數器不爲0的現象。爲了解決這個問題,提出了第二種算法,可達性分析算法,可達性分析算法中,是通過GCRoots作爲對象的起始點,每個對象會有自己的引用鏈跟GC Roots相連接,當不可達的時候,也就是說當對象的引用鏈並沒有刻意達到GCRoots的時候,就被判定爲刻意回收的對象。
在這裏引出Java之前給引用的四個類型定義:
1、強引用
不會被內存回收的引用,具有最高的權限,就算是拋出OOM也不捨得回收強應用類型的對象。
2、軟引用
只有在內存快溢出的時候纔會去收集這一部分對象,如果經過這次回收之後,依然是內存不足,那麼就會OOM。
3、弱引用
在檢查到弱引用需要回收,會在接下來的GC進行回收
4、虛引用
任意時刻都可以進行回收的,所以該優先級也是最低的。
但是,我們判定對象是沒用的, 不可達的時候,是否就會真的回收了呢?答案是不一定。
當對象被認爲是不可達的時候,其實只是進行了一次標記,真正需要判斷是否可以進行回收需要經歷如下的操作。
當對象在可達性分析的時候,發現並沒有跟GCRoots連接,那麼就會進行第一次標記,並進行一次篩選,篩選的原則是對象是否需要執行finalize()方法或者是對象是否覆蓋了該方法,如果對象不需要執行該方法的時候,就會被放過。不會進行回收,但是如果對象被判定需要執行finalize()的方法的時候,那麼虛擬機會將對象放在F-Queue隊列中,並且會開啓一個線程去執行。但是在這個時候可以進行自救,也就是說在finalize()方法中完成自救,擁有跟其他任意對象相關聯。
如果沒有完成自救,或者已經自救過一次被再次回收的,虛擬機就會進行回收,並釋放對應的內存區域。
此時,當虛擬機已經完成垃圾對象的判定的時候,接下來就是垃圾回收,垃圾回收的關鍵在於垃圾收集的算法。
這裏有以下幾種垃圾收集的算法:
1、標記——清除算法
標記——清除算法值得是在不可用的對象做一個標記,然後回收的時候原地回收,這個算法適合回收大規模的垃圾,不然該算法有一個缺陷,就是當進行標記——清除的時候會產生很多的內存碎片,當下次需要分配大對象內存的時候,需要尋找一塊大的內存空間,就會觸發下一次的GC。
2、複製算法
複製算法是一種把內存容量劃分爲相等大小的兩塊內存空間A和B,當A空間需要進行GC的時候,將A中的存活的對象放在B中,然後對A內存整塊回收,這樣的算法可以解決大對象開闢空間的問題,但是內存減少了一半,所以我們可以考慮是否將內存空間劃分成其他比例,例如811呢。
3、標記——整理算法
標記整理算法其實跟標記清除算法是異曲同工的,但是唯一的區別在於,標記整理算法是將存活的對象往一邊挪動,然後在這個邊界之外的對象進行GC。
知道了如何判斷對象死活,知道了垃圾收集算法,現在執行的垃圾收集器我們也需要了解。
垃圾收集器有很多的種類,由於認知原因,只拿出以下幾種瞭解瞭解。
第一種、Serial 單線程垃圾回收器
Serial是一個執行在新生代的單線程垃圾回收器,當Serial進行GC的時候,會觸發STW(Stop the World),也就意味着會暫停用戶的所有進程,當GC完之後再恢復用戶的操作,這樣帶給用戶端的體驗是不好的,但是反過來一想,其實也可以理解,就像你在打掃課室的時候,你打掃,然後你的同學繼續扔,這樣的回收可是很累的。但是無可否認的是,Serial在單CPU,單核機器上面的處理性能是很棒的。
第二種、ParNew 多線程垃圾回收器
ParNew實際上就是Serial的多線程版本,實現了可以讓用戶的進程和垃圾回收的進程可以同時運行的效果。
第三種、Parallel Scavenge
其實這個跟ParNew是很類似的,唯一的不同是他們考慮的側重點不同,ParNew是爲了減少用戶線程的停頓時間,但是Parallel Scavenge是爲了提高吞吐量,吞吐量也就是意味着有效代碼的執行時間,有效代碼的執行時間佔總代碼執行時間(等於有效代碼+GC)的比例就是吞吐量,提高吞吐量也會造成GC次數過多,因爲你需要保證垃圾少,那麼就需要保證多次觸發進行垃圾回收。
還有各垃圾回收器對應的老年代的垃圾回收期,還有G1就先不講了。
到這裏就已經將垃圾回收的過程和核心講完了。
接下來既然有垃圾回收,那麼也有對象在內存中的存放
內存分配也是一個大學問。
但是會有一下幾條原則:
1、對象優先分配到新生代的Eden區,當Eden區沒有足夠的空間分配的時候,虛擬機進行一次Minior GC,經歷一次Minor GC之後,新生代Eden區的對象會放在Survivor區,如果Survivor區不夠容量去存放這些對象的時候,會將對象放在老年代區,此時涉及到擔保
擔保指的是當對象移到Survivor區的時候,如果沒有足夠的區域存放從Eden區的對象的時候,便會送到老年代區,此時老年代區的存放能力就是一種擔保。
2、大對象直接進入老年代
大對象,舉例而言就是比如數組,數組就是一個典型的大對象,需要的內存也大,同時還有字符串等等。大對象對於內存分配是一個壞消息,因爲在新生代我們採用的是複製算法,爲了避免複製的頻繁發生就放在老年代,老年代除存放大對象之外,更重要的是將長期存活的對象放入老年代中。
這裏也涉及到一個標準,就是如何衡量對象是長期存活的呢,這個就要看我們的設置,但是我們在這裏只講算法,稱之爲年齡計算算法。
年齡計數算法是依靠Minor GC來實現的。當第一次Minor GC的時候,新生代Eden區會將其移動到Survivor,然後年齡增加1,再次經歷Minor GC的時候會增加1,然後增加到虛擬機指定的次數,一般是15的時候,那麼就移動到老年代。
然後,洗澡去了,哈哈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章