JVM之垃圾回收
1.什麼是垃圾
垃圾是指沒有被任何其他對象引用的對象。不及時回收會導緻記憶體溢出。
2.java垃圾回收特點
1.自動記憶體管理
降低記憶體洩漏和記憶體溢出的風險
2.哪些區域重點關心垃圾回收
1.堆和方法區
堆:
頻繁回收年輕代
少回收老年代
方法區:基本不回收方法區
3.垃圾回收機制
1.垃圾标注階段
标注垃圾:垃圾标注階段主要是為了判斷對象是否存活
标注方式
1.引用計數法
2.可達性分析算法
1.引用計數法
引用計數法對每一個對象儲存一個整形的值,用來記錄對象被引用的次數,對象被引用則加一,引用失效則減一
對象的計數器如果為則證明該對象需要被垃圾回收
優點:
1.簡單,便于辨識;回收無延遲
缺點:
1.單獨儲存計數器,浪費空間
2.每次指派都需要計算,增加了時間的開銷
3.無法處理循環引用的問題
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI2EzX4xSZz91ZsAzNfRHLGZkRGZkRfJ3bs92YsAjMfVmepNHL9EFWTR0VZVTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLkNGO0kDM3EzM4YjZwUzNiFTZwQTOjFWYiRTNjRmZ0AzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如圖
1.myObject1引用第一個對象,myObject2引用第二個對象
2.第一個對象計數器和第二個對象計數器都為1
3.第一個對象裡的ref引用第二個對象,則第二個對象的計數器加一為2
4.第二個對象裡的ref引用第一個對象,則第一個對象的計數器再加一為2
5.當myObject1和myObject2都失效則第一個和第二個對象的計數器都減一都為1
6.此時兩個對象客觀沒有被引用
7.但兩個對象間的ref互相引用使得Gc永遠不會将他們标記為垃圾進而造成記憶體洩漏
2.可達性分析算法
可達性分析算法:也稱之為根搜尋算法、追蹤性垃圾收集
1.作用
可達性分析算法也同樣高效簡單,但可以解決對象之間互相引用(循環引用)造成的記憶體洩漏問題
JVM中用的垃圾标記算法就是可達性分析算法
原理
實作思路
1.可達性分析算法找到根對象(GC Roots)的起始點,按照從上到下的方式找到被根對象所連接配接(引用)的目标對象是否可達
2.記憶體中存活的對象會被根對象直接和間接的連接配接着,連接配接的路徑稱之為引用鍊。
3.如果對象沒有與任何一個根引用鍊連接配接,則說明在不可達,将該對象标記為垃圾。
可以作為根對象(GC Roots)有哪些元素?
1.虛拟機棧中的引用對象
2.本地方法棧的引用對象
3.方法區中靜态屬性的引用變量
4.方法區常量引用變量
5.同步鎖syschronized持有的對象
3.最終判斷垃圾是否要被清理還需要最終判斷
…流程圖
1.當對象不在根對象的引用鍊上,還需要再進行兩次标記判斷方可清除真正的垃圾
2.第一次标記
篩選的條件是此對象是否有必要執行finalize()方法。
如果對象沒有重寫finalize()或者已經執行過了,那麼會直接進行第二次标記并直接回收。
3.第二次标記
如果對象重寫了finalize()方法,會放入F-Queue的隊列之中,并在稍後由一條虛拟機自動建立的、低優先級的Finalizer線程去執行。這裡所謂的“執行”是指虛拟機會觸發這個方法,但并不承諾會等待它運作結束。
Finalize()方法是對象脫逃死亡命運的最後一次機會,稍後GC将對F-Queue中的對象進行第二次小規模标記,如果對象要在finalize()中成功拯救自己----隻要重新與引用鍊上的任何的一個對象建立關聯即可,在第二次标記時它将移除出“即将回收”的集合。如果對象這時候還沒逃脫,那基本上它就真的被回收了。
解釋:
finalize():對象銷毀前的回調方法。
2.垃圾回收階段
1.垃圾回收階段算法
①标記-清除算法(Mark-Sweep)
通過垃圾标注算法将垃圾進行标注,将其清除
(這裡的清除不是真正的清除,而是當有新的對象加載時對其進行替換)
缺點:由于新對象的大小和老對象不相同,可能會造成很多的記憶體碎片
②複制法
把記憶體分為兩塊相等區域,始終有一塊記憶體為空記憶體,把可用的記憶體(正在使用的對象)複制
到另一塊未使用記憶體中去(可使記憶體連續可用),清除之前的那塊區域所有對象。
優點
1.記憶體連續
2.不用使用标記算法
缺點
1.把記憶體分為兩塊,始終有一塊浪費(記憶體開銷大)
适用對象:記憶體中垃圾多有用的對象少,那麼這種算法效率很高
在新生代,對正常應用的垃圾回收,一次通常可以回收 70% - 99%
的記憶體空間。 回收成本效益很高。是以現在的商業虛拟機都是用這種
收集算法回收新生代
③标記壓縮算法
通過垃圾标記算法把垃圾标記出來,把可用的記憶體(正在使用的對象)複制到記憶體的一端,
清理邊界外的所有空間。
優點:
消除碎片,使對象可以連續儲存
消除了複制算法當中,記憶體減半的高額代價
缺點
效率較低
修改對象位置,還得修改引用此對象的對象
修改的過程需要暫停應用程式Swt
标記清除算法 | 複制算法 | 标記壓縮算法 | |
---|---|---|---|
速率 | 中 | 快 | 慢 |
消耗記憶體 | 中(因為又碎片) | 大(兩塊記憶體) | 小 |
是否移動 | 否 | 否 | 是 |
2.實際收集算法
①分代收集算法
前面講述的三種垃圾收集算法各有利弊,但各個代生命周期、記憶體大小等各種特點各不相同,是以根據各個代的不同特點去使用 不同的垃圾回收算法
年輕代:區域相對老年代較小,對象生命周期短、存活率低,回收頻繁(可以使用複制算法,y)
老年代:
②增量收集算法
當垃圾回收的時候,系統的所有其他線程都處于挂起狀态,使用者就會出現卡頓的現象,是以,會實行,每次隻清理一部分垃圾
然後就切換使用者線程,提高使用者的體驗,增量收集算法其實仍然是标記清除算法和标記壓縮算法。
缺點:由于使用者線程和垃圾回收線程頻繁的切換,導緻切換過程中的記憶體損耗過大,效率下降
3.垃圾回收相關概念
在預設情況下,通過 System.gc()者 Runtime.getRuntime().gc() 的調用,會
顯式觸發 Full GC,同時對老年代和新生代進行回收,嘗試釋放被丢棄對象占用的記憶體。
然而 System.gc()調用附帶一個免責聲明,無法保證對垃圾收集器的調用(不能確定立即生效)。
JVM 實作者可以通過 System.gc() 調用來決定 JVM 的 GC 行為。而一般情況下,垃圾回收應該是自動進行的,無須手動觸發,否則就太過于麻煩了。在一些
特殊情況下,如我們正在編寫一個性能基準,我們可以在運作之間調用
System.gc()。
4.Stop the World
Stop-the-World,簡稱 STW,指的是 GC 事件發生過程中,會産生應用程式的
停頓。停頓産生時整個應用程式線程都會被暫停,沒有任何響應,有點像卡死的
感覺,這個停頓稱為 STW。
可達性分析算法中枚舉根節點(GC Roots)會導緻所有 Java 執行線程停頓,為
什麼需要停頓所有 Java 執行線程呢?
1.分析工作必須在一個能確定一緻性的快照中進行
2.一緻性指整個分析期間整個執行系統看起來像被當機在某個時間點上
3.如果出現分析過程中對象引用關系還在不斷變化,則分析結果的準确性無法保
證
4.被 STW 中斷的應用程式線程會在完成 GC 之後恢複,頻繁中斷會讓使用者感覺
5.像是網速不快造成電影卡帶一樣,是以我們需要減少 STW 的發生。
6.STW 事件和采用哪款 GC 無關,所有的 GC 都有這個事件。
7.越優秀,回收效率越來越高,盡可能地縮短了暫停時間。
STW 是 JVM 在背景自動發起和自動完成的。在使用者不可見的情況下,把使用者正
常的工作線程全部停掉。