您好,登錄后才能下訂單哦!
JVM棧由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:
Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把對象分為年青代(Young)、年老代(Tenured)、持久代(Perm),對不同生命周期的對象使用不同的算法。(基于對對象生命周期分析)
年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區復制過來的并且此時還存活的對象,將被復制年老區(Tenured。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。
用于存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設置。
舉個例子:當在程序中生成對象時,正常對象會在年輕代中分配空間,如果是過大的對象也可能會直接在年老代生成(據觀測在運行某程序時候每次會生成一個十兆的空間用收發消息,這部分內存就會直接在年老代分配)。年輕代在空間被分配完的時候就會發起內存回收,大部分內存會被回收,一部分幸存的內存會被拷貝至Survivor的from區,經過多次回收以后如果from區內存也分配完畢,就會也發生內存回收然后將剩余的對象拷貝至to區。等到to區也滿的時候,就會再次發生內存回收然后把幸存的對象拷貝至年老區。
通常我們說的JVM內存回收總是在指堆內存回收,確實只有堆中的內容是動態申請分配的,所以以上對象的年輕代和年老代都是指的JVM的Heap空間,而持久代則是之前提到的MethodArea,不屬于Heap。
手動將生成的無用對象,中間對象置為null,加快內存回收。
對象池技術如果生成的對象是可重用的對象,只是其中的屬性不同時,可以考慮采用對象池來較少對象的生成。如果有空閑的對象就從對象池中取出使用,沒有再生成新的對象,大大提高了對象的復用率。
Java堆中存放著幾乎所有的對象實例,垃圾收集器對堆中的對象進行回收前,要先確定這些對象是否還有用,判定對象是否為垃圾對象有如下算法:
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任何時刻計數器都為0的對象就是不可能再被使用的。
引用計數算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的選擇,當Java語言并沒有選擇這種算法來進行垃圾回收,主要原因是它很難解決對象之間的相互循環引用問題。
Java和C#中都是采用根搜索算法來判定對象是否存活的。這種算法的基本思路是通過一系列名為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,就證明此對象是不可用的。在Java語言里,可作為GC Roots的兌現包括下面幾種:
實際上,在根搜索算法中,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行根搜索后發現沒有與GC Roots相連接的引用鏈,那它會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為沒有必要執行。如果該對象被判定為有必要執行finalize()方法,那么這個對象將會被放置在一個名為F-Queue隊列中,并在稍后由一條由虛擬機自動建立的、低優先級的Finalizer線程去執行finalize()方法。finalize()方法是對象逃脫死亡命運的最后一次機會(因為一個對象的finalize()方法最多只會被系統自動調用一次),稍后GC將對F-Queue中的對象進行第二次小規模的標記,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中讓該對象重新引用鏈上的任何一個對象建立關聯即可。而如果對象這時還沒有關聯到任何鏈上的引用,那它就會被回收掉。
判定除了垃圾對象之后,便可以進行垃圾回收了。下面介紹一些垃圾收集算法,由于垃圾收集算法的實現涉及大量的程序細節,因此這里主要是闡明各算法的實現思想,而不去細論算法的具體實現。
標記—清除算法是最基礎的收集算法,它分為“標記”和“清除”兩個階段:首先標記出所需回收的對象,在標記完成后統一回收掉所有被標記的對象,它的標記過程其實就是前面的根搜索算法中判定垃圾對象的標記過程。標記—清除算法的執行情況如下圖所示:
該算法有如下缺點:
復制算法比較適合于新生代,復制算法是針對標記—清除算法的缺點,在其基礎上進行改進而得到的,它講課用內存按容量分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活著的對象復制到另外一塊內存上面,然后再把已使用過的內存空間一次清理掉。復制算法有如下優點:
它的缺點是:可一次性分配的最大內存縮小了一半。
復制算法的執行情況如下圖所示:
但一般不用按1:1劃分內存空間,可以分成一個大的eden和兩塊小的survivor。
在老年代中,對象存活率比較高,如果執行較多的復制操作,效率將會變低,所以老年代一般會選用其他算法,如標記—整理算法。該算法標記的過程與標記—清除算法中的標記過程一樣,但對標記后出的垃圾對象的處理情況有所不同,它不是直接對可回收對象進行清理,而是讓所有的對象都向一端移動,然后直接清理掉端邊界以外的內存。標記—整理算法的回收情況如下所示:
當前商業虛擬機的垃圾收集都采用分代收集來管理內存,它根據對象的存活周期的不同將內存劃分為幾塊,一般是把Java堆分為新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量對象死去,只有少量存活,因此可選用復制算法來完成收集,而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除算法或標記—整理算法來進行回收。
每個對象都有一個年齡(Age)計數器,如果對象在Eden出聲并講過一次Minor GC還存活,將被移動到Survivor區并將Age設置為1,之后每在Survivor區中熬過一次Minor GC,Age就加1,當增加到一定程度(默認為15),就可以放到老年代中。
垃圾收集器是內存回收算法的具體實現,Java虛擬機規范中對垃圾收集器應該如何實現并沒有任何規定,因此不同廠商、不同版本的虛擬機所提供的垃圾收集器都可能會有很大的差別。Sun HotSpot虛擬機1.6版包含了如下收集器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old。這些收集器以不同的組合形式配合工作來完成不同分代區的垃圾收集工作。
在用代碼分析之前,我們對內存的分配策略明確以下三點:
對垃圾回收策略說明以下兩點:
Dalvik虛擬機使用Mark-Sweep算法來進行垃圾收集。顧名思義,Mark-Sweep算法就是為Mark和Sweep兩個階段進行垃圾回收。其中,Mark階段從根集(Root Set)開始,遞歸地標記出當前所有被引用的對象,而Sweep階段負責回收那些沒有被引用的對象。在分析Dalvik虛擬機使用的Mark-Sweep算法之前,我們先來了解一下什么情況下會觸發GC。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。