GC是什么?為什么我們要去使用它


GC(Garbage Collection)是各大語言的寵兒,也是計算機科學領域里很熱門的一個話題。最早在JVM中有看過這個算法,后來發現即使是js這種腳本語言也是有GC的。單純就JVM來說的話,GC算法也在不斷地改進,成熟。從最早的串行到高頓吞吐量的並行,為了解決高延遲又演化出了CMS(Concurrent Mark Sweep),為了解決碎片的問題,又開發了G1.

為什么我們需要進行GC呢


在早期的編程語言中,程序運行中沒有棧幀(Stack Frame)去維護,所以采用的是靜態分配策略,這比動態分配要快不少,但是它有一個很不人性化的缺點就是需要在編譯期的時候確定程序所需要的數據結構大小。

在1958年,Algol-58 語言首次提出了塊結構(block-structured),塊結構語言通過在內存中申請棧幀來實現按需分配的動態策略。在過程被調用時,幀(frame)會被壓到棧的最上面,調用結束時彈出。棧分配策略賦予程序員極大的自由度,局部變量在不同的調用過程中具有不同的值,這為遞歸提供了基礎。但是后進先出(Last-In-First-Out, LIFO)的棧限制了棧幀的生命周期不能超過其調用者,而且由於每個棧幀是固定大小,所以一個過程的返回值也必須在編譯期確定。所以誕生了新的內存管理策略——堆(heap)管理。

現在計算機VS圖靈機


現代計算機相對於圖靈機來說,本質上的區別就在於資源有限性,所以在使用完各種資源以后,需要將其釋放(release)。

那釋放資源全都用GC可以嘛


GC本來就是給馬大哈的程序員准備的,人為的手動釋放資源很容易出問題,那我能不能在每次需要釋放資源的時候都調用GC算法呢?這樣不是一勞永逸嘛。

並不能,GC同樣有它適用的和不適用的場景,在(socket/file handle)的使用中沒有使用GC,很大一部分原因在於它的不確定性。你即使調用了GC你也不知道它啥時會回收,它到底會不會回收,這樣的GC還是不可靠(霧,GC都是大豬蹄子

但是如果我們顯示對資源進行回收就不一樣了,調用了close/destroy后,資源百分百就被釋放掉了

為啥在內存里面就可以用GC呢,有兩個原因,第一是它具有獨占性(當然你也可以叫唯一性)OS給每個運行的程序分配的內存都是唯一的,也是相互獨立的,咋倆互不干擾,我晚一點回收對你也沒有什么影響。第二點是內存夠用,日常使用程序中也沒見內存占用太高,我晚一點回收也不會OutOfMenmory,那我就放心大膽地用GC的回收機制。

由這兩點可見,對於資源比較緊張的一些嵌入式的設備,還是手動釋放資源來的較好。(不包括樹莓派)

GC是什么


GC的本質是內存的自動管理,用來回收堆(Heap)中不再需要(使用)的對象。

我們知道內存其實也會被划分為各個區域的,常見的stack和heap。stack里面裝的是局部變量,函數的調用結束以后也就回收了,這個是個固定的流程,所以不需要GC。Heap中的空間,用來在多個函數之間去共享數據,由程序自己來動態申請,這個時候我們就需要利用到GC了

GC由兩大核心組成

  • 對象識別,其實也就是常說的判斷對象是否存活,live object和garbage
  • 回收算法,什么時候回收,如何回收

那咋辦嘛


先從對象識別開始說下吧,判斷對象是live object還是garbage

在堆(Heap)里存放着Java中基本上所有對象的實例,當一個對象沒有引用且不會再引用的時候就可以認為他已經死去,視為garbage

  1. 引用計數(Python和PHP都采用這種方式)

給對象一個引用計數器(在對象頭加上一個counter),當引用的時候計數器就加一,引用失效就減一,引用次數為0的時候,該對象就失效了。

這個很像OS里面的PV原語,實現是很簡單的,效率也比較高。但缺點也是顯而易見的,就是很難去解決對象之間循環使用的問題。

舉個栗子 如果現在有兩個對象 OA OB ,OA.instance = OB;OB.instance=OA;它們兩個相互引用,導致counter都不為0,於是都不會被回收

  1. 可達性分析(Tracing)

這個是現代語言使用最廣泛的一種方式,從root對象開始,不斷的去追蹤(tracing),找到所有可以被引用的對象,那這些對象就可以被稱作是可達的(reachable),剩下的對象自然就是不可達對象,視作garbage將其回收。

那么,你說的這個root對象,它是什么樣子的呢?
  • Java 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧中引用的對象
  • 方法區中常量引用的對象
  • 方法區中類靜態屬性引用的對象

被堆中對象所引用的對象,它們就不算Roots了,這樣就不會引起循環引用的問題。

引用


判定對象是否存活與“引用”有關。在 JDK 1.2 以前,Java 中的引用定義很傳統,一個對象只有被引用或者沒有被引用兩種狀態,我們希望能描述這一類對象:當內存空間還足夠時,則保留在內存中;如果內存空間在進行垃圾手收集后還是非常緊張,則可以拋棄這些對象。很多系統的緩存功能都符合這樣的應用場景。

在 JDK 1.2 之后,Java 對引用的概念進行了擴充,將引用分為了以下四種。不同的引用類型,主要體現的是對象不同的可達性狀態reachable和垃圾收集的影響。

強引用(Strong Reference)

類似 "Object obj = new Object()" 這類的引用,就是強引用,只要強引用存在,垃圾收集器永遠不會回收被引用的對象。但是,如果我們錯誤地保持了強引用,比如:賦值給了 static 變量,那么對象在很長一段時間內不會被回收,會產生內存泄漏。

軟引用(Soft Reference)

軟引用是一種相對強引用弱化一些的引用,可以讓對象豁免一些垃圾收集,只有當 JVM 認為內存不足時,才會去試圖回收軟引用指向的對象。JVM 會確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對象。軟引用通常用來實現內存敏感的緩存,如果還有空閑內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。

弱引用(Weak Reference)

弱引用的強度比軟引用更弱一些。當 JVM 進行垃圾回收時,無論內存是否充足,都會回收只被弱引用關聯的對象。

虛引用(Phantom Reference)

虛引用也稱幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響。它僅僅是提供了一種確保對象被 finalize 以后,做某些事情的機制,比如,通常用來做所謂的 Post-Mortem 清理機制。

事實上,軟引用,弱引用和虛引用都可以被叫做弱引用

以弱引用的方式指向一個對象,弱引用不會保護該對象被 GC 回收。如果該對象被回收了,那么這個弱引用會被賦予一個安全值(一般為NULL)。

可以看一下這個wiki,講了一下弱引用如何解決循環引用Dealing with reference cycles

參考



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM