JAVA 垃圾回收機制(一) --- 對象回收與算法初識

基本面試都會用到,假如面試官來一句,說說你對Java垃圾回收機制的瞭解,若是你沒概念,基本涼了,一些大廠最後面也基本會問這個問題,通常是爲了幫你定級。java

系列文章分3個部分面試

Java JVM -- 看這篇就夠了算法

JAVA 垃圾回收機制(一) --- 對象回收與算法初識bash

JAVA 垃圾回收機制(二) --- GC回收具體實現spa

1、概念

這裏說的GC回收,指的是 Java 堆的地方,這是一篇你能看懂 Java JVM 文章 中,咱們知道了程序計算器,虛擬機棧和本地方法棧都是隨線程開啓,隨線程關閉的,所以這幾塊區域的內存分配和回收都具有肯定性。而Java 堆和方法區則不同,一個接口中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,只有程序在運行時,才知道建立了哪些對象,這部份內存的分配和回收都是動態的,垃圾收集器所關注的就是這部份內存。 而 GC 關注的也就3個點.net

  • 哪些對象須要回收
  • 何時回收
  • 如何回收

2、哪些對象須要回收

怎麼判斷對象是「存活」 的,仍是已經"死亡"呢?主要有如下方法:線程

2.1 引用計算算法

給對象添加一個引用計算器,每當有一個地方引用它,則加1,當引用失效,則減1;任什麼時候刻計算器爲0的對象就是不可能再被使用的。但它很難解決對象之間相互循環引用的問題,因此主流的Java虛擬機都沒有采用這種算法。3d

2.2 可達性分析算法

經過一系列的 "GC Roots" 的對象做爲起始點,從這些起始點開始向下搜索,搜索走過的路徑被稱爲引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈項鍊,即GC Roots 不可達,則證實此對象是不可用的,以下圖(java 虛擬機第三版) code

在這裏插入圖片描述
在 Java 虛擬機中,可做爲 GC Roots 的對象包含如下幾種

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中的類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中 JNI (Native方法)引用的對象

關於這幾個區的介紹,這是一篇你能看懂 Java JVMcdn

引用判斷

不管是引用計數算法,仍是可達性分析算法,對象是否存貨都跟 "引用" (reference)有關,在JDK1.2 以後,引用可分爲如下4個

  • 強引用:直接 new ,如 new Object(); 只要這類強引用還在,對象就不會回收
  • 軟引用:(SoftReference類)用來描述一些還有用但非必需的對象;當未來發生內存溢出以前,系統會把這些有軟引用的對象列入回收範圍中進行二次回收,若是此次回收尚未足夠的內存,則內存溢出報異常;
  • 弱引用:(WakeReference類) 被弱引用的對象只能生存到下一次垃圾收集發生以前,當垃圾收集器開始工做時,不管內存是否足夠,都會對這些對象進行回收。
  • 虛引用(幽靈引用/幻影引用):(PhantomReference類) 一個虛引用的對象,它的惟一目的就是能在這個對象被收集器回收時收到一個對象實例,即一個對象是否有虛引用,徹底不會對其生存時間構成影響。

3、何時回收

在可達性分析算法不可達的對象,也不必定"非死不可',它會經歷兩次標記,一是當不可達 GC Roots 時,標記一次並篩選,篩選的條件是該對象是不是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或者已經執行過 finalize 方法時,則認爲此對象會被回收。以下圖

在這裏插入圖片描述
這裏的執行,是指虛擬機會觸發這個 finalize 方法,但並不會等待它結束,這是由於 finalize 方法執行緩慢,可能會致使 F-Queue其餘對象處於等待,甚至是崩潰。 若是在執行 finalize 時,對象從新和其餘對象關聯上了,則成功拯救了本身 注意: 任何一個對象的 finalize() 只會被系統調用一次,下次不會再執行

3.2 回收方法區

上面都是對 Java 堆進行回收,雖然說 Java 堆 能夠回收70%~95%的空間,但方法區一樣能夠回收一些資源,方法區主要回收兩個部分廢棄常量無用的類廢棄常量: 當前系統沒有任何一個 String 對象引用這個 "abc" 的常量池,也沒有其餘地方引用了這個字面量,這時能夠判斷這個常量是能夠廢棄回收的;其餘常量池中的接口,字段的符號引用也以此相似 無用的類: 無用類的回收,須要知足三個條件

  1. 是該類全部的實例都已經被回收,也就是Java堆中部存在實例
  2. 加載該類的 ClassLoader 已經被回收
  3. 對應的 java.lang.class 對象沒有再任何地方被引用,也沒法經過反射拿到該類 固然,這裏跟Java堆同樣,也只是 "能夠回收"了,是否對類進行回收,能夠對虛擬機的參數進行設置,這裏就不細講了。

4、如何回收

關於對象的回收,就涉及到 垃圾收集算法了。能夠參考 JAVA 垃圾回收機制(二) --- GC回收具體實現 第二節

4.1 分代算法

在說明這些回收機制以前,我想說明之前就據說過的 新生代和老年代的問題。那麼這個分代算法是怎麼回事呢? 首先先要理解,新生代和老年代都是一個內存空間,由參數配置,只是能夠根據算法,決定對象是在新生代仍是在老年代的內存區域!!! 一塊內存能夠分爲3個區域,一個 Eden 和兩個 Survivor 區,當對象在 Eden 建立,並經理了第一次 GC 以後仍然存活,而且能被 survivor 區容納的話,將移到 survivor 區;對象在Survivor 區中「熬過」一次,年齡增長1,當增長到 15 歲(默認,這個閾值能夠經過 -XX:MaxTenuringThreshold 設置),就會晉升成老年代的對象。 從這裏來看,能夠獲得兩個結論

  • 新生代:對象少,垃圾多
  • 老年代:對象多,垃圾少 (畢竟經歷了10幾回GC的老油條)

爲何是劃分紅一個 Eden 和 兩個 Survivor 呢,能夠查看這篇文章: blog.csdn.net/qq_35181209…

4.2 標記-清除算法

這裏的標記指的是對象進過前面第三章咱們介紹的那樣,已經能夠斷定就是能夠回收的意思;這個算法首先標記處全部須要回收的對象,在標記完成以後統一回收全部被標記的對象。 看似美好,實則否則,主要有如下兩個缺點:

  1. 效率問題:標記和清除兩個效率都不高
  2. 空間問題:標記清除後,會產生大量空間碎片,在大對象須要分配空間時,找不到內存,從而又觸發 GC 操做。

標記和清除的執行過程以下圖:

在這裏插入圖片描述
標記-清除算法是GC回收算法的基礎,後面產生的算法都是基於它的缺點改進的

4.3 複製算法

複製算法能夠分爲等比例的和8:2兩種

4.3.1 1:1 比例

基於 標記-清除算法的效率問題,複製算法出現的。 這種算法是把內存分爲相等的兩塊,一塊用來存儲對象,當GC操做後,把還存留的對象移動到未存對象的那塊內存區域,再把使用過的內存清掉。這樣每次都對整個半區進行內存回收,就不用擔憂內存碎片的問題了。執行過程以下圖:

在這裏插入圖片描述
從執行過程來看, 要消耗到一半的內存,怎麼想都是浪費的,在對象多時,會頻繁觸發GC

4.3.2 8:1 比例

因爲上面的 1:1 的鋪張浪費,基本主流的 Java 虛擬機都是採用 一個 Eden 和 兩個 Survivor 空間,這也是上面說的新生代和老年代的區分。由於新生代的對象 98% 都是"朝生夕死"的,每次都是用塊個 Eden 和 一塊 Survivor 空間,每次GC以後,還存活在 Eden 和 Survivor 空間的對象會被移動到另一塊Survivor上,並清掉 Eden 和剛纔用過的 Survivor 空間。當Survivor 控件不夠用時,還得依賴其餘內存(這裏指老年代),進行分配擔保。即存活下來的內存不夠放新生代上,只有移動到老年代的內存空間上。 舉個簡單例子:假如虛擬機中設置了新生代的內存大小爲10M,老年代的也爲10M,Eden 和 Survivor 爲 8:1 的關係,那麼Eden就只有8M,Survivor 爲1M;下面建立4個對象

public static void test(){
	// 假設 _1MB 字符創爲 1MB
	byte[] b1 = new byte[2*_1MB];
	byte[] b2 = new byte[2*_1MB];
	byte[] b3 = new byte[2*_1MB];
	byte[] b4 = new byte[4*_1MB];
}
複製代碼

當執行 test() ,當要分配 b4 對象時,會執行一次 Minor GC,緣由是 Dden 才 6M,被b1,b2,b3填充以後,已經沒有數據去填充 b4了,就會觸發 GC,而b4沒辦法,只有移動到老年代的內存區域了。

那咱們能夠獲得,** 對象存活率較高的狀況下,效率較低;若是不想浪費 50% 的控件,就須要額外的控件進行分配擔保,也就是內存跑到老年代去了,那這個對象沒進行GC就跑到老年代去了,那確定是很佔內存的** 因此就有了 標記-整理算法

4.4 標記-整理算法

標記整理算法,是針對老年代的。它與標記-清除算法同樣,分兩個步驟

  1. 標記那些被引用的對象
  2. 將被標記的對象移動按順序移動到一端,而後清除掉可回收的對象

在這裏插入圖片描述

5、總結 (分代收集算法)

在商業的虛擬機中,都採用 分代收集算法,根據對象存活週期將內存劃分爲幾塊,即新生代和老年代; 在新生代中,每次都有大量對象死去,只有少許活着,就選用複製算法;而老年代的對象存活率比較高,沒有額外空間對它進行分配擔保,就必須使用 「標記-整理」或者「標記-清理」 進行回收

至此,Java 回收機制(一) -- 對象回收與算法初識就講完啦。。。

相關文章
相關標籤/搜索