JAVA運(yùn)行時(shí)內(nèi)存及垃圾回收

JAVA

1. Java運(yùn)行時(shí)內(nèi)存區(qū)域

在JAVA運(yùn)行時(shí)的內(nèi)存區(qū)域中,由JVM管理的內(nèi)存區(qū)域分為以下幾個(gè)模塊:

  • 程序計(jì)數(shù)區(qū):由當(dāng)前線程獨(dú)占,記錄當(dāng)前線程的字節(jié)碼文件執(zhí)行到哪一行。
  • 虛擬機(jī)棧:由當(dāng)前線程獨(dú)占,存放當(dāng)前線程調(diào)用方法的棧幀的棧。
  • 本地方法棧:由當(dāng)前線程獨(dú)占,和虛擬機(jī)棧類似, 只不過虛擬機(jī)棧記錄的是JAVA方法,本地方法棧記錄的是native方法。
  • 堆:由所有線程共享,存放對(duì)象實(shí)例。
  • 方法區(qū):由所有線程共享,存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息,final常量,靜態(tài)變量,編譯器即時(shí)編譯的代碼等。

棧幀補(bǔ)充:每一個(gè)線程都有一個(gè)虛擬機(jī)棧,每當(dāng)線程中執(zhí)行一個(gè)方法的時(shí)候,就會(huì)向虛擬機(jī)棧中插入一個(gè)棧幀,當(dāng)方法執(zhí)行完后,再將棧幀出棧。棧幀中包含局部變量表,操作站,方法出口等。
局部變量表中存儲(chǔ)著方法相關(guān)的局部變量,包括基本數(shù)據(jù)類型,對(duì)象的引用,返回地址等。
具體請(qǐng)參考Java內(nèi)存區(qū)域與內(nèi)存溢出

方法區(qū)補(bǔ)充:方法區(qū)屬于垃圾回收機(jī)制中的永久代(一共有青年帶,老年代,永久代三種),因此方法區(qū)的垃圾回收很少,但不代表不會(huì)發(fā)生垃圾回收,其上的垃圾回收主要針對(duì)常量池的內(nèi)存回收和對(duì)已加載類的卸載。

2. JAVA對(duì)象的訪問方式

一般來說,一個(gè)JAVA引用至少會(huì)涉及到三個(gè)內(nèi)存區(qū)域,虛擬機(jī)棧、堆、方法區(qū)。
例如 Object obj = new Object();

  • Object obj表示一個(gè)本地引用,存儲(chǔ)在虛擬機(jī)棧的本地變量表中
  • new Object()作為實(shí)例對(duì)象存放在堆中
  • 堆中還存儲(chǔ)了Object類的類型信息(接口,方法,field,對(duì)象類型等)的地址,這些地址所指向的內(nèi)容存放在方法區(qū)中
3. JAVA內(nèi)存分配及回收機(jī)制

分代分配,分代回收。
JAVA內(nèi)存分為年輕代老年代、永久代

  • 年輕代:對(duì)象被創(chuàng)建時(shí),內(nèi)存分配首先創(chuàng)建在年輕代的Eden區(qū)(如果年輕代空間不足,則大對(duì)象直接分配在老年代上)。大部分對(duì)象很快就不再使用。年輕代的內(nèi)存區(qū)域分為:一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)(比例為8:1:1)
  • 老年代:對(duì)象如果在年輕代存活了很長(zhǎng)時(shí)間沒有被回收掉,就會(huì)被復(fù)制到老年代。老年代的空間比年輕代大,發(fā)生的GC次數(shù)也比年輕代少。
  • 永久代:方法區(qū)屬于永久代,永久代的垃圾回收次數(shù)很少,但是也會(huì)發(fā)生GC。

GC分為兩種:

  • Minor GC
    只會(huì)在年輕代的Eden區(qū)進(jìn)行垃圾回收
  • FULL GC
    會(huì)在年輕代, 老年代, 永久帶都進(jìn)行垃圾回收
    有如下原因可能導(dǎo)致Full GC:
    1.年老代(Tenured)被寫滿;
    2.持久代(Perm)被寫滿;
    3.System.gc()被顯示調(diào)用;
    4.上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化.

GC機(jī)制:

  • 年輕代:主要使用“停止-復(fù)制”算法,停止指的是,發(fā)生GC的時(shí)候會(huì)暫停除了GC線程以外的所有線程的運(yùn)行。
    復(fù)制的過程如下:
    1. 絕大部分的對(duì)象剛創(chuàng)建的時(shí)候會(huì)被分配到Eden區(qū),其中大部分的對(duì)象會(huì)很快消亡。Eden區(qū)是連續(xù)的內(nèi)存空間,因此在其上分配內(nèi)存是很快的。
    2. 最初一次,當(dāng)Eden區(qū)滿的時(shí)候,執(zhí)行Minor GC,將Eden區(qū)的消亡對(duì)象清除掉,并將剩余的對(duì)象放到Survivor1中(此時(shí)Survivor2是空的,兩個(gè)Survivor總有一個(gè)是空的)
    3. 下次Eden再滿的時(shí)候,執(zhí)行Minor GC,將Eden區(qū)的消亡對(duì)象清除掉,將剩余的對(duì)象放到Survivor1中
    4. 當(dāng)Survivor1滿了的時(shí)候則將Eden和Survivor1中的消亡的對(duì)象清除掉,并將Eden和Survivor1中剩余的對(duì)象復(fù)制到Survivor2中
    5. 當(dāng)兩個(gè)Survivor交換了幾次后,就可以將剩下的對(duì)象復(fù)制到老年代中了
  • 老年代:使用“標(biāo)記-整理”算法,即將存活的對(duì)象向一邊移動(dòng),以此來保證回收后,內(nèi)存依然是連續(xù)的,不會(huì)出現(xiàn)內(nèi)存碎片。每次年輕代的Eden發(fā)生Minor GC時(shí),虛擬機(jī)都會(huì)檢查每次晉級(jí)老年代的大小是否大于老年代的剩余大小,如果大于則會(huì)觸發(fā)FULL GC。
  • 永久代:永久代的回收有兩種,常量池中的常量,無用的類的信息。
    常量沒有引用了就可以回收。
    無用的類必須保證3點(diǎn)才可以回收:
    1. 類的所有實(shí)例已經(jīng)被回收
    2. 加載類的ClassLoader已經(jīng)被回收
    3. 類對(duì)象的class對(duì)象沒有被引用(即沒有反射調(diào)用該類的地方)
4. 減少GC開銷的措施

根據(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過程中的開銷。具體措施包括以下幾個(gè)方面:

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

  • 盡量減少臨時(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ì)。

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

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

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

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

  • 分散對(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ì)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容