Java中的垃圾回收器(GC)是Java中比較有特色的一點,不需要我們手動的去管理一個對象,不想C++中的構造函數和析構函數一樣,需要程序猿自己去手動的管理,很容易造成內存泄露的問題。當然如果學過OC語言的話,我們知道OC語言中有自動釋放池的概念,當然我們使用retain/release進行手動管理對象的。所以從這方面我們可以看到Android(Java)在這方面和iOS(OC)相比的話,比較卡,因為Java中的垃圾回收器是需要算法計算的,這個可能會有點耗時,但是好處就是不需要人工管理。但是OC是需要手動管理的,這樣系統就不需要復雜的算法去進行管理,運行速度就比Android流暢(當然IOS比Android流暢,有很多原因的,這個只是一方面內容)
在堆中存放著Java世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事就是要確定這些對象之中哪些還“存活”著,哪些已經“死去”
這里就介紹兩種方式來管理對象
這種算法很簡單的,而且也是比較常用的一種方式管理對象了
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器的值就加1,當引用失效時,計數器的值就減1,任何時刻計數器為0的對象就是不可能在被使用了,這種算法是很簡單的,而且早期很多面向對象語言中都采用這種方式,但是現在主流的Java虛擬機中并沒有采用這種方式來管理對象,其原因最主要的原因是它很難解決對象之間的相互循環引用。例子:
[java] view plain copy所以解決上面存在的問題,第二種方式就出現了。
這個算法的基本思路就是通過一系列的稱謂“GC Roots"的對象作為起始點,從這些節點開始向下搜索,搜索所有走過的路徑為引用鏈,當一個對象到GC Roots沒有任何引用鏈項鏈時,則證明此對象時不可用的,下面看一下例子:
上面的這張圖,對象object5、object6、object7雖然互相沒有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收的對象
注:Java語言中,可作為GC Roots的對象包括下面幾種:
1) 虛擬機棧(棧幀中的本地變量表)中引用的對象
2) 方法區中類靜態屬性引用的對象
3) 方法區中常量引用的對象
4) 本地方法棧中JNI(即一般說的Native方法)引用的對象
從JDK1.2之后,Java對引用的概念進行了擴充,將引用分為強引用,軟引用,弱引用,虛引用,這四種引用的強度一次逐漸減弱
1) 強引用就是指在程序代碼之中普遍存在的,類似 “Object obj = new Object()” 這類的引用,只要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象。
2) 軟引用是用來描述一些還有用但并非需要的對象,對于軟引用關聯著的對象,在系統將要發生內存異常之前,將會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存,才會拋出內存異常
3) 弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存島下一次垃圾收集發生之前,當垃圾收集器工作時,無論當前內存釋放足夠,都會回收掉只被弱引用關聯的對象
4) 虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系,一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例,對一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知
最基礎的收集算法是“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象,它的標記過程其實在前一節講述對象標記判定時已經基本介紹過了。之所以說它是最基礎的收集算法,是因為后續的收集算法都是基于這種思路并對其缺點進行改進而得到的。它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。 標記-清除算法的執行過程如圖
JVM內存模型中分兩大塊,一塊是New Generation, 另一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收后存活下來的對象。在Old Generation中,主要存放應用程序中生命周期長的內存對象,還有個Permanent Generation,主要用來放JVM自己的反射對象,比如類對象和方法對象等。1) 在New Generation塊中,垃圾回收一般用復制算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個Survivor Space, 當Survivor Space空間滿了后, 剩下的live對象就被直接拷貝到Old Generation中去。因此,每次GC后,Eden內存塊會被清空
2) 在Old Generation塊中,垃圾回收一般用標記整理的算法,速度慢些,但減少內存要求.
垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收Old段中的垃圾;1級或以上為部分垃圾回收,只會回收New中的垃圾,內存溢出通常發生于Old段或Perm段垃圾回收后,仍然無內存空間容納新的Java對象的情況。
Out Of Memory 只發生在jvm對old和perm generation 回收后還不能獲足夠內存的情況.當生成一個新對象時,內存申請過程如下:A. JVM會試圖為相關Java對象在Eden中初始化一塊內存區域B. 當Eden空間足夠時,內存申請結束。否則到下一步C. JVM試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收), 釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區D. Survivor區被用來作為Eden及Old的中間交換區域,當Old區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區E. 當Old區空間不夠時,JVM會在Old區進行完全的垃圾收集(0級)F. 完全垃圾收集后,若Survivor及Old區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現”out of memory錯誤”
造成full gc的原因new了很多對象,沒有即時在主動釋放掉->Eden內存不夠用->不斷把對象往old遷移->old滿了->full gc
總結:上面的內容就介紹了Java虛擬機如何管理對象的,我們也看到了上面主要就是收集算法和堆空間的從新劃分,這樣做的目的都是在于垃圾回收的高效執行,但是總歸看來,如果對象交給系統來管理,在系統運行的過程效率肯定會有影響的,但是這有一點比較好,就是不需要手動管理,給程序猿帶來方便。
新聞熱點
疑難解答