国产激情久久久久影院小草_国产91高跟丝袜_99精品视频99_三级真人片在线观看

JAVA垃圾收集算法與內(nèi)存泄露的解決方法

時(shí)間:2024-10-16 09:46:10 SUN認(rèn)證 我要投稿
  • 相關(guān)推薦

JAVA垃圾收集算法與內(nèi)存泄露的解決方法

  對(duì)于垃圾收集算法與內(nèi)存泄露的問(wèn)題,下面為大家整理了關(guān)于JAVA垃圾收集算法與內(nèi)存泄露解決方法,希望對(duì)你有所幫助。

JAVA垃圾收集算法與內(nèi)存泄露的解決方法

  1.垃圾收集算法的核心思想

  Java語(yǔ)言建立了垃圾收集機(jī)制,用以跟蹤正在使用的對(duì)象和發(fā)現(xiàn)并回收不再使用(引用)的對(duì)象。該機(jī)制可以有效防范動(dòng)態(tài)內(nèi)存分配中可能發(fā)生的兩個(gè)危險(xiǎn):因內(nèi)存垃圾過(guò)多而引發(fā)的內(nèi)存耗盡,以及不恰當(dāng)?shù)膬?nèi)存釋放所造成的內(nèi)存非法引用。

  垃圾收集算法的核心思想是:對(duì)虛擬機(jī)可用內(nèi)存空間,即堆空間中的對(duì)象進(jìn)行識(shí)別,如果對(duì)象正在被引用,那么稱(chēng)其為存活對(duì)象,反之,如果對(duì)象不再被引用,則為垃圾對(duì)象,可以回收其占據(jù)的空間,用于再分配。垃圾收集算法的選擇和垃圾收集系統(tǒng)參數(shù)的合理調(diào)節(jié)直接影響著系統(tǒng)性能,因此需要開(kāi)發(fā)人員做比較深入的了解。

  2.觸發(fā)主GC(Garbage Collector)的條件

  JVM進(jìn)行次GC的頻率很高,但因?yàn)檫@種GC占用時(shí)間極短,所以對(duì)系統(tǒng)產(chǎn)生的影響不大。更值得關(guān)注的是主GC的觸發(fā)條件,因?yàn)樗鼘?duì)系統(tǒng)影響很明顯?偟膩(lái)說(shuō),有兩個(gè)條件會(huì)觸發(fā)主GC:

 、佼(dāng)應(yīng)用程序空閑時(shí),即沒(méi)有應(yīng)用線程在運(yùn)行時(shí),GC會(huì)被調(diào)用。因?yàn)镚C在優(yōu)先級(jí)最低的線程中進(jìn)行,所以當(dāng)應(yīng)用忙時(shí),GC線程就不會(huì)被調(diào)用,但以下條件除外。

 、贘ava堆內(nèi)存不足時(shí),GC會(huì)被調(diào)用。當(dāng)應(yīng)用線程在運(yùn)行,并在運(yùn)行過(guò)程中創(chuàng)建新對(duì)象,若這時(shí)內(nèi)存空間不足,JVM就會(huì)強(qiáng)制地調(diào)用GC線程,以便回收內(nèi)存用于新的分配。若GC一次之后仍不能滿(mǎn)足內(nèi)存分配的要求,JVM會(huì)再進(jìn)行兩次GC作進(jìn)一步的嘗試,若仍無(wú)法滿(mǎn)足要求,則 JVM將報(bào)“out of memory”的錯(cuò)誤,Java應(yīng)用將停止。

  由于是否進(jìn)行主GC由JVM根據(jù)系統(tǒng)環(huán)境決定,而系統(tǒng)環(huán)境在不斷的變化當(dāng)中,所以主GC的運(yùn)行具有不確定性,無(wú)法預(yù)計(jì)它何時(shí)必然出現(xiàn),但可以確定的是對(duì)一個(gè)長(zhǎng)期運(yùn)行的應(yīng)用來(lái)說(shuō),其主GC是反復(fù)進(jìn)行的。

  3.減少GC開(kāi)銷(xiāo)的措施

  根據(jù)上述GC的機(jī)制,程序的運(yùn)行會(huì)直接影響系統(tǒng)環(huán)境的變化,從而影響GC的觸發(fā)。若不針對(duì)GC的特點(diǎn)進(jìn)行設(shè)計(jì)和編碼,就會(huì)出現(xiàn)內(nèi)存駐留等一系列負(fù)面影響。為了避免這些影響,基本的原則就是盡可能地減少垃圾和減少GC過(guò)程中的開(kāi)銷(xiāo)。具體措施包括以下幾個(gè)方面:

  (1)不要顯式調(diào)用System.gc()

  此函數(shù)建議JVM進(jìn)行主GC,雖然只是建議而非一定,但很多情況下它會(huì)觸發(fā)主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數(shù)。

  (2)盡量減少臨時(shí)對(duì)象的使用

  臨時(shí)對(duì)象在跳出函數(shù)調(diào)用后,會(huì)成為垃圾,少用臨時(shí)變量就相當(dāng)于減少了垃圾的產(chǎn)生,從而延長(zhǎng)了出現(xiàn)上述第二個(gè)觸發(fā)條件出現(xiàn)的時(shí)間,減少了主GC的機(jī)會(huì)。

  (3)對(duì)象不用時(shí)最好顯式置為Null

  一般而言,為Null的對(duì)象都會(huì)被作為垃圾處理,所以將不用的對(duì)象顯式地設(shè)為Null,有利于GC收集器判定垃圾,從而提高了GC的效率。

  (4)盡量使用StringBuffer,而不用String來(lái)累加字符串(詳見(jiàn)blog另一篇文章JAVA中String與StringBuffer)

  由于String是固定長(zhǎng)的字符串對(duì)象,累加String對(duì)象時(shí),并非在一個(gè)String對(duì)象中擴(kuò)增,而是重新創(chuàng)建新的String對(duì)象,如Str5=Str1+Str2+Str3+Str4,這條語(yǔ)句執(zhí)行過(guò)程中會(huì)產(chǎn)生多個(gè)垃圾對(duì)象,因?yàn)閷?duì)次作“+”操作時(shí)都必須創(chuàng)建新的String對(duì)象,但這些過(guò)渡對(duì)象對(duì)系統(tǒng)來(lái)說(shuō)是沒(méi)有實(shí)際意義的,只會(huì)增加更多的垃圾。避免這種情況可以改用StringBuffer來(lái)累加字符串,因StringBuffer是可變長(zhǎng)的,它在原有基礎(chǔ)上進(jìn)行擴(kuò)增,不會(huì)產(chǎn)生中間對(duì)象。

  (5)能用基本類(lèi)型如Int,Long,就不用Integer,Long對(duì)象

  基本類(lèi)型變量占用的內(nèi)存資源比相應(yīng)對(duì)象占用的少得多,如果沒(méi)有必要,最好使用基本變量。

  (6)盡量少用靜態(tài)對(duì)象變量

  靜態(tài)變量屬于全局變量,不會(huì)被GC回收,它們會(huì)一直占用內(nèi)存。

  (7)分散對(duì)象創(chuàng)建或刪除的時(shí)間

  集中在短時(shí)間內(nèi)大量創(chuàng)建新對(duì)象,特別是大對(duì)象,會(huì)導(dǎo)致突然需要大量?jī)?nèi)存,JVM在面臨這種情況時(shí),只能進(jìn)行主GC,以回收內(nèi)存或整合內(nèi)存碎片,從而增加主GC的頻率。集中刪除對(duì)象,道理也是一樣的。它使得突然出現(xiàn)了大量的垃圾對(duì)象,空閑空間必然減少,從而大大增加了下一次創(chuàng)建新對(duì)象時(shí)強(qiáng)制主GC的機(jī)會(huì)。

  4.gc與finalize方法

  ⑴gc方法請(qǐng)求垃圾回收

  使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請(qǐng)求Java的垃圾回收。需要注意的是,調(diào)用System.gc()也僅僅是一個(gè)請(qǐng)求。JVM接受這個(gè)消息后,并不是立即做垃圾回收,而只是對(duì)幾個(gè)垃圾回收算法做了加權(quán),使垃圾回收操作容易發(fā)生,或提早發(fā)生,或回收較多而已。

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

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

  protected void finalize() throws Throwable

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

  因此,當(dāng)對(duì)象即將被銷(xiāo)毀時(shí),有時(shí)需要做一些善后工作?梢园堰@些操作寫(xiě)在finalize()方法里。

  java 代碼

  protected void finalize()

  {

  // finalization code here

  }

 、谴a示例

  java 代碼

  class Garbage{

  int index;

  static int count;

  Garbage() {

  count++;

  System.out.println("object "+count+" construct");

  setID(count);

  }

  void setID(int id) {

  index=id;

  }

  protected void finalize() //重寫(xiě)finalize方法

  {

  System.out.println("object "+index+" is reclaimed");

  }

  public static void main(String[] args)

  {

  new Garbage();

  new Garbage();

  new Garbage();

  new Garbage();

  System.gc(); //請(qǐng)求運(yùn)行垃圾收集器

  }

  }

  5.Java 內(nèi)存泄漏

  由于采用了垃圾回收機(jī)制,任何不可達(dá)對(duì)象(對(duì)象不再被引用)都可以由垃圾收集線程回收。因此通常說(shuō)的Java 內(nèi)存泄漏其實(shí)是指無(wú)意識(shí)的、非故意的對(duì)象引用,或者無(wú)意識(shí)的對(duì)象保持。無(wú)意識(shí)的對(duì)象引用是指代碼的開(kāi)發(fā)人員本來(lái)已經(jīng)對(duì)對(duì)象使用完畢,卻因?yàn)榫幋a的錯(cuò)誤而意外地保存了對(duì)該對(duì)象的引用(這個(gè)引用的存在并不是編碼人員的主觀意愿),從而使得該對(duì)象一直無(wú)法被垃圾回收器回收掉,這種本來(lái)以為可以釋放掉的卻最終未能被釋放的空間可以認(rèn)為是被“泄漏了”。

  考慮下面的程序,在ObjStack類(lèi)中,使用push和pop方法來(lái)管理堆棧中的對(duì)象。兩個(gè)方法中的索引(index)用于指示堆棧中下一個(gè)可用位置。push方法存儲(chǔ)對(duì)新對(duì)象的引用并增加索引值,而pop方法減小索引值并返回堆棧最上面的元素。在main方法中,創(chuàng)建了容量為64的棧,并64次調(diào)用push方法向它添加對(duì)象,此時(shí)index的值為64,隨后又32次調(diào)用pop方法,則index的值變?yōu)?2,出棧意味著在堆棧中的空間應(yīng)該被收集。但事實(shí)上,pop方法只是減小了索引值,堆棧仍然保持著對(duì)那些對(duì)象的引用。故32個(gè)無(wú)用對(duì)象不會(huì)被GC回收,造成了內(nèi)存滲漏。

  java 代碼

  public class ObjStack {

  private Object[] stack;

  private int index;

  ObjStack(int indexcount) {

  stack = new Object[indexcount];

  index = 0;

  }

  public void push(Object obj) {

  stack[index] = obj;

  index++;

  }

  public Object pop() {

  index--;

  return stack[index];

  }

  }

  public class Pushpop {

  public static void main(String[] args) {

  int i = 0;

  Object tempobj;

  //new一個(gè)ObjStack對(duì)象,并調(diào)用有參構(gòu)造函數(shù)。分配stack Obj數(shù)組的空間大小為64,可以存64個(gè)對(duì)象,從0開(kāi)始存儲(chǔ)

  ObjStack stack1 = new ObjStack(64);

  while (i < 64)

  {

  tempobj = new Object();//循環(huán)new Obj對(duì)象,把每次循環(huán)的對(duì)象一一存放在stack Obj數(shù)組中。

  stack1.push(tempobj);

  i++;

  System.out.println("第" + i + "次進(jìn)棧" + " ");

  }

  while (i > 32)

  {

  tempobj = stack1.pop();//這里造成了空間的浪費(fèi)。

  //正確的pop方法可改成如下所指示,當(dāng)引用被返回后,堆棧刪除對(duì)他們的引用,因此垃圾收集器在以后可以回收他們。

  /*

  * public Object pop() {index - -;Object temp = stack [index];stack [index]=null;return temp;}

  */

  i--;

  System.out.println("第" + (64 - i) + "次出棧" + " ");

  }

  }

  }

  6.如何消除內(nèi)存泄漏

  雖然Java虛擬機(jī)(JVM)及其垃圾收集器(garbage collector,GC)負(fù)責(zé)管理大多數(shù)的內(nèi)存任務(wù),Java軟件程序中還是有可能出現(xiàn)內(nèi)存泄漏。實(shí)際上,這在大型項(xiàng)目中是一個(gè)常見(jiàn)的問(wèn)題。避免內(nèi)存泄漏的第一步是要弄清楚它是如何發(fā)生的。本文介紹了編寫(xiě)Java代碼的一些常見(jiàn)的內(nèi)存泄漏陷阱,以及編寫(xiě)不泄漏代碼的一些最佳實(shí)踐。一旦發(fā)生了內(nèi)存泄漏,要指出造成泄漏的代碼是非常困難的。因此本文還介紹了一種新工具,用來(lái)診斷泄漏并指出根本原因。該工具的開(kāi)銷(xiāo)非常小,因此可以使用它來(lái)尋找處于生產(chǎn)中的系統(tǒng)的內(nèi)存泄漏。

  垃圾收集器的作用

  雖然垃圾收集器處理了大多數(shù)內(nèi)存管理問(wèn)題,從而使編程人員的生活變得更輕松了,但是編程人員還是可能犯錯(cuò)而導(dǎo)致出現(xiàn)內(nèi)存問(wèn)題。簡(jiǎn)單地說(shuō),GC循環(huán)地跟蹤所有來(lái)自“根”對(duì)象(堆棧對(duì)象、靜態(tài)對(duì)象、JNI句柄指向的對(duì)象,諸如此類(lèi))的引用,并將所有它所能到達(dá)的對(duì)象標(biāo)記為活動(dòng)的。程序只可以操縱這些對(duì)象;其他的對(duì)象都被刪除了。因?yàn)镚C使程序不可能到達(dá)已被刪除的對(duì)象,這么做就是安全的。

  雖然內(nèi)存管理可以說(shuō)是自動(dòng)化的,但是這并不能使編程人員免受思考內(nèi)存管理問(wèn)題之苦。例如,分配(以及釋放)內(nèi)存總會(huì)有開(kāi)銷(xiāo),雖然這種開(kāi)銷(xiāo)對(duì)編程人員來(lái)說(shuō)是不可見(jiàn)的。創(chuàng)建了太多對(duì)象的程序?qū)?huì)比完成同樣的功能而創(chuàng)建的對(duì)象卻比較少的程序更慢一些(在其他條件相同的情況下)。

  而且,與本文更為密切相關(guān)的是,如果忘記“釋放”先前分配的內(nèi)存,就可能造成內(nèi)存泄漏。如果程序保留對(duì)永遠(yuǎn)不再使用的對(duì)象的引用,這些對(duì)象將會(huì)占用并耗盡內(nèi)存,這是因?yàn)樽詣?dòng)化的垃圾收集器無(wú)法證明這些對(duì)象將不再使用。正如我們先前所說(shuō)的,如果存在一個(gè)對(duì)對(duì)象的引用,對(duì)象就被定義為活動(dòng)的,因此不能刪除。為了確保能回收對(duì)象占用的內(nèi)存,編程人員必須確保該對(duì)象不能到達(dá)。這通常是通過(guò)將對(duì)象字段設(shè)置為null或者從集合(collection)中移除對(duì)象而完成的。但是,注意,當(dāng)局部變量不再使用時(shí),沒(méi)有必要將其顯式地設(shè)置為null。對(duì)這些變量的引用將隨著方法的退出而自動(dòng)清除。

  概括地說(shuō),這就是內(nèi)存托管語(yǔ)言中的內(nèi)存泄漏產(chǎn)生的主要原因:保留下來(lái)卻永遠(yuǎn)不再使用的對(duì)象引用。

  典型泄漏

  既然我們知道了在Java中確實(shí)有可能發(fā)生內(nèi)存泄漏,就讓我們來(lái)看一些典型的內(nèi)存泄漏及其原因。

  全局集合

  在大的應(yīng)用程序中有某種全局的數(shù)據(jù)儲(chǔ)存庫(kù)是很常見(jiàn)的,例如一個(gè)JNDI樹(shù)或一個(gè)會(huì)話表。在這些情況下,必須注意管理儲(chǔ)存庫(kù)的大小。必須有某種機(jī)制從儲(chǔ)存庫(kù)中移除不再需要的數(shù)據(jù)。

  這可能有多種方法,但是最常見(jiàn)的一種是周期性運(yùn)行的某種清除任務(wù)。該任務(wù)將驗(yàn)證儲(chǔ)存庫(kù)中的數(shù)據(jù),并移除任何不再需要的數(shù)據(jù)。

  另一種管理儲(chǔ)存庫(kù)的方法是使用反向鏈接(referrer)計(jì)數(shù)。然后集合負(fù)責(zé)統(tǒng)計(jì)集合中每個(gè)入口的反向鏈接的數(shù)目。這要求反向鏈接告訴集合何時(shí)會(huì)退出入口。當(dāng)反向鏈接數(shù)目為零時(shí),該元素就可以從集合中移除了。

  緩存

  緩存是一種數(shù)據(jù)結(jié)構(gòu),用于快速查找已經(jīng)執(zhí)行的操作的結(jié)果。因此,如果一個(gè)操作執(zhí)行起來(lái)很慢,對(duì)于常用的輸入數(shù)據(jù),就可以將操作的結(jié)果緩存,并在下次調(diào)用該操作時(shí)使用緩存的數(shù)據(jù)。

  緩存通常都是以動(dòng)態(tài)方式實(shí)現(xiàn)的,其中新的結(jié)果是在執(zhí)行時(shí)添加到緩存中的。典型的算法是:

  檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。

  如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。

  將計(jì)算出來(lái)的結(jié)果添加到緩存中,以便以后對(duì)該操作的調(diào)用可以使用。

  該算法的問(wèn)題(或者說(shuō)是潛在的內(nèi)存泄漏)出在最后一步。如果調(diào)用該操作時(shí)有相當(dāng)多的不同輸入,就將有相當(dāng)多的結(jié)果存儲(chǔ)在緩存中。很明顯這不是正確的方法。

  為了預(yù)防這種具有潛在破壞性的設(shè)計(jì),程序必須確保對(duì)于緩存所使用的內(nèi)存容量有一個(gè)上限。因此,更好的算法是:

  檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。

  如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。

  如果緩存所占的空間過(guò)大,就移除緩存最久的結(jié)果。

  將計(jì)算出來(lái)的結(jié)果添加到緩存中,以便以后對(duì)該操作的調(diào)用可以使用。

  通過(guò)始終移除緩存最久的結(jié)果,我們實(shí)際上進(jìn)行了這樣的假設(shè):在將來(lái),比起緩存最久的數(shù)據(jù),最近輸入的數(shù)據(jù)更有可能用到。這通常是一個(gè)不錯(cuò)的假設(shè)。

  新算法將確保緩存的容量處于預(yù)定義的內(nèi)存范圍之內(nèi)。確切的范圍可能很難計(jì)算,因?yàn)榫彺嬷械膶?duì)象在不斷變化,而且它們的引用包羅萬(wàn)象。為緩存設(shè)置正確的大小是一項(xiàng)非常復(fù)雜的任務(wù),需要將所使用的內(nèi)存容量與檢索數(shù)據(jù)的速度加以平衡。

  解決這個(gè)問(wèn)題的另一種方法是使用java.lang.ref.SoftReference類(lèi)跟蹤緩存中的對(duì)象。這種方法保證這些引用能夠被移除,如果虛擬機(jī)的內(nèi)存用盡而需要更多堆的話。

  ClassLoader

  Java ClassLoader結(jié)構(gòu)的使用為內(nèi)存泄漏提供了許多可乘之機(jī)。正是該結(jié)構(gòu)本身的復(fù)雜性使ClassLoader在內(nèi)存泄漏方面存在如此多的問(wèn)題。ClassLoader的特別之處在于它不僅涉及“常規(guī)”的對(duì)象引用,還涉及元對(duì)象引用,比如:字段、方法和類(lèi)。這意味著只要有對(duì)字段、方法、類(lèi)或ClassLoader的對(duì)象的引用,ClassLoader就會(huì)駐留在JVM中。因?yàn)镃lassLoader本身可以關(guān)聯(lián)許多類(lèi)及其靜態(tài)字段,所以就有許多內(nèi)存被泄漏了。

  確定泄漏的位置

  通常發(fā)生內(nèi)存泄漏的第一個(gè)跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError。這通常發(fā)生在您最不愿意它發(fā)生的生產(chǎn)環(huán)境中,此時(shí)幾乎不能進(jìn)行調(diào)試。有可能是因?yàn)闇y(cè)試環(huán)境運(yùn)行應(yīng)用程序的方式與生產(chǎn)系統(tǒng)不完全相同,因而導(dǎo)致泄漏只出現(xiàn)在生產(chǎn)中。在這種情況下,需要使用一些開(kāi)銷(xiāo)較低的工具來(lái)監(jiān)控和查找內(nèi)存泄漏。還需要能夠無(wú)需重啟系統(tǒng)或修改代碼就可以將這些工具連接到正在運(yùn)行的系統(tǒng)上。可能最重要的是,當(dāng)進(jìn)行分析時(shí),需要能夠斷開(kāi)工具而保持系統(tǒng)不受干擾。

  雖然OutOfMemoryError通常都是內(nèi)存泄漏的信號(hào),但是也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對(duì)于后者,或者必須增加JVM可用的堆的數(shù)量,或者對(duì)應(yīng)用程序進(jìn)行某種更改,使它使用較少的內(nèi)存。但是,在許多情況下,OutOfMemoryError都是內(nèi)存泄漏的信號(hào)。一種查明方法是不間斷地監(jiān)控GC的活動(dòng),確定內(nèi)存使用量是否隨著時(shí)間增加。如果確實(shí)如此,就可能發(fā)生了內(nèi)存泄漏。

  拓展:

  java垃圾回收機(jī)制是怎樣的

  手動(dòng)管理內(nèi)存

  在介紹現(xiàn)代版的垃圾回收之前,我們先來(lái)簡(jiǎn)單地回顧下需要手動(dòng)地顯式分配及釋放內(nèi)存的那些日子。如果你忘了去釋放內(nèi)存,那么這塊內(nèi)存就無(wú)法重用了。這塊內(nèi)存被占有了卻沒(méi)被使用。這種場(chǎng)景被稱(chēng)之為內(nèi)存泄露。

  下面是用C寫(xiě)的一個(gè)手動(dòng)管理內(nèi)存的簡(jiǎn)單例子:

  int send_request()

  {

  size_t n = read_size();

  int *elements = malloc(n * sizeof(int));

  if(read_elements(n, elements) < n)

  { // elements not freed! return -1; } // … free(elements)

  return 0;}

  可以看到,你很容易就會(huì)忘了釋放內(nèi)存。內(nèi)存泄露曾經(jīng)是個(gè)非常普遍的問(wèn)題。你只能通過(guò)不斷地修復(fù)自己的代碼來(lái)與它們進(jìn)行抗?fàn)。因此,需要有一種更優(yōu)雅的方式來(lái)自動(dòng)釋放無(wú)用內(nèi)存,以便減少人為錯(cuò)誤的可能性。這種自動(dòng)化過(guò)程又被稱(chēng)為垃圾回收(簡(jiǎn)稱(chēng)GC)。

  智能指針

  自動(dòng)垃圾回收早期的一種實(shí)現(xiàn)便是引用計(jì)數(shù)。你知曉每一個(gè)對(duì)象被引用了幾次,當(dāng)計(jì)數(shù)器歸0的時(shí)候,這個(gè)對(duì)象就可以被安全地回收掉了。C++的共享指針就是一個(gè)非常著名的例子:

  11int send_request() { size_t n = read_size(); stared_ptrelements = make_shared(); if(read_elements(n, elements) < n) { return -1; } return 0;}

  我們使用的sharedptr會(huì)記錄這個(gè)對(duì)象被引用的次數(shù)。如果你將它傳遞給別人則計(jì)數(shù)加一,當(dāng)它離開(kāi)了作用域后便會(huì)減一。一旦這個(gè)計(jì)數(shù)為0,sharedptr會(huì)自動(dòng)地刪除底層對(duì)應(yīng)的vector。當(dāng)然這只是個(gè)示例,因?yàn)橐灿凶x者指出來(lái)了,這個(gè)在現(xiàn)實(shí)中是不太可能出現(xiàn)的,但作為演示是足夠了。

  自動(dòng)內(nèi)存管理

  在上面的C++代碼中,我們還得顯式地聲明我們需要使用內(nèi)存管理。那如果所有的對(duì)象都采用這個(gè)機(jī)制會(huì)怎樣呢?那簡(jiǎn)直就太方便了,這樣開(kāi)發(fā)人員便無(wú)需 考慮清理內(nèi)存的事情了。運(yùn)行時(shí)會(huì)自動(dòng)知曉哪些內(nèi)存不再使用了,然后釋放掉它。也就是說(shuō),它自動(dòng)地回收了這些垃圾。第一代的垃圾回收器是1959年Lisp 引入的,這項(xiàng)技術(shù)迄今為止一直在不斷演進(jìn)。

  引用計(jì)數(shù)

  剛才我們用C++的共享指針?biāo)菔镜南敕ǹ梢詰?yīng)用到所有的對(duì)象上來(lái)。許多語(yǔ)言比如說(shuō)Perl, Python以及PHP,采用的都是這種方式。這個(gè)通過(guò)一張圖可以很容易說(shuō)明:

  綠色的云代表的是程序中仍在使用的對(duì)象。從技術(shù)層面上來(lái)說(shuō),這有點(diǎn)像是正在執(zhí)行的某個(gè)方法里面的局部變量,亦或是靜態(tài)變量之類(lèi)的。不同編程語(yǔ)言的情況可能會(huì)不一樣,因此這并不是我們關(guān)注的重點(diǎn)。

  藍(lán)色的圓圈代表的是內(nèi)存中的對(duì)象,可以看到有多少對(duì)象引用了它們;疑珗A圈的對(duì)象是已經(jīng)沒(méi)有任何人引用的了。因此,它們屬于垃圾對(duì)象,可以被垃圾回收器清理掉。

  看起來(lái)還不錯(cuò)對(duì)吧?沒(méi)錯(cuò),不過(guò)這里存在著一個(gè)重大的缺陷。很容易會(huì)出現(xiàn)一些孤立的環(huán),它們中的對(duì)象都不在任何域內(nèi),但彼此卻互相引用導(dǎo)致引用數(shù)不為0。下面便是一個(gè)例子:

  看到了吧,紅色部分其實(shí)就是應(yīng)用程序不再使用的垃圾對(duì)象。由于引用計(jì)數(shù)的缺陷,因此會(huì)存在內(nèi)存泄露。

  有幾種方法可以解決這一問(wèn)題,比如說(shuō)使用特殊的“弱”引用,或者使用一個(gè)特殊的算法回收循環(huán)引用。之前提到的Perl,Python以及PHP等語(yǔ)言,都是使用類(lèi)似的方法來(lái)回收循環(huán)引用的,不過(guò)這已經(jīng)超出本文講述的范圍了。我們準(zhǔn)備詳細(xì)介紹下JVM所采用的方法。

  標(biāo)記刪除

  首先,JVM對(duì)于對(duì)象可達(dá)性的定義要明確一些。它可不像前面那樣用綠色的云便含煳了事的,而是有著非常明確及具體的垃圾回收根對(duì)象(Garbage Collection Roots)的定義:

  局部變量

  活動(dòng)線程

  靜態(tài)字段

  JNI引用

  其它(后面將會(huì)討論到)

  JVM通過(guò)標(biāo)記刪除的算法來(lái)記錄所有可達(dá)(存活)對(duì)象,同時(shí)確保不可達(dá)對(duì)象的那些內(nèi)存能夠被重用。這包含兩個(gè)步驟:

  標(biāo)記是指遍歷所有可達(dá)對(duì)象,然后在本地內(nèi)存中記錄這些對(duì)象的信息

  刪除會(huì)確保不可達(dá)對(duì)象的內(nèi)存地址可以在下一次內(nèi)存分配中使用。

  JVM中的不同GC算法,比如說(shuō)Parallel Scavenge,Parallel Mark+Copy, CMS都是這一算法的不同實(shí)現(xiàn),只是各階段略有不同而已,從概念上來(lái)講仍然是對(duì)應(yīng)著上面所說(shuō)的那兩個(gè)步驟。

  這種實(shí)現(xiàn)最重要的就是不會(huì)再出現(xiàn)泄露的對(duì)象環(huán)了:

  缺點(diǎn)就是應(yīng)用程序的線程需要被暫停才能完成回收,如果引用一直在變的話你是無(wú)法進(jìn)行計(jì)數(shù)的。這個(gè)應(yīng)用程序被暫停以便JVM可以收拾家務(wù)的情況又被稱(chēng)為Stop The World pause(STW)。這種暫停被觸發(fā)的可能性有很多,不過(guò)垃圾回收應(yīng)該是最常見(jiàn)的一種。

【JAVA垃圾收集算法與內(nèi)存泄露的解決方法】相關(guān)文章:

Java內(nèi)存溢出的類(lèi)型10-03

Java的內(nèi)存劃分全解析12-17

Javascript 閉包引起IE內(nèi)存泄露分析07-05

電腦內(nèi)存常見(jiàn)故障及解決方法05-12

手機(jī)內(nèi)存卡故障及解決方法10-10

Java認(rèn)證輔導(dǎo):Java實(shí)現(xiàn)二叉樹(shù)遍歷算法10-21

電腦內(nèi)存六大故障以及解決方法09-26

JAVA認(rèn)證基礎(chǔ)知識(shí):近似算法(格雷厄姆算法)簡(jiǎn)介10-29

Intel傲騰內(nèi)存是內(nèi)存還是固態(tài)硬盤(pán)06-17