(1)垃圾回收機制的意義
java 語言中一個顯著的特點就是引入了java回收機制,是c++程序員最頭疼的內存管理的問題迎刃而解,它使得java程序員在編寫程序的時候不在考慮內存管理。由於有個垃圾回收機制,java中的額對象不在有“作用域”的概念,只有對象的引用纔有“作用域”。垃圾回收可以有效的防止內存泄露,有效的使用空閒的內存;
說到這,不得不提起內存泄漏(memory leak
)和內存溢出(out of memory)
(2)內存泄漏
和內存溢出
內存泄漏:
是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。
內存溢出:
指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,但是你卻存儲long類型的數據,那麼結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出。
內存泄露量大到一定程度會導致內存溢出。但是內存溢出不一定是內存泄露引起的。
(3)內存泄漏的分類(按發生方式來分類)
常發性內存泄漏:
發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。
偶發性內存泄漏:
發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。
一次性內存泄漏:
發生內存泄漏的代碼只會被執行一次,或者由於算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。
隱式內存泄漏:
程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對於一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏爲隱式內存泄漏
(4)內存溢出原因
- 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
- 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
- 代碼中存在死循環或循環產生過多重複的對象實體;
- 使用的第三方軟件中的BUG;
- 啓動參數內存值設定的過小;
(5)GC
Android觸發垃圾回收機制的時機是無法確定的,但是可以執行
System.gc();
主動觸發垃圾回收機制。
(6)Android代碼中的堆和棧
瞭解垃圾回收機制,必須瞭解堆和棧的關係,上面是我用畫圖工具畫的簡單圖。
首先我畫了兩個矩形區域,一個代表棧空間(負責存儲對象的引用),一個代表堆空間(負責存儲對象的實例),c是一個引用,存入棧空間,new Object()是對象,存入堆空間,兩者存在引用關係,用一根直線來表示。
重點知識:
因爲存在引用關係,所以垃圾回收機制是無法回收這個對象的。
當執行以下代碼時,引用將會斷掉
c = null;
這時,下次垃圾回收機制觸發時,Object的實例會被回收。
由於引用存入棧空間佔用空間很小,對象存入堆中佔用空間很大,所以將堆中無引用的對象回收是垃圾回收機制的主要工作。
那麼,是否只要執行了c = null;
,垃圾回收機制就一定會將對象回收呢?
答案是不一定。我再畫一個簡單圖來說明:
這是一個 簡單的樹形結構,GC Roots
是垃圾回收機制樹形結構的根節點,當對象存在引用時,我們就在GC Roots
和對象之間畫一條直線,如果設置爲null
a = null;
GC Roots
和a的連線就會斷開,如圖:
這種情況下,當垃圾回收機制觸發時,由於a對象和b對象也存在引用關係,所以對象a不會被回收,這樣就造成了內存泄漏。
(7)原理
本來想畫個圖來說一下垃圾回收機制的原理的,後來發現圖畫的並不咋滴,然後我查找了很多很多有關垃圾回收機制的文章,最後終於找到一篇和我腦海中的圖差不多的,Java 技術之垃圾回收機制,這篇文章寫得非常不錯,只能用牛逼來形容,我也認真讀完了,大家看一下這篇文章的這張圖
頂端有一個節點是GC Roots
,這是垃圾回收機制樹形結構的根節點,其中object 1、object 2、object 3、object 4和GC Roots
存在直接或者間接的引用,object 5、object 6、object 7和GC Roots
不存在引用,那麼可以總結爲:
- object 1、object 2、object 3、object 4與根節點仍然存在引用;
- object 5、object 6、object 7與根節點不存在引用,所以可以被GC回收。