從Java垃圾回收機制窺探記憶體優化(增強版)

拉丁吳發表於2016-11-11

前言

其實這篇文章幾個月之前已經發布到簡書部落格裡了,但是文章釋出之後,有人反饋了一些問題,而我對於這篇文章也有刪改增補的想法,所以這篇文章會是原文的簡潔增強版

回答一個疑問

這篇文章講的是Java的JVM的垃圾回收機制,但是Android使用的虛擬機器是Dalvik或者ART,那麼下面講的垃圾回收機制是否適用於Android呢?

答案是,Yes,是可以類比的。

增補
文章末尾有人提出了質疑: JVM 的記憶體模型和 Android 虛擬機器是區別的?

答案:是的,Android基於暫存器,jvm基於堆疊,本文其實避免了這樣的探討,那是因為在邏輯上它們其實沒有太大區別,Android作為移動作業系統基於暫存器,可以極大提高它的訪存速度,從而提高它的程式執行速度,堆疊訪存就慢些。這是最大的區別,在這裡做出說明。

其他的問題我也在評論裡做出了迴應,有任何問題都可以在下面探討,如果本文有重大漏洞,我會解除安裝勘誤
一欄中,大家可以關注一下。


開始

作為Android工程師,我看過很多關於Android記憶體洩漏的相關優化的文章,其中大部分都是告訴你該怎麼做,做哪些,列一些具體的措施。很少解釋為什麼要這麼做,即使解釋,也只是一筆帶過。

今天,我就以記憶體回收為突破口,向大家解釋從底層到上層,是如何配合完成記憶體回收的。我們就從Java程式執行時的記憶體區域開始吧。

執行時記憶體區域,指的是當你的程式被JVM載入進來執行的時候,記憶體是怎麼分配的。

下面,就是Java程式執行時的記憶體模型

從Java垃圾回收機制窺探記憶體優化(增強版)

  • 如上圖所示,當你的Java位元組碼執行起來的時候,虛擬機器就會它所管理的記憶體大致分成這五個部分,把你的程式碼,產生的物件等等分別扔到這五個框框裡:
    • 方法區:用於儲存類資訊,常量,靜態變數,編譯後的程式碼等等這些
    • 本地方法棧:用於為本地方法的執行提供服務(可以不瞭解)
    • 虛擬機器棧:是虛擬機器執行Java方法的重要記憶體模型。內部有一個區域性變數表,這個表儲存了物件引用(這個區域性變數表常被我們成為棧)。
    • 堆:儲存物件例項的區域(比如 A a=new A(),那麼 a這個引用就儲存在上面的區域性變數表中,而new A() 的這個物件就放在堆中
    • 程式計數器:用來記錄當前執行緒執行的位元組碼的地址,指示執行引擎執行程式碼。
       執行引擎:哎呀臥槽,剛才執行到哪一步來著?
       程式計數器:傻逼。複製程式碼

其實上面那五個區域,和這篇文章關係最緊密的時虛擬機器棧中的區域性變數表(也就是我們常說的棧),棧中主要儲存了物件的引用,堆中儲存了實際的物件,因此,實際上堆佔了整個程式執行記憶體的大頭,因此,記憶體回收就發生在堆中。

當程式執行時,然後躲在暗處的那個飢渴難耐端的垃圾回收機制(GC)就馬上跳出來了。虎視眈眈的盯著堆中的物件,專門找那些“落單”的物件....

那麼,GC是怎麼知道哪個物件“落單”了呢,答案是GC Roots引用鏈。

  • 通過這個引用鏈往下搜尋,所有在這些鏈條上的物件都拿到了免死金牌,而其餘就是落單的,它們隨後就會被GC回收。

關於GC引用鏈無論怎麼說,都很難想象,那沒關係,我來畫一畫就好了。

從Java垃圾回收機制窺探記憶體優化(增強版)

  • GC程式從棧頂開始一直找到棧底,尋找每個物件引用所對應的物件例項,然後在例項中尋找是否有指向其他物件的引用,若有,繼續找(這一部分並沒有畫出,但是也是重點),沒有,則回到棧中,繼續往棧底搜尋。
  • 當然,GC不止會在棧中尋找引用,還會在靜態儲存區(儲存靜態變數的資訊儲存在方法區)中尋找引用,(因為有的堆中的物件在棧中沒有引用,它的引用在靜態儲存區,類似於 static A a =new A() )

當那些待回收的物件被標記之後,GC就會開始回收物件釋放記憶體。GC的回收演算法有很多種,一般虛擬機器配合多種記憶體回收演算法來實現記憶體的高效回收,但是這些和我們其實關係不大,因此可以略過。

好了,我們終於把Java記憶體回收的機理講完了,那麼記憶體優化背後的機理也基本被我們扒開了:

  • 不再使用物件時,將引用置空,那麼GC順著棧或者靜態儲存區中的引用就找不到堆中對應的物件,那麼這個對應的物件就會被回收。
  • 儘量不要在生命週期較長(程式執行期間可能都不會被收回)的物件例項中引用其他的不必要的佔記憶體較大的物件例項

很多Android優化場景,基本圍繞了一個點:

  • Android中Activity,context,bitmap等佔據較大的記憶體空間物件,在不用的時候,一定保證當前物件的直接引用和間接引用全部被置為空。記憶體才能被釋放。(重點注意多執行緒和網路請求這種不可控的場景)

所以Java記憶體優化一個最根本的準則,就是努力使你的程式適配Java的GC機制

後記

最近下定決心要涉足新領域,因此給自己定下目標,從這一篇開始,要系統的歸納自己所學的知識技術以及積攢的經驗,以文章的方式產出。內容主要以Java+Android相關為主,以JS+React-native次之,然後是一些基於python的有意思的專案(是大學裡寫的,會重新整理)。

大家放心,我的文章會保證一貫的簡單友好,至少同一個層次的知識,我的文章能比別人更加友好,我有這樣的信心。

回到這篇文章本身,其實在重新整理的時候,我已經發現最近掘金上已經有人把Android優化整理了一個系列文章,我覺得寫的很不錯,在這裡也可以推薦給大家@anly_jun: android優化系列

當然,如果你比較欣賞我的文章風格,歡迎關注我,我會在保證文章質量的前提下,提高寫作速度的。

共勉

勘誤

暫無

參考

《深入理解Java虛擬機器》

相關文章