日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入理解JVM虚拟机读书笔记——垃圾回收算法

發(fā)布時間:2023/12/10 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解JVM虚拟机读书笔记——垃圾回收算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

:本文參考自周志明老師的著作《深入理解Java虛擬機(第3版)》,相關(guān)電子書可以關(guān)注WX公眾號,回復 001 獲取。

1. 如何判斷對象已死?

JVM 中判斷對象是否已經(jīng)死亡的算法主要有 2 種:引用計數(shù)法、可達性分析法

1.1 引用計數(shù)法

  • 如果一個對象被其他變量所引用,則讓該對象的引用計數(shù)+1,如果該對象被引用2次則其引用計數(shù)為2,依次類推。
  • 某個變量不再引用該對象,則讓該對象的引用計數(shù)-1,當該對象的引用計數(shù)變?yōu)?時,則表示該對象沒用被其他變量所引用,這時候該對象就可以被作為垃圾進行回收。

引用計數(shù)法弊端:循環(huán)引用時,兩個對象的引用計數(shù)都為1,導致兩個對象都無法被釋放回收。最終就會造成內(nèi)存泄漏!

1.2 可達性分析算法

可達性分析算法就是JVM中判斷對象是否是垃圾的算法:該算法首先要確定GC Root(根對象,就是肯定不會被當成垃圾回收的對象)。

在垃圾回收之前,JVM會先對堆中的所有對象進行掃描,判斷每一個對象是否能被GC Root直接或者間接的引用,如果能被根對象直接或間接引用則表示該對象不能被垃圾回收,反之則表示該對象可以被回收:

  • JVM中的垃圾回收器通過可達性分析來探索所有存活的對象。
  • 掃描堆中的對象,看能否沿著GC Root為起點的引用鏈找到該對象,如果找不到,則表示可以回收,否則就可以回收。
  • **在Java技術(shù)體系里面,固定可作為GC Roots的對象包括以下幾種 **:
    • 虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時變量等。
    • 在方法區(qū)中類靜態(tài)屬性引用的對象,譬如Java類的引用類型靜態(tài)變量。
    • 在方法區(qū)中常量引用的對象,譬如字符串常量池(String Table)里的引用。
    • 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。
    • 所有被同步鎖(synchronized關(guān)鍵字)持有的對象。
    • Java虛擬機內(nèi)部的引用,如基本數(shù)據(jù)類型對應的Class對象,一些常駐的異常對象(比如 NullPointExcepiton、OutOfMemoryError)等,還有系統(tǒng)類加載器。

1.3 Java中的五種引用

強引用

上圖實心線表示強引用:比如,new 一個對象M,將對象M通過=(賦值運算符),賦值給某個變量m,則變量m就強引用了對象M。

強引用的特點:只要沿著GC Root的引用鏈能夠找到該對象,就不會被垃圾回收;只有當GC Root都不引用該對象時,才會回收強引用對象。

  • 如上圖B、C對象都不引用A1對象時,A1對象才會被回收

軟引用

上圖中寬虛線所表示的就是軟引用:

軟引用的特點:當GC Root指向軟引用對象時,若內(nèi)存不足,則會回收軟引用所引用的對象

  • 如上圖如果B對象不再引用A2對象且內(nèi)存不足時,軟引用所引用的A2對象就會被回收。

軟引用的使用

public class Demo1 {public static void main(String[] args) {final int _4M = 4*1024*1024;// 軟引用對象內(nèi)部包裝new byte[_4M]對象SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);// 這時List 跟 SoftReference之間是強引用,SoftReference 跟 byte[] 之間是軟引用List<SoftReference<byte[]>> list = new ArrayList<>();} }

如果在垃圾回收時發(fā)現(xiàn)內(nèi)存不足,在回收軟引用所指向的對象時,軟引用本身不會被清理

如果想要清理軟引用,需要使用引用隊列

public class Demo04 {final static int _4M = 4 * 1024 * 1024;public static void main(String[] args) {// List和SoftReference是強引用,而SoftReference和byte數(shù)組則是軟引用List<SoftReference<byte[]>> list = new ArrayList<>();// 引用隊列,用于移除引用為空的軟引用對象ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 5; i++) {// 關(guān)聯(lián)引用隊列,當軟引用所關(guān)聯(lián)的 byte[]被回收時,軟引用自己會假如到queue中去SoftReference<byte[]> ref = new SoftReference<>(new byte[_4M], queue);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}// 遍歷,從引用隊列中獲取無用的軟引用對象,并移除Reference<? extends byte[]> poll = queue.poll();while (poll != null) {// 引用隊列不為空,則從集合中移除該元素list.remove(poll);// 移動到引用隊列中的下一個元素poll = queue.poll();}System.out.println("==========================");for (SoftReference<byte[]> reference : list) {System.out.println(reference.get());}} }

**大概思路為:**查看引用隊列中有無軟引用,如果有,則將該軟引用從存放它的集合中移除(這里為一個list集合)。

弱引用

只有當弱引用引用該對象,在垃圾回收時,無論內(nèi)存是否充足,都會回收弱引用所引用的對象。

  • 如上圖如果B對象不再引用A3對象,則A3對象會被回收。

弱引用的使用和軟引用類似,只是將 SoftReference 換為了 WeakReference。

虛引用

當引用的對象ByteBuffer被垃圾回收以后,虛引用對象Cleaner就會被放入引用隊列中:

然后調(diào)用Cleaner的clean方法(Unsafe.freeMemory())來釋放直接內(nèi)存:

  • 虛引用的一個體現(xiàn)是釋放直接內(nèi)存所分配的內(nèi)存,當引用的對象ByteBuffer被垃圾回收以后,虛引用對象Cleaner就會被放入引用隊列中,然后調(diào)用Cleaner的clean方法來釋放直接內(nèi)存。
  • 如上圖,B對象不再引用ByteBuffer對象,ByteBuffer就會被回收。但是直接內(nèi)存中的內(nèi)存還未被回收。這時需要將虛引用對象Cleaner放入引用隊列中,然后調(diào)用它的clean方法來釋放直接內(nèi)存。

終結(jié)器引用

所有的類都繼承自Object類,Object類有一個finalize()方法。當某個對象不再被其他的對象所引用時,會先將終結(jié)器引用對象放入引用隊列中,然后根據(jù)終結(jié)器引用對象找到它所引用的對象,然后調(diào)用該對象的finalize()方法。調(diào)用以后,該對象就可以被垃圾回收了。

  • 如上圖,B對象不再引用A4對象。這是終結(jié)器對象就會被放入引用隊列中,引用隊列會根據(jù)它,找到它所引用的對象。然后調(diào)用被引用對象的finalize()方法。調(diào)用以后,該對象就可以被垃圾回收了。

引用隊列

  • 軟引用和弱引用可以配合引用隊列(也可以不配合):
    • 弱引用虛引用所引用的對象被回收以后,會將這些引用放入引用隊列中,方便一起回收這些軟/弱引用對象。
  • 虛引用和終結(jié)器引用必須配合引用隊列:
    • 虛引用和終結(jié)器引用在使用時會關(guān)聯(lián)一個引用隊列。

1.4 回收方法區(qū)

方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量不再使用的類型。舉個常量池中字面量回收的例子,假如一個字符串“java”曾經(jīng)進入常量池中,但是當前系統(tǒng)又沒有任何一個字符串對象的值是“java”,換句話說,已經(jīng)沒有任何字符串對象引用常量池中的“java”常量,且虛擬機中也沒有其他地方引用這個字面量。如果在這時發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話,這個“java”常量就將會被系統(tǒng)清理出常量池。常量池中其他類(接口)、方法、字段的符號引用也與此類似。

判定一個常量是否“廢棄”需要同時滿足下面三個條件:

  • 該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實例。
  • 加載該類的類加載器已經(jīng)被回收,這個條件除非是經(jīng)過精心設計的可替換類加載器的場景,如 OSGi、JSP 的重加載等,否則通常是很難達成的。
  • 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法

2. 垃圾回收算法

在Java堆劃分出不同的區(qū)域之后,垃圾收集器才可以每次只回收其中某一個或者某些部分的區(qū)域——因而才有了“Minor GC”“Major GC”“Full GC”這樣的回收類型的劃分;也才能夠針對不同的區(qū)域安排與里面存儲對象存亡特征相匹配的垃圾收集算法——因而發(fā)展出了“標記-復制算法”“標記-清除算法”“標記-整理算法”等針對性的垃圾收集算法。

四種GC概念的介紹:

■ 新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集。

■ 老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行為。

■ 混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行為。

■ 整堆收集(Full GC):收集整個Java堆和方法區(qū)的垃圾收集。

下面逐個介紹下4種回收算法:

2.1 標記-清除

定義:標記清除算法顧名思義,是指在虛擬機執(zhí)行垃圾回收的過程中,先采用標記算法確定可回收對象,然后垃圾收集器根據(jù)標識,清除相應的內(nèi)容,給堆內(nèi)存騰出相應的空間。

  • 這里的騰出內(nèi)存空間并不是將內(nèi)存空間的字節(jié)清 0,而是記錄下這段內(nèi)存的起始結(jié)束地址,下次分配內(nèi)存的時候,會直接覆蓋這段內(nèi)存。

缺點:(容易產(chǎn)生大量的內(nèi)存碎片,可能無法滿足大對象的內(nèi)存分配,一旦導致無法分配對象,那就會導致jvm啟動gc)。

  • 第一個是執(zhí)行效率不穩(wěn)定,如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過

    程的執(zhí)行效率都隨對象數(shù)量增長而降低;

  • 第二個是內(nèi)存空間的碎片化問題,標記、清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導致當以后在程序運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。

標記-清除算法的執(zhí)行過程(書中配圖):

2.2 標記-整理

標記-整理:會將不被GC Root引用的對象回收,清除其占用的內(nèi)存空間。然后整理剩余的對象,可以有效避免因內(nèi)存碎片而導致的問題,但是牽扯到對象的整理移動,需要消耗一定的時間,所以效率較低。

標記-整理算法的執(zhí)行過程(書中配圖):

2.3 標記-復制


當需要回收對象時,先將GC Root直接引用的的對象(不需要回收)放入TO中:


然后清除FROM中的需要回收的對象:

最后 交換 FROMTO 的位置:(FROM換成TO,TO換成FROM)

復制算法:將內(nèi)存分為等大小的兩個區(qū)域,FROMTO(TO中為空)。先將被GC Root引用的對象從FROM放入TO中,再回收不被GC Root引用的對象。然后交換FROM和TO。這樣也可以避免內(nèi)存碎片的問題,但是會占用雙倍的內(nèi)存空間

標記-復制算法的執(zhí)行過程(書中配圖):

2.4 分代回收

把分代收集理論具體放到現(xiàn)在的商用Java虛擬機里,設計者一般至少會把Java堆劃分為新生代(Young Generation)和老年代(Old Generation)兩個區(qū)域,顧名思義,在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。

長時間使用的對象放在老年代中(長時間回收一次,回收花費時間久),用完即可丟棄的對象放在新生代中(頻繁需要回收,回收速度相對較快),如下圖所示:

回收流程

新創(chuàng)建的對象都被放在了新生代的伊甸園中:

當伊甸園中的內(nèi)存不足時,就會進行一次垃圾回收,這時的回收叫做 Minor GC

Minor GC 會將伊甸園和幸存區(qū)FROM仍需要存活的對象復制到 幸存區(qū) TO中, 并讓其壽命加1,再交換FROM和TO

伊甸園中不需要存活的對象清除:

交換FROM和TO

同理,繼續(xù)向伊甸園新增對象,如果滿了,則進行第二次Minor GC:

流程相同,仍需要存活的對象壽命+1:(下圖中FROM中壽命為1的對象是新從伊甸園復制過來的,而不是原來幸存區(qū)FROM中的壽命為1的對象,這里只是靜態(tài)圖片不好展示,只能用文字描述了)

再次創(chuàng)建對象,若新生代的伊甸園又滿了,則會再次觸發(fā) Minor GC(會觸發(fā) stop the world, 暫停其他用戶線程,只讓垃圾回收線程工作),這時不僅會回收伊甸園中的垃圾,還會回收幸存區(qū)中的垃圾,再將活躍對象復制到幸存區(qū)TO中。回收以后會交換兩個幸存區(qū),并讓幸存區(qū)中的對象壽命加1

如果幸存區(qū)中的對象的壽命超過某個閾值(最大為15,4bit),就會被放入老年代中:

如果新生代老年代中的內(nèi)存都滿了,就會先觸發(fā)Minor Gc,再觸發(fā)Full GC,掃描新生代和老年代中所有不再使用的對象并回收:

分代回收小結(jié):

  • 新創(chuàng)建的對象首先會被分配在伊甸園區(qū)域。
  • 新生代空間不足時,觸發(fā)Minor GC,伊甸園和 FROM幸存區(qū)需要存活的對象會被COPY到TO幸存區(qū)中,存活的對象壽命+1,并且交換FROM和TO。
  • Minor GC會引發(fā) Stop The World:暫停其他用戶的線程,等待垃圾回收結(jié)束后,用戶線程才可以恢復執(zhí)行。
  • 當對象壽命超過閾值15時,會晉升至老年代。
  • 如果新生代、老年代中的內(nèi)存都滿了,就會先觸發(fā)Minor GC,再觸發(fā)Full GC,掃描新生代和老年代中所有不再使用的對象并回收。

后續(xù)會陸續(xù)更新,這本書的筆記記的差不多了,排版和格式需要花時間整理,文章都會同步到公眾號上,也歡迎大家通過公眾號加入我的交流qun互相討論jvm這塊的知識內(nèi)容!

總結(jié)

以上是生活随笔為你收集整理的深入理解JVM虚拟机读书笔记——垃圾回收算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。