Java垃圾回收簡介
答:JVM不定時的去檢測回收不可達對象。
什麼是不可達對象
答:就是被創建的對象沒用被繼續使用,但是對象在創建的時候是可達的,是否可達是根據垃圾回收機制算法進行控制的。
拓展:將對象賦值爲null的時候,那麼這個對象就是不可達對象,這時候調用System.gc()方法,那麼就會通知GC線程進行垃圾回收,但是不是立馬被回收。
拓展:重寫finalize()方法,那麼該對象在被回收的時候會先執行finalize()方法。
在開始之前我們詳細講一下我們在--java虛擬機基礎--中提到的堆內存
堆內存結構介紹
- 新生代內存區:剛創建或者不經常使用的對象--經常發生垃圾回收
- 老年代內存區:經常使用的對象保存的地方--不經常發生垃圾回收
其中新生代內存區又可以細分爲:
- cden區:剛創建的對象都將存在於該區域中
- form(S0)區:當cden區中的對象使用次數達到一定數的時候,則會被放入from區域
- to(S1)區:內存大小與S0區一樣,而且S1和S0區必然會存在一個爲空
堆內存結構依賴關係
垃圾回收怎麼之後對象是否爲可達對象
我們的GC線程是怎麼知道該對象是否爲可達對象呢?
兩種判斷算法:
- 引用計數法
- 根搜索算法(GC Roots)
引用計數法(已經淘汰)
實現原理:每一個對象都有一個年齡,如果小於等於15歲,那麼會存放在新生代裏面,如果大於15那麼會被存放在老年代裏面。GC線程在不定時進行回收時,如果對象繼續被引用,年齡會加1,如果沒用被引用則年齡會減一,初始年齡爲0,當年齡爲0歲的時候,那麼會被垃圾回收機制認爲是不可達對象,則會被GC垃圾回收
缺點:該算法缺點也是致命的,因爲會參數循環依賴問題,即爲:當存在循環依賴的時候,這個時候引用計數法就很難知道該對象是否被引用。
舉例說明:創建對象A,B,把對象A賦值給對象B,在把對象B賦值給對象A,然後在把對象A,B都賦值爲null,然後調用System.gc(),通知垃圾回收,但是使用引用計數法,GC就很難知道該對象是否被引用。
根搜索算法(GC Roots)
實現原理:判斷對象是否可達,需要判斷對象是否和GC Roots是否存在引用關係,如果不存在引用關係,GC會認爲是不可達對象,這個時候則會被GC進行回收。即爲:循環判斷是否對根對象有依賴,有則爲可達對象,無則爲不可達對象。(類似於樹狀結構圖)
舉例說明:如下圖,user4和user6雖然存在依賴關係,但是還是會被GC回收,因爲他們沒有一個依賴的跟節點。
哪些對象可以作爲根節點:存在方法區和堆內存中的數據對象,都可以作爲根節點,這個時候依賴於他們的成員變量以及靜態變量,常量,數組,線程池等都可以作爲根節點。
垃圾回收算法
既然有了方法知道對象是否爲可達對象,那麼接下來就應該將不可達對象進行回收,那麼有哪些回收方式呢?
垃圾回收算法:
- 標記清除法:一個個刪除
- 複製算法:批量刪除
爲什麼之前會先講解堆內存結構以及關係呢,就是因爲垃圾回收的兩種算法就是基於對內存關係結構進行的。
標記清除法
實現原理:GC會不定時的去檢測是否存在不可達對象,如果對象有被引用則標記爲0,如果沒有引用則標記爲1,當標記爲1的時候就是不可達對象,則會進行垃圾回收。
缺陷:會產生內存碎片化(很抽象),效率慢
優點:對內存的佔用比較少
缺陷舉例說明:因爲垃圾回收是存在時間限制的,加入當檢測到1W+的對象都是不可達對象的時候,這個時候GC就會刪除1W+的不可達對象,因爲系統高負荷,肯定會有一部分在指定時間不能成功刪除,但是又刪除了一部分,這就導致了了那一部分的內存空間不可用。導致內存碎片化,就像是window下載一個軟件,刪除一部分依賴導致軟件不可用,但是該軟件還是會佔用系統一部分空間
適用場景:老年代的回收,因爲老年代比較穩定,不會發生大量刪除。
複製算法
這裏的複製算法就跟前面的堆內存結構依賴有關係了,這裏先講解一些結構依賴關係
主流程詳解:當對象先創建的時候會保存在cden區中,GC會不定時的通過垃圾回收算法進行掃描並清除不可達對象,如果cden區中的對象在GC掃描的時候發現是不可達對象,則會進行回收,如果是可達對象並且被使用次數達到一定數量的時候就會把該對象放入到survivor區中,默認是先進入S0區,然後如果survivor區域中的對象在GC檢測的時候發現經常使用,並且次數達到一定數量的時候就會把該對象放入到老年代內存裏面,當老年代之中的對象爲不可達的時候則會進行垃圾回收。
實現原理:survivor區中存在兩個區,from以及to,這裏就稱之爲S0,S1。那麼S0和S1的大小是完全一樣的,當S0或S1中存在不可達對象的時候,那麼GC就會把該內存空間的可達對象賦值到另外一個S空間下面,並且清空掉該內存空間中的所有對象。
缺陷:佔用內存,因爲存在兩個同樣大小的S區,並且有一個必然爲空,爲了讓另外一個空間內存在不可達對象之後,GC好賦值其中的可達對象到該內存中
優點:效率高,類似於系統格式化,不會發生碎片化
適用場景:新生代中使用,因爲新生代中回收比較頻繁。不適合一個個進行回收。
JVM垃圾回收總結:
什麼是不可達對象:沒有被使用的對象。
堆內存結構:新生代,老年代,cden區,survivor區,from區,to區
cden區:屬於新生代,剛創建的對象存放的地方。
survivor區:屬於新生代,CG發現cden區的某個對象被多次使用,則會將該對象保存在survivor區
form區:屬於survivor,目的是存在不可達對象則清空form區,將可達對象全部複製到to區
to區:屬於survivor,目的是存在不可達對象則清空to區,將可達對象全部複製到form區
JVM判斷對象是否爲可達對象:引用計數發以及根搜索法
引用計數法:當GC檢測的時候發現對象被使用,則會加一操作,相反減一,當爲0的時候標記爲不可達對象
根搜索法:通過循環判斷對象是否對根節點root存在依賴關係,如果不存咋依賴關係則爲不可達對象
垃圾回收算法:標記清除法和複製算法。
標記清除法:如果該對象被使用,則標記爲0,沒有使用則標記爲1,GC會回收所有標記爲1的對象
複製算法:通過利用survivor,進行批量刪除
另外推薦一些文章:
堆內存結果:JVM內存結構--新生代及新生代裏的兩個Survivor區(下一輪S0與S1交換角色,如此循環往復)、常見調優參數
內存碎片化:內部碎片 && 外部碎片
根搜索算法:如何簡單理解GC roots和 gc的標記過程。