欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java超詳細分析垃圾回收機制

 更新時間:2022年05月10日 09:57:14   作者:羨羨ˇ  
一個運行中的程序,?產(chǎn)生的對象是大量的,?如果對象不被繼續(xù)使用,?就會成為垃圾,?最后越堆越多,?最后占滿內(nèi)存,?所以我們要對這些垃圾進行回收,保持程序的正常運行

前言

在前面我們對類加載, 運行時數(shù)據(jù)區(qū) ,執(zhí)行引擎等作了詳細的介紹 , 這節(jié)我們來看另一重點 : 垃圾回收.

垃圾回收概述

垃圾回收是java的招牌能力 ,極大的提高了開發(fā)效率, java是自動化的垃圾回收, 其他語言有的則需要程序員手動回收 , 那么什么是垃圾呢?

垃圾是指在運行程序中沒有任何引用指向的對象,這個對象就是需要被回收的垃圾。如果不及時對內(nèi)存中的垃圾進行清理,那么,這些垃圾對象所占的內(nèi)存空間會一直保留到應用程序結束,被保留的空間無法被其他對象使用。甚至可能導致內(nèi)存溢出。

在早期C/C++時代, 需要手動回收垃圾, 如果一旦疏忽, 還會導致內(nèi)存泄漏問題

這里引出了兩個名詞 , 內(nèi)存溢出和內(nèi)存泄漏, 先來解釋這兩個意思

內(nèi)存溢出和內(nèi)存泄漏

內(nèi)存溢出 : 內(nèi)存被占滿, 內(nèi)存不夠用了

內(nèi)存泄漏 : 程序中存在不被使用的對象(但GC無法判定它們?yōu)槔?, GC(垃圾回收)無法去收集清理它們, 這就導致這塊空間一直被占用, 無法釋放出來,這就是內(nèi)存泄漏

一些提供 close() 的對象等, 例如在JDBC中 Connection 沒有去關閉等, 這樣的越積越多就導致內(nèi)存泄漏問題 , 內(nèi)存泄漏越來越多最終會導致內(nèi)存溢出問題(泄漏逐漸蠶食內(nèi)存)

實際情況很多時候一些不太好的實踐(或疏忽)會導致對象的生命周期變得很長甚至導致OOM,也可以叫做寬泛意義上的“內(nèi)存泄漏”。

在單例模式下, 單例的生命周期和程序一樣長,如果持有對外部對象的引用的話,那么這個外部對象是不能被回收的,則會導致內(nèi)存泄漏的產(chǎn)生

那么GC主要關心哪塊區(qū)域的收集呢?

在前面運行時數(shù)據(jù)區(qū)我們說過 , 可以總結如下 : 頻繁回收新生區(qū), 較少回收老年區(qū) , 基本不收集方法區(qū)(元空間)

垃圾回收算法

在垃圾回收時, 分為兩個階段, 標記階段和回收階段 , 這兩個階段使用了不同的算法思想來區(qū)分垃圾 , 我們來依次論述

標記階段

想要清除垃圾, 我們先得了解什么是垃圾 , 那么如何來判斷一個對象是否是垃圾呢?簡單來說,當一個對象已經(jīng)不再被任何的存活對象繼續(xù)引用時, 就已經(jīng)是垃圾了

標記階段有兩種算法 : 引用計數(shù)算法和可達性分析算法

引用計數(shù)算法

這個算法思想比較簡單 , 就是使用一個計數(shù)器, 如果有一個引用指向這個對象, 那么計數(shù)器就加1 , 引用失效計數(shù)器就減一 . 計數(shù)器為0 則表示該對象可回收

但是這個算法有一個嚴重的問題, 此問題也導致我們現(xiàn)如今已不再使用此算法 : 無法處理循環(huán)依賴問題 , 這是一個致命缺陷 , 什么是循環(huán)依賴問題呢 ?

如圖 , 引用P 指向對象A , 而對象A又指向對象B, 對象B又指向對象C , 對象C繼續(xù)指向對象A, 此時將引用 P 置null , 此時這三個對象形成了依賴閉環(huán), 但都沒有直接的引用去指向它們, 這時如果采用引用計數(shù)算法, 這三個都不為0 , 也就無法被回收,出現(xiàn)內(nèi)存泄漏問題

所以在這種條件下, 我們提出了

可達性分析算法(根搜索算法、追蹤性垃圾收集)

相對于引用計數(shù)算法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高效等特點,更重要的是該算法可以有效地解決在引用計數(shù)算法中循環(huán)引用的問題 ,防止內(nèi)存泄漏的發(fā)生。

基本思路如下 :

1.可達性分析算法是以根對象(GCRoots)為起始點,按照從上至下的方式搜索被根對象集合所連接的目標對象是否可達。

2.使用可達性分析算法后,內(nèi)存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈(Reference Chain)

3.如果目標對象沒有任何引用鏈相連,則是不可達的,就意味著該對象己經(jīng)死亡, 可以標記為垃圾對象。

4.在可達性分析算法中,只有能夠被根對象集合直接或者間接連接的對象才是存活對象。

也就是從根對象往下開始搜索 , 如果目標對象不存在引用鏈 ,則判斷可以回收

public static void main(String[] args) {
   List<Integer> list = new ArrayList(); 
   while(true){ 
      list.add(new Random().nextInt()); 
   } 
}

以上程序, list指向的對象作為根對象, 死循環(huán)生成的每個隨機數(shù)都存在引用鏈, 所以此程序最終會導致內(nèi)存溢出

public static void main(String[] args) { 
   while(true){ 
      Random r = new Random(); 
   } 
}

上面的雖然存在引用指向, 但每次循環(huán)引用都會改變指向, 也就不存在引用鏈 , 所以每次都會被回收, 不會導致無法回收的問題

那么哪些對象可以作為根對象呢?

1.虛擬機棧中引用的對象 比如:各個線程被調用的方法中使用到的參數(shù)、局部變量等。

2.本地方法棧內(nèi) JNI(通常說的本地方法)引用的對象

3.方法區(qū)中類靜態(tài)屬性引用的對象,比如:Java 類的引用類型靜態(tài)變量

4.方法區(qū)中常量引用的對象,比如:字符串常量池(StringTable)里的引用

5.所有被同步鎖 synchronized 持有的對象

6.Java 虛擬機內(nèi)部的引用。

7.基 本 數(shù) 據(jù) 類 型 對 應 的 Class 對 象 ,一些常駐 的 異 常 對 象 ( 如 :NullPointerException、OutofMemoryError),系統(tǒng)類加載器。

總結就是 : 棧, 方法區(qū) ,字符串常量池等地方對堆空間進行引用的,都可以作為 GC Roots 進行可達性分析

以上可作為根對象的都有這樣的特點 : 活躍, 不可變,存活時間長, 在程序中至關重要. 例如: 靜態(tài)成員等, 同步鎖持有的對象等, 這些都是不能被隨意回收的

另外在可達性分析算法枚舉根節(jié)點(root 對象)時會產(chǎn)生STW(Stop-the-World) , 關于STW , 我們下面來介紹

STW(Stop-the-World)

指的是 GC 事件發(fā)生過程中,會產(chǎn)生應用程序的停頓。停頓產(chǎn)生時整個應用程序線程都會被暫停,沒有任何響應,有點像卡死的感覺,這個停頓稱為 STW。

我們再次回到上面的問題 , 執(zhí)行可達性分析算法為什么需要停頓所有java執(zhí)行線程呢(STW)?

因為對象的狀態(tài)是不停變化了 , 如果在我們確定哪個對象是垃圾的時候, 此對象的狀態(tài)還在不停變化時, 這樣是沒法分析的 , 此時我們?nèi)シ治瞿鼙3忠恢滦缘囊粋€快照(某一時間點的執(zhí)行狀態(tài)) ,從而得到一個比較準確的結果

需要注意的是, STW是無法避免的 , 和采用哪款GC也無關, 我們只能去盡量減少停頓的時間,STW 是 JVM 在后臺自動發(fā)起和自動完成的。在用戶不可見的情況下,把用戶正常的工作線程全部停掉。

了解了這些, 接著我們來看回收階段的算法

回收階段

當GC識別了垃圾之后, 接著就是垃圾回收了, 這里采用了 3 種不同的算法,接著來介紹

標記-清除算法

顧名思義, 包括兩個階段 : 標記和清除, 不過此處的標記和垃圾標記階段的標記可是不同的

標記:Collector 從引用根節(jié)點開始遍歷,標記所有被引用的對象。一般是在對象的Header 中記錄為可達對象。(注意:標記的是被引用的對象,也就是可達對象,并非標記的是即將被清除的垃圾對象)。

清除:Collector 對堆內(nèi)存從頭到尾進行線性的遍歷,如果發(fā)現(xiàn)某個對象在其 Header 中沒有標記為可達對象,則將其回收。

圖示如下 :

這里也需要注意, 此清除也不是簡單的清除, 發(fā)現(xiàn)了垃圾對象后, 會先維護一個空列表用來記錄垃圾的地址,下次有新對象需要加載時,判斷垃圾的位置空間是否夠,如果夠,就存放(也就是覆蓋原有的地址)。

標記 - 清除算法比較基礎容易理解, 另外它也有很多缺點 , 例如效率不高, GC時存在STW . 另外可以注意到, 這樣做會造成空間不是連續(xù)的(空間碎片化) , 此時就需要一個空列表來記錄這些地址

復制算法

為了解決標記 - 清除算法在效率方面的缺陷 , 復制算法采用將內(nèi)存按容量劃分的方式, 劃分成大小相等的兩塊 , 每次只使用其中的一塊. 算法思想如下 :

將正在使用的存活對象全部復制到另一塊未被使用空間 , 擺放整齊 , 然后清空此空間所有對象

復制算法優(yōu)點是 : 簡單高效, 不會出現(xiàn)"碎片"問題

缺點當然也很明顯 : 需要兩倍的內(nèi)存空間 , 開銷較大 , 另外GC如果采用 G1 垃圾回收器的話 , 它將空間拆成了很多份, 如果采用復制算法, 還需要維護各區(qū)之間的關系

對于復制算法的思想而言, 如果對老年區(qū)采用此算法, 老年區(qū)對象較多,存活周期較長, 這時效率就會有點低 , 所以復制算法大多用于 young 區(qū), 幸存者0 區(qū)和幸存者1 區(qū)之間的相互轉換中

標記-壓縮算法

上面我們說過, 復制算法相對于老年區(qū)來說, 效率就有點低了 , 所以針對老年區(qū)的回收, 就采用了標記 - 壓縮算法 , 標記 - 清除算法雖然也可以應用于老年區(qū), 但是效率低下, 容易產(chǎn)生內(nèi)存碎片

算法思想 :

第一階段和標記清除算法一樣,從根節(jié)點開始標記所有被引用對象

第二階段將所有的存活對象壓縮到內(nèi)存的一端,按順序排放。之后,清理邊界外所有的空間。

標記-壓縮算法的最終效果等同于標記-清除算法執(zhí)行完成后,再進行一次內(nèi)存碎片整理,因此,也可以把它稱為標記-清除-壓縮(Mark-Sweep-Compact)算法 , 標記- 壓縮是移動式的 , 將對象在內(nèi)存中依次排列比維護一個空列表少了不少開銷(如果對象排列整齊,當我們需要給新對象分配內(nèi)存時,JVM 只需要持有一個內(nèi)存的起始地址即可)

優(yōu)點 : 相對于標記 -清除算法避免了內(nèi)存碎片化 , 相對于復制算法, 避免開辟額外的空間

缺點 : 從效率上來說是不如復制算法的 , 移動時, 如果存在對象相互引用, 則需要調整引用的位置, 另外移動過程中也會有STW

三種算法的比較

復制算法是效率最高的 , 但是花費空間最大

標記 - 壓縮算法雖然較為兼顧 , 但效率也變低, 比標記- 清除多了個整理內(nèi)存的過程, 比復制算法多了標記的過程

總結

到此關于 jvm 的大部分已經(jīng)講述完了, 在后續(xù)會再補充兩個部分 : 對象的finalize() 方法機制和對象的引用 ,感謝您的閱讀與關注 ,謝謝 !!!

到此這篇關于Java超詳細分析垃圾回收機制的文章就介紹到這了,更多相關Java垃圾回收內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • mybatis?plus中如何編寫sql語句

    mybatis?plus中如何編寫sql語句

    這篇文章主要介紹了mybatis?plus中如何編寫sql語句,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 解決myBatis generator逆向生成沒有根據(jù)主鍵的select,update和delete問題

    解決myBatis generator逆向生成沒有根據(jù)主鍵的select,update和delete問題

    這篇文章主要介紹了解決myBatis generator逆向生成沒有根據(jù)主鍵的select,update和delete問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • java編寫貪吃蛇小游戲

    java編寫貪吃蛇小游戲

    貪吃蛇是經(jīng)典手機游戲,既簡單又耐玩。通過控制蛇頭方向吃蛋,使得蛇變長,從而獲得積分。今天我們就來用java來實現(xiàn)下貪吃蛇小游戲,有需要的小伙伴可以參考下
    2015-03-03
  • Java中Spring的Security使用詳解

    Java中Spring的Security使用詳解

    這篇文章主要介紹了Java中Spring的Security使用詳解,在web應用開發(fā)中,安全無疑是十分重要的,選擇Spring Security來保護web應用是一個非常好的選擇,需要的朋友可以參考下
    2023-07-07
  • Spring中@EnableScheduling注解的工作原理詳解

    Spring中@EnableScheduling注解的工作原理詳解

    這篇文章主要介紹了Spring中@EnableScheduling注解的工作原理詳解,@EnableScheduling是 Spring Framework 提供的一個注解,用于啟用Spring的定時任務(Scheduling)功能,需要的朋友可以參考下
    2024-01-01
  • uploadify java實現(xiàn)多文件上傳和預覽

    uploadify java實現(xiàn)多文件上傳和預覽

    這篇文章主要為大家詳細介紹了java結合uploadify實現(xiàn)多文件上傳和預覽的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java的MyBatis框架中Mapper映射配置的使用及原理解析

    Java的MyBatis框架中Mapper映射配置的使用及原理解析

    Mapper用于映射SQL語句,可以說是MyBatis操作數(shù)據(jù)庫的核心特性之一,這里我們來討論Java的MyBatis框架中Mapper映射配置的使用及原理解析,包括對mapper的xml配置文件的讀取流程解讀.
    2016-06-06
  • 將一個數(shù)組按照固定大小進行拆分成數(shù)組的方法

    將一個數(shù)組按照固定大小進行拆分成數(shù)組的方法

    下面小編就為大家?guī)硪黄獙⒁粋€數(shù)組按照固定大小進行拆分成數(shù)組的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11
  • 利用Java實現(xiàn)帶GUI的氣泡詩詞特效

    利用Java實現(xiàn)帶GUI的氣泡詩詞特效

    這篇文章主要為大家介紹了如何利用Java語言實現(xiàn)帶GUI的氣泡詩詞特效,文中的示例代碼講解詳細,對我們學習Java有一定幫助,感興趣的可以了解一下
    2022-08-08
  • spring boot注解方式使用redis緩存操作示例

    spring boot注解方式使用redis緩存操作示例

    這篇文章主要介紹了spring boot注解方式使用redis緩存操作,結合實例形式分析了spring boot注解方式使用redis緩存相關的依賴庫引入、注解使用及redis緩存相關操作技巧,需要的朋友可以參考下
    2019-11-11

最新評論