首頁  >  文章  >  後端開發  >  Java與C#的垃圾回收機制的詳情介紹

Java與C#的垃圾回收機制的詳情介紹

黄舟
黄舟原創
2017-03-03 13:24:131112瀏覽

(一)垃圾回收器的基本假定

(1)最近被分配記憶體空間的物件最有可能需要被釋放。在方法被執行前,通常需要為該方法所使用到的物件分配記憶體空間,搜尋最近被分配的物件集合有助於花費最少的工作來釋放進可能多的空閒記憶體空間。

(2)生命期最長的物件需要釋放的可能性最小。在通過幾輪垃圾回收後仍然存在的對像不大可能是那種能夠在下一輪回收中被釋放的臨時對象,搜索這些內存塊往往要進行大量的工作,卻只能釋放很小一部分的內存空間。

(3)同時分配記憶體的物件通常也會同時使用。將同時分配記憶體的物件儲存位置彼此連接有助於提高快取效能。

(二)幾種垃圾回收機制

#(1)標記-清除收集器

#  這種收集器首先遍歷對象圖並標記可到達的對象,然後掃描堆疊以尋找未標記對象並釋放它們的記憶體。這種收集器一般使用單線程工作並停止其他操作。

(2)標記-壓縮收集器

#  有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記物件複製到堆疊的新域中以便壓縮堆疊。這種收集器也停止其他操作。

(3)複製收集器

  這種收集器將堆疊分成兩個域,常稱為半空間。每次僅使用一半的空間,jvm產生的新物件則放在另一半空間。 gc運作時,它把可到達物件複製到另一半空間,從而壓縮了堆疊。這種方法適用於短生存期的對象,持續複製長生存期的對象則導致效率降低。

(4)增量收集器

增量收集器把堆疊分成多個域,每次只從一個域收集垃圾。這會造成較小的應用程式中斷。

(5)分代收集器

  這種收集器把堆疊分成兩個或多個域,用以存放不同壽命的對象。 jvm產生的新物件一般會放在其中的某個網域中。過一段時間,繼續存在的物件將獲得使用期並轉入更長壽命的域中。分代收集器對不同的域使用不同的演算法以優化性能。

(6)並發收集器

  並發收集器與應用程式同時運作。這些收集器在某點上(例如壓縮時)一般都必須停止其他操作以完成特定的任務,但是因為其他應用程式可進行其他的後台操作,所以中斷其他處理的實際時間大大降低。 

(7)並行收集器

#  並行收集器使用某種傳統的演算法並使用多執行緒並行的執行它們的工作。在多cpu機器上使用多執行緒技術可以顯著的提高java應用程式的可擴展性。

(三).NET框架垃圾回收機制

#    .NET架構包含一個託管堆,所有的. NET語言在指派參考類型物件時都要使用它。像值型別這樣的輕量級物件總是被分配在堆疊中,但是所有的類別實例和陣列都被產生在一個記憶體池中,這個記憶體池就是託管堆。

   .NET框架中的垃圾回收器被稱為分代的垃圾回收器(Generational Garbage Collector),也就是說被分配的物件分為3個類別,或稱為「代」。分別為0,1,2。0、1、2代對應的託管堆的初始化大小分別為256K,2M和10M。垃圾回收器在發現改變大小能夠提高效能的話,會改變託管堆的大小。例如當應用程式初始化了許多小的物件,並且這些物件會很快被回收的話,垃圾回收器就會將第0代的託管堆變為128K,並且提高回收的頻率。如果情況相反,垃圾回收器發現在第0代的託管堆中無法回收很多空間時,就會增加託管堆的大小。在應用程式初始化的之前,所有等級的託管堆都是空的。當物件被初始化的時候,他們會按照初始化的先後順序被放入第0代的託管堆中。 

    最近被分配記憶體空間的物件被放置在第0代,因為第0代很小,小到足以放進處理器的二級(L2)緩存,所以第0代能夠為我們提供對其中物件的快速存取。經過一輪垃圾回收後,仍保留在第0代中的物件被移進第1代中,再經過一輪垃圾記憶體回收後,仍保留在第1代中的物件則被移進第2代中。第2代包含了生存期較長的對象,這些對象至少經過了兩輪回收。

    C#程式為物件分配記憶體時,託管堆幾乎可以立即返回新物件所需的內存,託管堆之所以能有如此高效的記憶體分配效能是由於託管堆較為簡單的資料結構。託管堆類似於簡單的位元組數組,有一個指向第一個可用記憶體空間的指標。

    在某區塊被某個物件所要求時,上述指標值就會傳回給呼叫函數,而指標會重新調整至指向下一個可用的記憶體空間。分配一個託管記憶體區塊只比遞增一個指標的值稍微複雜一點。這也是託管堆所優化的效能之一。在一個不需太多垃圾回收的應用程式中,託管堆的表現會優於傳統的堆。

    由於這個線性的記憶體分配方法的存在,在C#應用程式中同時分配的物件在託管堆上通常會被分配成彼此相鄰。著安排和傳統的堆記憶體分配完全不同,傳統的堆記憶體分配是基於記憶體區塊大小的。例如,兩個同時分配的物件在堆上的位置可能相距很遠,從而降低了快取的效能。因此雖然記憶體分配很快,但在一些比較重要的程式中,第0代中的可用記憶體很有可能會徹底被消耗光。記住,第0代小到可以裝進L2緩衝區,並且沒有被使用的記憶體不會被自動釋放。當第0代中沒有可以分配的有效內存時,就會在第0代中觸發一輪垃圾回收,在這輪垃圾回收中將刪除所有不再被引用的對象,並將當前正在使用中的對象移至第1代。針對第0代的垃圾回收是最常見的回收類型,而且速度很快。在第0代的垃圾記憶體回收不能有效的請求到充足的記憶體時,就啟動第1代的垃圾記憶體回收。第2代的垃圾記憶體回收要作為最後一種手段而使用,當且僅當第1代和第0代的垃圾記憶體回收不能被提供足夠記憶體時進行。如果各代都進行了垃圾回收後仍沒有可用的內存,就會引發一個OutOfMemeryException異常 。

(四)Java垃圾回收機制

#       執行Java程式時記憶體如何放置? 《Java程式設計思想》一書中提到了六個地方:

## (1)暫存器(Register)

 (2)堆疊( Stack)

 (3)堆(Heap):用來放置所有Java物件

 (4)靜態儲存空間(Static storage ):用來存放「程式執行期間​​」一直存在的資料。用statci修飾。

 (5)常數儲存空間(Constant storage)

 (6)Non-RAM儲存空間:我理解為磁碟儲存區,即非記憶體區域。

    Sun HotSpot 1.4.1使用分代收集器,它將堆疊分為三個主要的領域:新域、舊域以及永久域。 Jvm產生的所有新物件都放在新網域中。一旦物件經歷了一定數量的垃圾收集循環後,便獲得使用期並進入舊域。在永久域中jvm則儲存class和method物件。就配置而言,永久域是一個獨立域並且不認為是堆的一部分。這樣看來,採用了HotSpot引擎技術的JVM應該採用了和.NET框架類似的垃圾回收機制-----分代垃圾回收方法。

以下介紹如何控制這些域的大小。可使用-Xms和-Xmx 控制整個堆的原始大小或最大值。

  下面的指令是把初始大小設為128M:

java –Xms128m

# –Xmx256m為控制新域的大小,可使用-XX:NewRatio設定新域在堆中所佔的比例。

  下面的指令把整個堆設定成128m,新域比率設定成3,即新域與舊域比例為1:3,新域為堆的1/4或32M:

java –Xms128m –Xmx128m #

–XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize設定新網域的初始值和最大值。

  下面的指令把新網域的初始值和最大值設定成64m:

java –Xms256m –Xmx256m –Xmn64m##java –Xms256m –Xmx256m –Xmn64m

  永久域預設大小為4m。執行程式時,jvm會調整永久域的大小以滿足需求。每次調整時,jvm會對堆進行一次完全的垃圾收集。

  使用-XX:MaxPerSize標誌來增加永久域搭大小。在WebLogic Server應用程式載入較多類別時,經常需要增加永久域的最大值。當jvm載入類別時,永久域中的物件急劇增加,從而使jvm不斷調整永久域大小。為了避免調整,可使用-XX:PerSize標誌設定初始值。

  下面把永久域初始值設定成32m,最大值設定成64m。

java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m

  預設狀態下,HotSpot在新域中使用複製收集器。該域一般分為三個部分。第一部分為Eden,用於產生新的物件。另兩部分稱為救助空間,當Eden充滿時,收集器停止應用程序,把所有可到達對象複製到當前的from救助空間,一旦當前的from救助空間充滿,收集器則把可到達對象複製到當前的to救助空間。 From和to救助空間互換角色。維持活動的對象將在救助空間不斷複製,直到它們獲得使用期並轉入舊域。使用-XX:SurvivorRatio可控制新域子空間的大小。

  與NewRation一樣,SurvivorRation規定某救助域與Eden空間的比值。例如,以下指令把新域設定成64m,Eden佔32m,每個救助域各佔16m:

java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2

  如前所述,預設狀態下HotSpot對新網域使用複製收集器,對舊網域使用標記-清除-壓縮收集器。在新網域中使用複製收集器有很多意義,因為應用程式產生的大部分物件是短壽命的。理想狀態下,所有過渡物件在移出Eden空間時將被收集。如果能夠這樣的話,並且移出Eden空間的對像是長壽命的,那麼理論上可以立即把它們移進舊域,避免在救助空間反覆複製。但是,應用程式不能適合這種理想狀態,因為它們有一小部分中長壽命的物件。最好是保持這些中長壽命的物件並放在新網域中,因為複製小部分的物件總比壓縮舊網域廉價。為控制新域中物件的複製,可用-XX:TargetSurvivorRatio控制救助空間的比例(該值是設定救助空間的使用比例。如救助空間位1M,該值50表示可用500K)。該值是一個百分比,預設值是50。當較大的堆疊使用較低的sruvivorratio時,應增加該值到80至90,以便更好地利用救助空間。用-XX:maxtenuring threshold可控制上限。

  為放置所有的複製全部發生以及希望物件從eden擴展到舊域,可以把MaxTenuring Threshold設定成0。設定完成後,實際上就不再使用救助空間了,因此應把SurvivorRatio設成最大值以最大化Eden空間,設定如下:

java … -XX:MaxTenuringThreshold= 0 –XX:SurvivorRatio=50000 … 

後記:正如《Java本紀之Java虛擬機的10年》所提到的「最近的五年,就是(JVM)繼續優化的五年。研究深度優化的方法。 ,例如漸進式收集、火車演算法等。我想也沒有哪個面試官敢輕易問你談談C#或Java的垃圾回收機制(至少我還沒碰到過),很多問題一旦討論的深入,足以寫成長篇大著。但刨根問底、追根溯源真的是一件很美的事情,孔子登東山而小魯,登泰山而小天下。 


 以上就是Java與C#的垃圾回收機制的詳情介紹的內容,更多相關內容請關注PHP中文網(www.php.cn)!


####
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn