App下載

簡述Java虛擬機中的垃圾收集器,以及對象生存法則

猿友 2021-07-17 09:46:30 瀏覽數(shù) (2087)
反饋

在學(xué)習(xí) Java 堆內(nèi)存的時候,有了解過 Java 有個垃圾回收機制,會不定時回收堆內(nèi)存里面的垃圾。本篇文章將為您簡要概述 Java 在虛擬機中的垃圾收集器,以及對象生存法則,以下是詳細(xì)內(nèi)容。

Java與C++之間有一堵由內(nèi)存分配和垃圾收集技術(shù)所圍成的高墻,墻外面的人想進(jìn)去,墻里面的人卻想出來。

垃圾搜集器與內(nèi)存分配策略

一、概述

Java堆和方法區(qū)這兩個區(qū)域有著很顯著的不確定性:

1、一個接口的多個實現(xiàn)類需要的內(nèi)存可能會不一樣,一個方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣
2、只有處于運行期間,我們才能知道程序究竟會創(chuàng)建哪些對象,創(chuàng)建多少個對象,這部分內(nèi)存的分配和回收是動態(tài)的

垃圾收集器所關(guān)注的正是這部分的內(nèi)存該如何管理

2020072320140535.png

二、對象已死?

1、引用計數(shù)法

在對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器就加一;當(dāng)引用失效時,計數(shù)器就減一;任何時刻計數(shù)器為零的對象是不可能再被使用的。

引用計數(shù)器雖然占用了一些額外的內(nèi)存空間來進(jìn)行計數(shù),原理簡單,判定效率很高;

為什么主流Java虛擬機沒有使用引用計數(shù)器來管理內(nèi)存呢?

引用計數(shù)法看似簡單的算法有很多例外情況要考慮,必須配合大量額外處理才能保證正確的工作,比如單純的引用計數(shù)很難解決對象之間互相循環(huán)引用的問題。

引用計數(shù)器的缺陷

/**
 * @Author: yky
 * @CreateTime: 2020-12-13
 * @Description: 引用計數(shù)器的缺陷
 */
public class ReferenceCountingGC {
 public Object instance = null;
 private static final int _1MB = 1024 * 1024;
 /**
  * 這個成員變量唯一作用是占內(nèi)存
  */
 private byte[] bigSize = new byte[2 * _1MB];
 public static void testGC(){
  ReferenceCountingGC objA = new ReferenceCountingGC();
  ReferenceCountingGC objB = new ReferenceCountingGC();
  objA.instance = objB;
  objB.instance = objA;
  //發(fā)生GC,objA、objB能否被回收
  System.gc();
 }
}

運行代碼收查看日志信息發(fā)現(xiàn),這兩個對象均被回收虛擬機并沒有因為這兩個相互引用就放棄回收他們---->Java虛擬機并不是通過計數(shù)算法來判斷對象是否存活的;

2、可達(dá)性分析算法

該算法的核心思想:通過一系列稱為“GC Roots”的根對象作為起始節(jié)點集,從這些結(jié)點開始,根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路徑稱為“引用鏈”,如果某個對象到GC Roots間沒有任何引用鏈相連(圖論話來說從GC Roots到這個對象不可達(dá)時,則證明此對象是不可能再被使用的)

image

對象obj5obj6、obj7雖然有關(guān)聯(lián),但是他們到GC roots不可達(dá)因此他們會被判定為可回收對象

在 Java 語言中,可作為 GC Roots 的對象包括以下幾種

  • 虛擬機棧(棧中的本地變量表)中的引用對象,如各線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時變量等;
  • 方法區(qū)中的類靜態(tài)屬性引用的對象,如Java類的引用類型靜態(tài)變量;
  • 方法區(qū)中的常量引用的對象,如字符串常量里的引用;
  • 本地方法??侸NI(Navicat方法)引用的對象;
  • Java虛擬機內(nèi)部的引用
  • 所有被同步鎖(synchronized關(guān)鍵字)持有的對象
  • 反應(yīng)Java虛擬機內(nèi)部情況的JMXBean、JVMTI中注冊的回調(diào)、本地代碼緩存等;
  • 根據(jù)用戶所選的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域不同,還可以有其他對象“臨時性”地加入;

無論通過哪種算法判斷對象是否存活都和“引用”離不開關(guān)系。

1)強引用

是指在程序代碼之間普遍存在的引用賦值,Object obj = new Object();這種引用關(guān)系。
無論什么情況下,只要強引用關(guān)系還在,垃圾收集器就不會回收掉被引用的對象;

2)軟引用

用來描述一些還有用,但非必須的對象。只要軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出前,會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收;如果這次的回收還沒有足夠的空間,才會拋出內(nèi)存溢出的異常;

JDK1.2后提供SoftReference類實現(xiàn)軟引用:

Soft reference objects, which are cleared at the discretion of the garbage
collector in response to memory demand. Soft references are most often used
to implement memory-sensitive caches.

3)弱引用

弱引用也被用來描那些非必須對象,強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止,當(dāng)垃圾收集器開始工作,無論當(dāng)前內(nèi)存是足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象;

JDK1.2后WeakReference類用來實現(xiàn)弱引用:

Weak reference objects, which do not prevent their referents from being

made finalizable, finalized, and then reclaimed. Weak references are most

often used to implement canonicalizing mappings.

4)虛引用

也叫“幽靈引用”、“幻影引用”,最弱的一種引用關(guān)系

  • 一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用獲得一個對象的實例;
  • 為一個對象設(shè)置虛引用的唯一目的是為了能在這個對象被收集器回收時收到一個系統(tǒng)通知;

PhantomReference類來實現(xiàn)虛引用:

Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed. Phantom references are most often used to schedule post-mortem cleanup actions.

應(yīng)用需要讀取大量本地圖片

如果每次讀取圖片都從硬盤讀取,則會嚴(yán)重影響性能;解決方案:【軟引用或者弱引用】

Map<String,SoftReference<BitMap>> imp = new HashMap<String,SoftReference<BitMap>>

4、生存還是死亡?

在進(jìn)行過可達(dá)性分析后的對象也不一定是非死不可的,該對象進(jìn)行可達(dá)性分析后,發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈

  • 這個對象就會第一次被標(biāo)記起來;對對象是否必要執(zhí)行finalize()方法進(jìn)行判斷(已經(jīng)被虛擬機調(diào)用過finalize()方法或者沒有覆蓋finalize()方法都認(rèn)為是沒有必要執(zhí)行該finalize()方法)
  • F-Queue隊列中存放該對象,優(yōu)先級較低的Finalizer線程會去執(zhí)行它;Gc 會對這個隊列里面的對象再進(jìn)行一次標(biāo)記,如果在finalize方法中,對象沒有自己自救的話,它就會被標(biāo)記回收
  • finalize方法自救自己的辦法是:重新與引用鏈上面的任何一個對象建立連接;如把自己this賦值給某個類或?qū)ο蟮某蓡T變量


結(jié)果如下:

finalize method executed !
yes,I am still alive :)
no, i am dead :(

  • 并不鼓勵使用這種辦法來拯救對象,它的運行代價高昂,不確定性大,無法保證順序;
  • finalize方法能做的所有工作,try-finally也可以做的更好,更及時,所以希望忘記這個方法的存在;

5、回收方法區(qū)

很多人認(rèn)為方法區(qū)(或者HotSpot虛擬機中的元空間或永久代)是沒有垃圾收集行為的,《Java虛擬機規(guī)范》中確實說過可以不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集,而且在方法區(qū)進(jìn)行垃圾收集的“性價比”一般比較低:在堆中,尤其是在新生代中,常規(guī)應(yīng)用進(jìn)行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠(yuǎn)低于此。

方法區(qū)的垃圾收集主要回收兩部分:廢棄的常量和不再使用的類型;

  • 回收廢棄常量與回收J(rèn)ava堆中的對象非常類似。以常量池中字面量的回收為例:

假如一個字符串“Java”已經(jīng)進(jìn)入了常量池中,但是當(dāng)前系統(tǒng)沒有任何一個字符串對象的值是“Java”,換句話說是沒有任何String對象引用常量池中的“Java”常量,也沒有其他地方引用了這個字面量,如果在這時候發(fā)生內(nèi)存回收,而且必要的話,這個“Java”常量就會被系統(tǒng)清理出常量池。

  • 常量池中的其他類(接口)、方法、字段的符號引用也與此類似。

判定一個常量是否是“廢棄常量”比較簡單。而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件才能算是“無用的類”:

  1. 該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實例。
  2. 加載該類的ClassLoader已經(jīng)被回收。
  3. 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

虛擬機可以對滿足上述3個條件的無用類進(jìn)行回收,這里說的僅僅是“被允許”,而不是和對象一樣,不使用了就必然會回收。在大量使用反射、動態(tài)代理、CGLib等bytecode框架的場景,以及動態(tài)生成JSP和OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證不會被方法區(qū)造成過大的內(nèi)存壓力。

以上就是關(guān)于簡述 Java 虛擬機中的垃圾收集器,以及對象生存法則的全部內(nèi)容。如果想要了解更多相關(guān)Java虛擬機的內(nèi)容請搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持我們!

0 人點贊