Android性能調優篇之探索垃圾回收機制


開篇廢話

如果我們想要進行內存優化的工作,還是需要了解一下,但這一塊的知識屬於純理論的,有可能看起來會有點枯燥,我盡量把這一篇的內容按照一定的邏輯來走一遍。首先,我們為什么要學習垃圾回收的機制,我大概歸納了一下幾點:

1.方便我們理解什么樣的對象,什么時候,會被系統回收掉

2.有助於我們后面的內存優化

3.了解這一塊的知識也能提升自己的知識廣度,和同事一起裝逼的時候有話題

4.如果有面試的需求的話,了解這一塊,也能從容面對考官,對於內存回收能夠說出個一二

好了,廢話不多說了,我大概按以下這個邏輯來一個一個講述:

1.什么是垃圾回收(GC)

2.垃圾回收機制對於我們來說有什么好處?又有什么缺點?

3.垃圾回收它是如何工作的?

技術詳情

1.什么是垃圾回收(GC)

垃圾回收或GC(Garbage Collection),是一種自動的存儲管理機制,它是Java語言的一大特性,方便了我們這些程序員編碼,把內存釋放工作的壓力都轉讓到了系統,故而是以消耗系統性能為代價的。C++編碼的時候,我們 需要自己實現析構函數來進行內存釋放,很麻煩,而且非常容易遺漏而最終導致程序崩掉。所以Java語言就引入了自動內存管理的機制,也就是垃圾回收機制,針對的主要的內存的堆區域,關於內存的分配機制,請查看我的上一篇Android性能調優篇之探索JVM內存分配

2.垃圾回收機制對於我們來說有什么好處?又會帶來哪些坑?

以下我列舉一下系統自動垃圾回收給我們帶來的一些好處:

1.讓作為程序員的我們專注於邏輯實現,提高了我們的編碼效率

2.能夠保護程序的完整性,垃圾回收是Java語言的安全策略的一個重要部分

但是隨之的,也會到來一些缺點:

1.系統層面負責垃圾回收,明顯會加大系統資源的開銷,從而影響程序的性能

2.垃圾回收機制也存在不完備性,並不能百分百保證回收所有的垃圾內存

3.垃圾回收它是如何工作的?

其實,GC是主要的一個流程是:先根據一定的算法判定某個對象是否存活,然后把判定是垃圾的對象進行回收。詳細點說的話,GC的工作流程分以下幾個步驟:

1.可回收對象的判定

2.通過某些算法回收垃圾內存

下面一個一個進行講述

1.可回收對象的判定

我們的GC需要把某個對象回收掉,肯定是需要判斷它到底是不是垃圾,是不是需要被回收,因此,就需要對每一個對象進行可回收判定。

目前,市面上存在有兩種算法來判定一個對象是否是垃圾

1.引用計數算法

這種算法的工作原理是:

1.首先給每一個對象都添加一個引用計數器

2.當程序的某一個地方引用了此對象,這個計數器的值就加1

3.當引用失效的時候(例如超過了作用域),這個計數器就減1

4.當某一個對象的計數器的值是0的時候,則判定這個對象不可能被使用

這種算法對於系統來說比較簡單,高效,垃圾回收器運行較快,不需要長時間中斷我們的程序的執行,但是缺點是很難處理循環引用,這就導致相互引用的對象都無法被回收:

循環引用
循環引用

我記得OC(Objective-C)中的垃圾判定就是用的引用計數方法,引用了一個第三方變量來打破這個平衡,但OC也沒有很好的解決這個問題,而是更多的依靠我們這些開發者來處理。

2.GC Root可達性分析算法

這個算法的工作原理是:

1.以稱作“GC Root”的對象作為起點向下搜索

2.每走過一個對象,就生成一條引用鏈

3.從根開始到搜索完,生成了一棵引用樹,那些到GC Root不可達的對象就是可以回收的

這個方法明顯就解決了循環引用的問題,不過這個算法還是稍微有點復雜的,以下是GC Root可達性算法的一個圖解:

比較
比較

我們程序中能夠被用來當做GC Root對象的有:

1.虛擬機棧(棧幀中的本地變量表)中引用的對象

2.方法區中靜態屬性引用的對象

3.方法區中常量引用的對象

4.本地方法棧中JNI引用的對象

以下拿一個圖來進行引用計數算法與可達性分析算法的比較:

比較
比較

文字說明:

1.若使用引用計數算法判定,但圖中的C和D對象存在相互引用,導致計數器不為0,無法回收掉

2.若使用可達性分析算法,C和D對象到GC Roots 不可達,則能夠回收

關於對象可回收的判定,我們還需要注意的是,當系統開始啟動GC的時候,為了保證引用鏈的狀態不變,就需要停止該進程中所有的程序(Stop The World),我們Android中的現象就是UI卡頓了,但一般這樣的卡頓時間是非常短的,當然,要是頻繁的產生GC,那就另當別論了。

還有值得注意的是,不可達對象也並非立即就被回收了,還需要經過兩次的標記過程后才被會被真正回收:

1.如果對象與GC Root沒有連接的引用鏈,就會被第一次標記,隨后判定該對象是否有必要執行finalize()方法

2.如果有必要執行finalize()方法,則這個對象就會被放到F-Queue的隊列中,稍后由虛
擬機建立低優先級的Finalizer線程去執行,但並不承諾等待它運行結束(對象類中能夠
重寫finalize()方法進行自救,但系統最多只能執行一次)

3.如果沒必要執行finalize()方法,則第二次標記

2.通過某些算法回收垃圾內存

以上內容講述了系統如何去判定某一個對象是否是垃圾,是否應該被回收。接着,當判定了某一個對象為垃圾對象后,系統就要開始進行回收了,那么系統的垃圾回收算法以下幾種:

1.標記清除算法(Mark-Sweep)

2.復制算法(Copying)

3.標記整理算法(Mark-Compact)

4.分代回收算法

以下進行一一講述

1.標記清除算法(Mark-Sweep)

顧名思義,這個算法是先進行標記,然后進行清除,也正是這個算法的兩個階段:標記階段和清除階段,以下圖解:

標記清除
標記清除

從圖中可以看出:

這個算法的優點是易於理解,容易實現,只需要將特定地址的空間進行處理。
但缺點也比較明顯,把整個內存區域弄得非常不完整,形成了很多碎片化的內存,對於
分配大內存的對象時,無法申請足夠的空間,從而更多次的觸發GC

2.復制算法(Copying)

復制算法,是對標記清除算法而導致內存碎片化的一個解決方案,算法原理如下:

復制算法
復制算法
1.如圖所示,復制算法將內存平均分成兩個區域A (上)和 B(下)

2.將A中的存活的那些對象復制到B區域中

3.然后將A區域的所有對象都清除,這樣A區域就是一個完整的內存塊了,也就避免了內
存碎片化了

但是這個算法也有明顯的缺點,那就是不管A區域或B區域有多少個存活對象,都需要將整塊內存分成兩個區域,意味着能夠真正使用的內存變成了一半。

3.標記整理算法(Mark-Compact)

標記整理算法是對於標記清楚的一個優化,工作原理是:

標記整理
標記整理
1.如圖所示,第一步也需要進行存活對象的一個標記,這一步與標記清除算法一模一樣

2.將存活的對象向一端移動,例如圖中是往左上角那一端進行移動

3.然后把另一端的內存進行清理

從圖中也可以看出,這個算法也能避免內存碎片化的問題,但是效率確實不怎么樣,畢竟相較於復制算法, 多了一步效率同樣比較低的標記過程,而與標記清除算法相比,多了一步內存整理(往一端移動)的過程,效率上明顯就更低了。

畢竟世界是公平的,任何算法都有兩面性,我們開發者只能具體情況具體分析,使用最適合的算法。因此標記整理算法從圖中可以看出,這個算法適合存活對象多的,回收對象少的情況。

4.分代回收算法

鑒於以上三種算法都存在自己的缺陷,然后大神們就提出了根據不同對象的不同特性,使用不同的算法進行處理,所以嚴格來講並不是一個新的算法,而是屬於一種算法整合方案,我們知道:

1.復制算法適用於存活對象少,回收對象多的情況

2.標記整理算法適用於存活對象多,回收對象少的情況

這兩種算法剛好互補,因此只要將這兩個算法作用於不同特性的對象,就完美了。。

那么我們就應該知道,哪個區域的對象是什么樣的特性,根據我的上一篇的內存分配模型,堆內存中的新生代有很多的垃圾需要回收,老年代有很少的垃圾需要回收,那么剛好能夠根據這個特點使用不同的算法進行回收,具體使用的方式為:

1.對於新生代區域采用復制算法,因為新生代中每次垃圾回收都要回收大部分對象,那么也就意味着復制次數比較少,因此采用復制算法更高效

2.而老年代區域的特點是每次回收都只能回收很少的對象,一般使用的是標記整理或者標記清除算法

通過以上的方式,使得GC的整個過程達到了最高效的狀態。

這里需要注意的是分代回收算法的中的復制算法的使用。

之前說的復制算法是將內存均分為二,但是在分代回收中,並不是這樣,而是根據Eden:Survivor A:Survivor B= 8:1:1,具體的過程是(簡化版):

1.新創建一個對象,默認是分在Eden區域,當Eden區域內存不夠的時候,會觸發一次Min
or GC(新生代回收),這次回收將存活的對象放到Survivor A區域,然后新的對象繼續放在Eden區域

2.再創建一個新的對象,要是Eden區域又不夠了,再次觸發Minor GC,這個時候會把Eden區域的存活對象以及
Survivor A區域的存活的對象移動到Survivor B區域,然后清空Eden區域以及Survivor A區域

3.如果繼續有新的對象創建,不斷觸發Minor GC,有些對象就會不斷在Survivor A區域以及Survivor B區域
來回移動,但移動次數超過15次后,這些對象就會被移動到老年代區域

4.如果新的對象在Minor GC后還是放不下,就直接放到老年代

5.如果Survivor區域放不下該對象,這直接放到老年代

6.如果老年代也滿了,就會觸發一次Full GC(major gc)

干貨總結

通過以上的講述,我們了解了什么是GC,它的優缺點,它是如何工作的,整過過程下來,腦子里也算有了一個GC的概率模型在了。

本文參考了以下博客:

理解Android Java垃圾回收機制

JAVA垃圾回收機制

掌握好GC策略和原理,對於我們編碼來說能夠避免一些不必要的內存泄露,我們使用Java語言進行開發,不要一味的去追求各種牛逼的框架或者酷炫的業務實現,有的時候,還是需要我們沉下心來,好好了解一下底層系統的一些機制,個人覺得還是很有必要的。



作者:進擊的歐陽
鏈接:http://www.jianshu.com/p/4b6adee12682
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

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



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