明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

Java堆的管理--垃圾回收

[摘要]引言   Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的實(shí)例(對象)從中分配空間。Java虛擬機(jī)(JVM)的堆中儲存著正在運(yùn)行的應(yīng)用程序所建立的所有對象,這些對象通過new、newarray、anewarray和multianewarray等指令建立,但是它們不需要程序代碼來顯式地釋放。一般來說,堆的是由垃...

  引言

  Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的實(shí)例(對象)從中分配空間。Java虛擬機(jī)(JVM)的堆中儲存著正在運(yùn)行的應(yīng)用程序所建立的所有對象,這些對象通過new、newarray、anewarray和multianewarray等指令建立,但是它們不需要程序代碼來顯式地釋放。一般來說,堆的是由垃圾回收 來負(fù)責(zé)的,盡管JVM規(guī)范并不要求特殊的垃圾回收技術(shù),甚至根本就不需要垃圾回收,但是由于內(nèi)存的有限性,JVM在實(shí)現(xiàn)的時(shí)候都有一個(gè)由垃圾回收所管理的堆。垃圾回收是一種動(dòng)態(tài)存儲管理技術(shù),它自動(dòng)地釋放不再被程序引用的對象,按照特定的垃圾收集算法來實(shí)現(xiàn)資源自動(dòng)回收的功能。

  垃圾收集的意義

  在C++中,對象所占的內(nèi)存在程序結(jié)束運(yùn)行之前一直被占用,在明確釋放之前不能分配給其它對象;而在Java中,當(dāng)沒有對象引用指向原先分配給某個(gè)對象的內(nèi)存時(shí),該內(nèi)存便成為垃圾。JVM的一個(gè)系統(tǒng)級線程會自動(dòng)釋放該內(nèi)存塊。垃圾收集意味著程序不再需要的對象是"無用信息",這些信息將被丟棄。當(dāng)一個(gè)對象不再被引用的時(shí)候,內(nèi)存回收它占領(lǐng)的空間,以便空間被后來的新對象使用。事實(shí)上,除了釋放沒用的對象,垃圾收集也可以清除內(nèi)存記錄碎片。由于創(chuàng)建對象和垃圾收集器釋放丟棄對象所占的內(nèi)存空間,內(nèi)存會出現(xiàn)碎片。碎片是分配給對象的內(nèi)存塊之間的空閑內(nèi)存洞。碎片整理將所占用的堆內(nèi)存移到堆的一端,JVM將整理出的內(nèi)存分配給新的對象。

  垃圾收集能自動(dòng)釋放內(nèi)存空間,減輕編程的負(fù)擔(dān)。這使Java 虛擬機(jī)具有一些優(yōu)點(diǎn)。首先,它能使編程效率提高。在沒有垃圾收集機(jī)制的時(shí)候,可能要花許多時(shí)間來解決一個(gè)難懂的存儲器問題。在用Java語言編程的時(shí)候,靠垃圾收集機(jī)制可大大縮短時(shí)間。其次是它保護(hù)程序的完整性, 垃圾收集是Java語言安全性策略的一個(gè)重要部份。

  垃圾收集的一個(gè)潛在的缺點(diǎn)是它的開銷影響程序性能。Java虛擬機(jī)必須追蹤運(yùn)行程序中有用的對象, 而且最終釋放沒用的對象。這一個(gè)過程需要花費(fèi)處理器的時(shí)間。其次垃圾收集算法的不完備性,早先采用的某些垃圾收集算法就不能保證100%收集到所有的廢棄內(nèi)存。當(dāng)然隨著垃圾收集算法的不斷改進(jìn)以及軟硬件運(yùn)行效率的不斷提升,這些問題都可以迎刃而解。

  垃圾收集的算法分析

  Java語言規(guī)范沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾收集算法一般要做2件基本的事情:(1)發(fā)現(xiàn)無用信息對象;(2)回收被無用對象占用的內(nèi)存空間,使該空間可被程序再次使用。

  大多數(shù)垃圾回收算法使用了根集(root set)這個(gè)概念;所謂根集就量正在執(zhí)行的Java程序可以訪問的引用變量的集合(包括局部變量、參數(shù)、類變量),程序可以使用引用變量訪問對象的屬性和調(diào)用對象的方法。垃圾收集首選需要確定從根開始哪些是可達(dá)的和哪些是不可達(dá)的,從根集可達(dá)的對象都是活動(dòng)對象,它們不能作為垃圾被回收,這也包括從根集間接可達(dá)的對象。而根集通過任意路徑不可達(dá)的對象符合垃圾收集的條件,應(yīng)該被回收。下面介紹幾個(gè)常用的算法。

  1、引用計(jì)數(shù)法(Reference Counting Collector)

  引用計(jì)數(shù)法是唯一沒有使用根集的垃圾回收得法,該算法使用引用計(jì)數(shù)器來區(qū)分存活對象和不再使用的對象。一般來說,堆中的每個(gè)對象對應(yīng)一個(gè)引用計(jì)數(shù)器。當(dāng)每一次創(chuàng)建一個(gè)對象并賦給一個(gè)變量時(shí),引用計(jì)數(shù)器置為1。當(dāng)對象被賦給任意變量時(shí),引用計(jì)數(shù)器每次加1。當(dāng)對象出了作用域后(該對象丟棄不再使用),引用計(jì)數(shù)器減1,一旦引用計(jì)數(shù)器為0,對象就滿足了垃圾收集的條件。

  基于引用計(jì)數(shù)器的垃圾收集器運(yùn)行較快,不會長時(shí)間中斷程序執(zhí)行,適宜地必須 實(shí)時(shí)運(yùn)行的程序。但引用計(jì)數(shù)器增加了程序執(zhí)行的開銷,因?yàn)槊看螌ο筚x給新的變量 ,計(jì)數(shù)器加1,而每次現(xiàn)有對象出了作用域生,計(jì)數(shù)器減1。

  2、tracing算法(Tracing Collector)

  tracing算法是為了解決引用計(jì)數(shù)法的問題而提出,它使用了根集的概念。基于tracing算法的垃圾收集器從根集開始掃描,識別出哪些對象可達(dá),哪些對象不可達(dá),并用某種方式標(biāo)記可達(dá)對象,例如對每個(gè)可達(dá)對象設(shè)置一個(gè)或多個(gè)位。在掃描識別過程中,基于tracing算法的垃圾收集也稱為標(biāo)記和清除(mark-and-sweep)垃圾收集器.

  3、compacting算法(Compacting Collector)

  為了解決堆碎片問題,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的過程中,算法將所有的對象移到堆的一端,堆的另一端就變成了一個(gè)相鄰的空閑內(nèi)存區(qū),收集器會對它移動(dòng)的所有對象的所有引用進(jìn)行更新,使得這些引用 在新的位置能識別原來 的對象。在基于Compacting算法的收集器的實(shí)現(xiàn)中,一般增加句柄和句柄表。

  4、coping算法(Coping Collector)

  該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時(shí)把堆分成 一個(gè)對象 面和多個(gè)空閑面, 程序從對象面為對象分配空間,當(dāng)對象滿了,基于coping算法的垃圾 收集就從根集中掃描活動(dòng)對象,并將每個(gè) 活動(dòng)對象復(fù)制到空閑面(使得活動(dòng)對象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內(nèi)存。

  一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閑區(qū)域面,在對象面與空閑區(qū)域面的切換過程中,程序暫停執(zhí)行。

  5、generation算法(Generational Collector)

  stop-and-copy垃圾收集器的一個(gè)缺陷是收集器必須復(fù)制所有的活動(dòng)對象,這增加了程序等待時(shí)間,這是coping算法低效的原因。在程序設(shè)計(jì)中有這樣的規(guī)律:多數(shù)對象存在的時(shí)間比較短,少數(shù)的存在時(shí)間比較長。因此,generation算法將堆分成兩個(gè)或多個(gè),每個(gè)子堆作為對象的一代(generation)。由于多數(shù)對象存在的時(shí)間比較短,隨著程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運(yùn)行后,上次運(yùn)行存活下來的對象移到下一最高代的子堆中,由于老一代的子堆不會經(jīng)常被回收,因而節(jié)省了時(shí)間。

  6、adaptive算法(Adaptive Collector)

  在特定的情況下,一些垃圾收集算法會優(yōu)于其它算法;贏daptive算法的垃圾收集器就是監(jiān)控當(dāng)前堆的使用情況,并將選擇適當(dāng)算法的垃圾收集器。

   透視Java垃圾回收

  1、命令行參數(shù)透視垃圾收集器的運(yùn)行

  使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請求Java的垃圾回收。在命令行中有一個(gè)參數(shù)-verbosegc可以查看Java使用的堆內(nèi)存的情況,它的格式如下:

  java -verbosegc classfile

  可以看個(gè)例子:

  class TestGC
  {
  public static void main(String[] args)
  {
  new TestGC();
  System.gc();
  System.runFinalization();
  }
  }

  在這個(gè)例子中,一個(gè)新的對象被創(chuàng)建,由于它沒有使用,所以該對象迅速地變?yōu)榭蛇_(dá),程序編譯后,執(zhí)行命令: java -verbosegc TestGC 后結(jié)果為:

  [Full GC 168K->97K(1984K), 0.0253873 secs]

  機(jī)器的環(huán)境為,Windows 2000 + JDK1.3.1,箭頭前后的數(shù)據(jù)168K和97K分別表示垃圾收集GC前后所有存活對象使用的內(nèi)存容量,說明有168K-97K=71K的對象容量被回收,括號內(nèi)的數(shù)據(jù)1984K為堆內(nèi)存的總?cè)萘,收集所需要的時(shí)間是0.0253873秒(這個(gè)時(shí)間在每次執(zhí)行的時(shí)候會有所不同)。

  2、finalize方法透視垃圾收集器的運(yùn)行

  在JVM垃圾收集器收集一個(gè)對象之前 ,一般要求程序調(diào)用適當(dāng)?shù)姆椒ㄡ尫刨Y源,但在沒有明確釋放資源的情況下,Java提供了缺省機(jī)制來終止化該對象心釋放資源,這個(gè)方法就是finalize()。它的原型為:

  protected void finalize() throws Throwable

  在finalize()方法返回之后,對象消失,垃圾收集開始執(zhí)行。原型中的throws Throwable表示它可以拋出任何類型的異常。

  之所以要使用finalize(),是由于有時(shí)需要采取與Java的普通方法不同的一種方法,通過分配內(nèi)存來做一些具有C風(fēng)格的事情。這主要可以通過"固有方法"來進(jìn)行,它是從Java里調(diào)用非Java方法的一種方式。C和C++是目前唯一獲得固有方法支持的語言。但由于它們能調(diào)用通過其他語言編寫的子程序,所以能夠有效地調(diào)用任何東西。在非Java代碼內(nèi)部,也許能調(diào)用C的malloc()系列函數(shù),用它分配存儲空間。而且除非調(diào)用了free(),否則存儲空間不會得到釋放,從而造成內(nèi)存"漏洞"的出現(xiàn)。當(dāng)然,free()是一個(gè)C和C++函數(shù),所以我們需要在finalize()內(nèi)部的一個(gè)固有方法中調(diào)用它。也就是說我們不能過多地使用finalize(),它并不是進(jìn)行普通清除工作的理想場所。

  在普通的清除工作中,為清除一個(gè)對象,那個(gè)對象的用戶必須在希望進(jìn)行清除的地點(diǎn)調(diào)用一個(gè)清除方法。這與C++"破壞器"的概念稍有抵觸。在C++中,所有對象都會破壞(清除);蛘邠Q句話說,所有對象都"應(yīng)該"破壞。若將C++對象創(chuàng)建成一個(gè)本地對象,比如在堆棧中創(chuàng)建(在Java中是不可能的),那么清除或破壞工作就會在"結(jié)束花括號"所代表的、創(chuàng)建這個(gè)對象的作用域的末尾進(jìn)行。若對象是用new創(chuàng)建的(類似于Java),那么當(dāng)程序員調(diào)用C++的delete命令時(shí)(Java沒有這個(gè)命令),就會調(diào)用相應(yīng)的破壞器。若程序員忘記了,那么永遠(yuǎn)不會調(diào)用破壞器,我們最終得到的將是一個(gè)內(nèi)存"漏洞",另外還包括對象的其他部分永遠(yuǎn)不會得到清除。

  相反,Java不允許我們創(chuàng)建本地(局部)對象--無論如何都要使用new。但在Java中,沒有"delete"命令來釋放對象,因?yàn)槔占鲿䦷椭覀冏詣?dòng)釋放存儲空間。所以如果站在比較簡化的立場,我們可以說正是由于存在垃圾收集機(jī)制,所以Java沒有破壞器。然而,隨著以后學(xué)習(xí)的深入,就會知道垃圾收集器的存在并不能完全消除對破壞器的需要,或者說不能消除對破壞器代表的那種機(jī)制的需要(而且絕對不能直接調(diào)用finalize(),所以應(yīng)盡量避免用它)。若希望執(zhí)行除釋放存儲空間之外的其他某種形式的清除工作,仍然必須調(diào)用Java中的一個(gè)方法。它等價(jià)于C++的破壞器,只是沒后者方便。

  下面這個(gè)例子向大家展示了垃圾收集所經(jīng)歷的過程,并對前面的陳述進(jìn)行了總結(jié)。

  class Chair {
  static boolean gcrun = false;
  static boolean f = false;
  static int created = 0;
  static int finalized = 0;
  int i;
  Chair() {
  i = ++created;
  if(created == 47)
   System.out.println("Created 47");
  }
  protected void finalize() {
  if(!gcrun) {
   gcrun = true;
   System.out.println("Beginning to finalize after " + created + " Chairs have been created");
  }
  if(i == 47) {
   System.out.println("Finalizing Chair #47, " +"Setting flag to stop Chair creation");
   f = true;
  }
  finalized++;
  if(finalized >= created)
   System.out.println("All " + finalized + " finalized");
  }
  }

  public class Garbage {
  public static void main(String[] args) {
  if(args.length == 0) {
   System.err.println("Usage: " + "java Garbage before or: " + "java Garbage after");
   return;
  }
  while(!Chair.f) {
   new Chair();
   new String("To take up space");
  }
  System.out.println("After all Chairs have been created: " + "total created = " + Chair.created +
  ", total finalized = " + Chair.finalized);
  if(args[0].equals("before")) {
    System.out.println("gc():");
    System.gc();
    System.out.println("runFinalization():");
    System.runFinalization();
  }
  System.out.println("bye!");
  if(args[0].equals("after"))
   System.runFinalizersOnExit(true);
  }
  }


  上面這個(gè)程序創(chuàng)建了許多Chair對象,而且在垃圾收集器開始運(yùn)行后的某些時(shí)候,程序會停止創(chuàng)建Chair。由于垃圾收集器可能在任何時(shí)間運(yùn)行,所以我們不能準(zhǔn)確知道它在何時(shí)啟動(dòng)。因此,程序用一個(gè)名為gcrun的標(biāo)記來指出垃圾收集器是否已經(jīng)開始運(yùn)行。利用第二個(gè)標(biāo)記f,Chair可告訴main()它應(yīng)停止對象的生成。這兩個(gè)標(biāo)記都是在finalize()內(nèi)部設(shè)置的,它調(diào)用于垃圾收集期間。另兩個(gè)static變量--created以及finalized--分別用于跟蹤已創(chuàng)建的對象數(shù)量以及垃圾收集器已進(jìn)行完收尾工作的對象數(shù)量。最后,每個(gè)Chair都有它自己的(非static)int i,所以能跟蹤了解它具體的編號是多少。編號為47的Chair進(jìn)行完收尾工作后,標(biāo)記會設(shè)為true,最終結(jié)束Chair對象的創(chuàng)建過程。(關(guān)于這個(gè)例子的更具體的分析和說明請參看《Java編程思想》的第四章)

   關(guān)于垃圾收集的幾點(diǎn)補(bǔ)充

  經(jīng)過上述的說明,可以發(fā)現(xiàn)垃圾回收有以下的幾個(gè)特點(diǎn):

 。1)垃圾收集發(fā)生的不可預(yù)知性:由于實(shí)現(xiàn)了不同的垃圾收集算法和采用了不同的收集機(jī)制,所以它有可能是定時(shí)發(fā)生,有可能是當(dāng)出現(xiàn)系統(tǒng)空閑CPU資源時(shí)發(fā)生,也有可能是和原始的垃圾收集一樣,等到內(nèi)存消耗出現(xiàn)極限時(shí)發(fā)生,這與垃圾收集器的選擇和具體的設(shè)置都有關(guān)系。

  (2)垃圾收集的精確性:主要包括2 個(gè)方面:(a)垃圾收集器能夠精確標(biāo)記活著的對象;(b)垃圾收集器能夠精確地定位對象之間的引用關(guān)系。前者是完全地回收所有廢棄對象的前提,否則就可能造成內(nèi)存泄漏。而后者則是實(shí)現(xiàn)歸并和復(fù)制等算法的必要條件。所有不可達(dá)對象都能夠可靠地得到回收,所有對象都能夠重新分配,允許對象的復(fù)制和對象內(nèi)存的縮并,這樣就有效地防止內(nèi)存的支離破碎。 (3)現(xiàn)在有許多種不同的垃圾收集器,每種有其算法且其表現(xiàn)各異,既有當(dāng)垃圾收集開始時(shí)就停止應(yīng)用程序的運(yùn)行,又有當(dāng)垃圾收集開始時(shí)也允許應(yīng)用程序的線程運(yùn)行,還有在同一時(shí)間垃圾收集多線程運(yùn)行。

 。4)垃圾收集的實(shí)現(xiàn)和具體的JVM 以及JVM的內(nèi)存模型有非常緊密的關(guān)系。不同的JVM 可能采用不同的垃圾收集,而JVM 的內(nèi)存模型決定著該JVM可以采用哪些類型垃圾收集,F(xiàn)在,HotSpot 系列JVM中的內(nèi)存系統(tǒng)都采用先進(jìn)的面向?qū)ο蟮目蚣茉O(shè)計(jì),這使得該系列JVM都可以采用最先進(jìn)的垃圾收集。

 。5)隨著技術(shù)的發(fā)展,現(xiàn)代垃圾收集技術(shù)提供許多可選的垃圾收集器,而且在配置每種收集器的時(shí)候又可以設(shè)置不同的參數(shù),這就使得根據(jù)不同的應(yīng)用環(huán)境獲得最優(yōu)的應(yīng)用性能成為可能。

  針對以上特點(diǎn),我們在使用的時(shí)候要注意:

 。1)不要試圖去假定垃圾收集發(fā)生的時(shí)間,這一切都是未知的。比如,方法中的一個(gè)臨時(shí)對象在方法調(diào)用完畢后就變成了無用對象,這個(gè)時(shí)候它的內(nèi)存就可以被釋放。

  (2)Java中提供了一些和垃圾收集打交道的類,而且提供了一種強(qiáng)行執(zhí)行垃圾收集的方法--調(diào)用System.gc(),但這同樣是個(gè)不確定的方法。Java 中并不保證每次調(diào)用該方法就一定能夠啟動(dòng)垃圾收集,它只不過會向JVM發(fā)出這樣一個(gè)申請,到底是否真正執(zhí)行垃圾收集,一切都是個(gè)未知數(shù)。

 。3)挑選適合自己的垃圾收集器。一般來說,如果系統(tǒng)沒有特殊和苛刻的性能要求,可以采用JVM的缺省選項(xiàng)。否則可以考慮使用有針對性的垃圾收集器,比如增量收集器就比較適合實(shí)時(shí)性要求較高的系統(tǒng)之中。系統(tǒng)具有較高的配置,有比較多的閑置資源,可以考慮使用并行標(biāo)記/清除收集器。

 。4)關(guān)鍵的也是難把握的問題是內(nèi)存泄漏。良好的編程習(xí)慣和嚴(yán)謹(jǐn)?shù)木幊虘B(tài)度永遠(yuǎn)是最重要的,不要讓自己的一個(gè)小錯(cuò)誤導(dǎo)致內(nèi)存出現(xiàn)大漏洞。

 。5)盡早釋放無用對象的引用。大多數(shù)程序員在使用臨時(shí)變量的時(shí)候,都是讓引用變量在退出活動(dòng)域(scope)后,自動(dòng)設(shè)置為null,暗示垃圾收集器來收集該對象,還必須注意該引用的對象是否被監(jiān)聽,如果有,則要去掉監(jiān)聽器,然后再賦空值。

  結(jié)束語

  一般來說,Java開發(fā)人員可以不重視JVM中堆內(nèi)存的分配和垃圾處理收集,但是,充分理解Java的這一特性可以讓我們更有效地利用資源。同時(shí)要注意finalize()方法是Java的缺省機(jī)制,有時(shí)為確保對象資源的明確釋放,可以編寫自己的finalize方法。




相關(guān)文章