瘋狂Java筆記之Java的內(nèi)存與回收

Java引用的種類

1.對(duì)象在內(nèi)存中的狀態(tài)

對(duì)于JVM的垃圾回收機(jī)制來說,是否回收一個(gè)對(duì)象的標(biāo)準(zhǔn)在于:是否還有引用變量引用改對(duì)象吁断?只要有引用變量引用對(duì)象趁蕊,垃圾回收機(jī)制就不會(huì)回收它。

也就是說仔役,當(dāng)java對(duì)象被創(chuàng)建出來之后掷伙,垃圾回收機(jī)制會(huì)實(shí)時(shí)的監(jiān)控每個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)又兵,引用,被引用任柜,賦值等。當(dāng)垃圾回收機(jī)制實(shí)時(shí)的監(jiān)控到某個(gè)對(duì)象不再被引用變量所引用時(shí)寒波,垃圾回收機(jī)制就會(huì)回收它所占用的空間乘盼。

基本上,可以把JVM內(nèi)存中的對(duì)象引用理解成一種有向圖俄烁,把引用變量绸栅,對(duì)象都當(dāng)成有向圖的頂點(diǎn),將引用關(guān)系當(dāng)成圖的有向邊页屠,有向邊總是從引用端指向被引用的Java對(duì)象粹胯。因?yàn)镴ava的所有對(duì)象都是由一條條線程創(chuàng)建出來的,因此可以把線程對(duì)象當(dāng)成有向圖的起始頂點(diǎn)辰企。

對(duì)于單線程程序而言风纠,整個(gè)程序只有一條main線程,那么該圖就是以main進(jìn)程為頂點(diǎn)的有向圖牢贸。在這個(gè)有向圖中竹观,main頂點(diǎn)可達(dá)的對(duì)象都處于可達(dá)狀態(tài),垃圾回收機(jī)制不會(huì)回收它們潜索;如果某個(gè)對(duì)象在這個(gè)有向圖中處于不可達(dá)狀態(tài)臭增,那么就認(rèn)為這個(gè)對(duì)象不再被引用。

采用有向圖管理內(nèi)存中的對(duì)象具有較高的精度竹习,當(dāng)缺點(diǎn)是效率較低誊抛。
當(dāng)一個(gè)對(duì)象在堆內(nèi)存中運(yùn)行時(shí),根據(jù)它在對(duì)應(yīng)有向圖中的狀態(tài)整陌,可以把它所處的狀態(tài)分成

  • 可達(dá)狀態(tài):當(dāng)一個(gè)對(duì)象被創(chuàng)建后拗窃,有一個(gè)以上的引用變量引用它瞎领。在有向圖中可以從起始頂點(diǎn)導(dǎo)航到該對(duì)象,那么它就處于可達(dá)狀態(tài)随夸,程序可以通過引用變量來調(diào)用該對(duì)
    象的屬性和方法九默。
  • 可恢復(fù)狀態(tài):如果程序中某個(gè)對(duì)象不再有任何引用變量引用它,它將先進(jìn)入可恢復(fù)狀態(tài)逃魄,此時(shí)從有向圖的起始頂點(diǎn)不能導(dǎo)航到該對(duì)象荤西。在這種狀態(tài)下,系統(tǒng)的垃圾回
    收機(jī)制準(zhǔn)備回收該對(duì)象所占用的內(nèi)存伍俘。在回收該對(duì)象之前邪锌,系統(tǒng)會(huì)調(diào)用可恢復(fù)狀態(tài)的對(duì)象的finalize方法進(jìn)行資源清理,如果系統(tǒng)調(diào)用finalize方法重新讓一個(gè)以L的
    引用變量引用該對(duì)象癌瘾,則這個(gè)對(duì)象會(huì)再次變?yōu)榭蛇_(dá)狀態(tài):否則觅丰,該對(duì)象將進(jìn)入不可達(dá)狀態(tài)。
  • 不可達(dá)狀態(tài):書對(duì)象的所有關(guān)聯(lián)都被切斷妨退,fl甲系統(tǒng)調(diào)用所有對(duì)象的finaii}e方法依然沒有使該對(duì)象變成可達(dá)狀態(tài)后妇萄,這個(gè)對(duì)象將永久性地失去引用,最后變成不可達(dá)狀態(tài)咬荷。只有當(dāng)一個(gè)對(duì)象處于不可達(dá)狀態(tài)時(shí)冠句,系統(tǒng)才會(huì)真正回收該對(duì)象所占有的資源。
memery.PNG

一個(gè)對(duì)象可以被一個(gè)方法的局部變量引用幸乒,也可以被其他類的類變量引用懦底,或者被其他對(duì)象的實(shí)例變量引用。當(dāng)某個(gè)對(duì)象被其他類的類變量引用時(shí)罕扎,只有該類被銷毀后聚唐,該對(duì)象才會(huì)進(jìn)入可恢復(fù)狀態(tài);當(dāng)某個(gè)對(duì)象那個(gè)被其他對(duì)象的實(shí)例變量引用時(shí)腔召,只有當(dāng)引用該對(duì)象的對(duì)象被銷毀或變成不可達(dá)狀態(tài)后杆查,改對(duì)象才會(huì)進(jìn)入不可達(dá)狀態(tài)。

對(duì)于垃圾回收機(jī)制來說臀蛛,判斷一個(gè)對(duì)象是否可回收的標(biāo)準(zhǔn)就在改對(duì)象時(shí)候被引用亲桦,因此引用也是JVM進(jìn)行內(nèi)存管理的一個(gè)重要概念。為了更好的管理對(duì)象的引用浊仆,從JDK1.2開始烙肺,Java在java.lang.ref包下提供了三個(gè)類:SoftReference,PhantomReference和WeakReference,它們分別代表了系統(tǒng)對(duì)對(duì)象的三種引用方式:軟引用,虛引用和弱引用氧卧。歸納起來,Java語言對(duì)對(duì)象的引用有如下四種:

  • 強(qiáng)引用
  • 軟引用
  • 弱引用
  • 虛引用

2.強(qiáng)引用

當(dāng)程序創(chuàng)建一個(gè)對(duì)象氏堤,并把這個(gè)對(duì)象賦給一個(gè)引用變量沙绝,這個(gè)引用變量就是強(qiáng)引用搏明。強(qiáng)引用是最常見的。

當(dāng)一個(gè)對(duì)象被一個(gè)或一個(gè)以上的強(qiáng)引用變量所引用時(shí)闪檬,它處于可達(dá)狀態(tài)星著,它不可能被系統(tǒng)垃圾回收機(jī)制回收,即使系統(tǒng)內(nèi)存非常緊張粗悯,即使有些Java對(duì)象以后永遠(yuǎn)都不會(huì)被用到虚循,JVM也不會(huì)回收被強(qiáng)引用所引用的Java對(duì)象。

由于JVM肯定不會(huì)回收被強(qiáng)引用所引用的Java對(duì)象样傍,因此強(qiáng)引用時(shí)造成Java內(nèi)存泄漏的只要原因之一横缔。

3.軟引用

對(duì)于強(qiáng)引用所引用的Java對(duì)象而言,無論系統(tǒng)的內(nèi)存如何緊張衫哥,即使某些Java以后不再使用茎刚,垃圾回收機(jī)制也不會(huì)回收它所占的內(nèi)存。當(dāng)時(shí)軟引用不同撤逢,當(dāng)系統(tǒng)內(nèi)存充足時(shí)膛锭,和強(qiáng)引用是沒有什么區(qū)別的。但是當(dāng)系統(tǒng)內(nèi)存不足時(shí)蚊荣,軟引用所引用的Java對(duì)象可以被垃圾回收機(jī)制回收初狰,從而避免系統(tǒng)內(nèi)存的不足的異常。

當(dāng)程序需要大量創(chuàng)建某個(gè)類的新對(duì)象互例,而且有可能重新訪問已創(chuàng)建的來對(duì)象是奢入,可以充分使用軟引用來解決內(nèi)存緊張的難題。代碼如下:

class Person{
    String name;
    int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return "Person[name="+name+",age="+age+"]";
    }
}

public class Main {
    public static void main(String[] args) {
        SoftReference<Person>[] people=new SoftReference[100000];
        for(int i=0;i<people.length;i++){
            people[i]=new SoftReference<Person>(new Person("HelloJack"+i,i));
        }
        System.out.println(people[2].get());
        System.out.println(people[4].get());
        System.gc();
        System.runFinalization();
        System.out.println(people[2].get());
        System.out.println(people[4].get());
    }
}

4.弱引用

軟引用與軟引用有點(diǎn)相似敲霍,區(qū)別在于弱引用所引用的對(duì)象的生命周期更短俊马。弱引用通過WeakReference類實(shí)現(xiàn)。對(duì)于軟引用的對(duì)象而言肩杈,當(dāng)系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí)柴我,不管系統(tǒng)內(nèi)存時(shí)候足夠,總會(huì)回收改對(duì)象所占用的內(nèi)存扩然。當(dāng)然艘儒,并不是說當(dāng)一個(gè)對(duì)象只有弱引用時(shí),它就會(huì)立即被回收夫偶,正如那些失去引用的對(duì)象一樣界睁,必須等到系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí)才會(huì)被回收。

弱引用具有很大的不確定性兵拢,因?yàn)槊看卫厥諜C(jī)制執(zhí)行時(shí)都會(huì)回收弱引用所引用的對(duì)象翻斟,而垃圾回收機(jī)制的運(yùn)行又不受程序員的控制,因此程序獲取弱引用所引用的Java對(duì)象是必須小心空指針異常说铃,通過弱引用所獲取的Java對(duì)象可能是null.
代碼如下:

String str=new String("HelloJack");
WeakReference<String> wr=new WeakReference<String>(str);
str=null;
System.out.println(wr.get());
System.gc();
System.runFinalization();
System.out.println(wr.get());

5.虛引用

弱引用和軟引用可以單獨(dú)使用访惜,當(dāng)虛引用不能單獨(dú)使用嘹履,單獨(dú)使用虛引用沒有太大的意義。虛引用的主要作用就是跟蹤對(duì)象被垃圾回收的狀態(tài)债热,程序可以通過檢查與虛引用關(guān)聯(lián)的引用隊(duì)列中是否包含指定的虛引用砾嫉,從而了解虛引用所引用的對(duì)象是否即將被回收。虛引用通過PhantomReference類實(shí)現(xiàn)窒篱,他完全類似于沒有引用焕刮。虛引用對(duì)對(duì)象本身沒有太大的影響,對(duì)象甚至感覺不到虛引用的存在墙杯。
代碼如下:

String str=new String("HelloJack");
ReferenceQueue<String> rq=new ReferenceQueue<String>();
PhantomReference<String> pr=new PhantomReference<>(str,rq);

str=null;
System.out.println(pr.get());
System.gc();
System.runFinalization();

System.out.println(rq.poll()==pr);

Java的內(nèi)存泄漏

程序運(yùn)行過程中會(huì)不斷地分配內(nèi)存空間配并,那些不在使用的內(nèi)存空間應(yīng)該即時(shí)被回收,從而保證系統(tǒng)可以再次使用這些內(nèi)存霍转,如果存在無用的內(nèi)存空間應(yīng)該即時(shí)被回收荐绝,從而保證系統(tǒng)可以再次使用這些內(nèi)存,如果存在無用的內(nèi)存沒有被回收回來避消,那就內(nèi)存泄漏低滩。

垃圾回收機(jī)制

垃圾回收機(jī)制只要完成兩件事:

  • 跟蹤并監(jiān)控每個(gè)Java對(duì)象,當(dāng)某個(gè)對(duì)象處于不可達(dá)狀態(tài)岩喷,回收該對(duì)象所占用的內(nèi)存
  • 清理內(nèi)存分配恕沫,回收過程中產(chǎn)生的內(nèi)存碎片

一個(gè)高效的JVM一個(gè)重要的方面是提供高效的垃圾回收機(jī)制,高效的垃圾回收機(jī)制可以保證垃圾回收的快速運(yùn)行纱意,避免應(yīng)用程序的性能瓶頸婶溯,又不會(huì)到時(shí)應(yīng)用程序卡頓。

1.垃圾回收的基本算法

實(shí)際上偷霉,垃圾回收機(jī)制不可能實(shí)時(shí)檢測(cè)到每個(gè)Java對(duì)象的狀態(tài)迄委,因此當(dāng)一個(gè)對(duì)象失去引用后,它也不會(huì)立即被回收类少,只有等垃圾回收機(jī)制運(yùn)行時(shí)才會(huì)被回收叙身。
對(duì)于一個(gè)垃圾回收器的設(shè)計(jì)算法來說,大致如下可供選擇的設(shè)計(jì)硫狞。

  • 串行回收(Serial)和并行回收(Parallel):串行回收就是不管系統(tǒng)有多少個(gè)CPU信轿,始終只用一個(gè)CPU來執(zhí)行回收操作;而并行回收就是把整個(gè)回收工作拆分成多部分残吩,每個(gè)部分有一個(gè)CPU負(fù)責(zé)财忽,從而讓多個(gè)CPU并行回收。并行回收的執(zhí)行效率很高泣侮,但復(fù)雜度增加即彪,另外也有其他一些副作用,比如內(nèi)存碎片會(huì)增加等活尊。
  • 并發(fā)執(zhí)行(Concorrent)和應(yīng)用程序(Stop-the-world)停止:Stop-the-world的垃圾回收方式在執(zhí)行垃圾回收的同時(shí)會(huì)導(dǎo)致應(yīng)用程序暫停祖凫。并發(fā)執(zhí)行的垃圾回收雖然不會(huì)導(dǎo)致應(yīng)用程序暫停琼蚯,但由于并發(fā)執(zhí)行垃圾回收需要解決和應(yīng)用程序的執(zhí)行沖突(應(yīng)用程序可能會(huì)在垃圾回收的過程中修改對(duì)象),因此并發(fā)執(zhí)行垃圾回收的系統(tǒng)開銷比Stop-the-world更高惠况,而且執(zhí)行時(shí)也需要更多的堆內(nèi)存。
  • 壓縮(Compacting)/不壓縮(Non-compacting)和復(fù)制(Copying):為了減少內(nèi)存碎片宁仔,支持壓縮的垃圾回收器會(huì)把所有的活對(duì)象搬遷到一起稠屠,然后將之前占用的內(nèi)存全部回收。不壓縮的垃圾回收器只是回收內(nèi)存翎苫,這樣回收回來的內(nèi)存不可能是連續(xù)的权埠,因此將有較多的內(nèi)存碎片,相對(duì)壓縮垃圾回收機(jī)制煎谍,不壓縮垃圾回收機(jī)制回收內(nèi)存更快攘蔽,而分配內(nèi)存是就會(huì)更慢,而且無法解決內(nèi)存碎片的問題呐粘。復(fù)制
    垃圾回收會(huì)將所有的可達(dá)對(duì)象復(fù)制到另一塊相同的內(nèi)存中满俗,這種方式的優(yōu)點(diǎn)是垃圾回收過程不會(huì)產(chǎn)生內(nèi)存碎片,但缺點(diǎn)也很明顯作岖,需要復(fù)制數(shù)據(jù)和額外的內(nèi)存唆垃。

上面介紹的復(fù)制,不壓縮痘儡,壓縮都是垃圾回收器回收已用內(nèi)存空間的方式辕万,關(guān)于這三種方式詳述如下:

  • 復(fù)制:將堆內(nèi)存分成兩個(gè)相同空間,從根(類似有向圖起始頂點(diǎn))開始訪問每一個(gè)關(guān)聯(lián)的可達(dá)對(duì)象沉删,將空間A的可達(dá)對(duì)象全部復(fù)制到空間B渐尿,然后一次性回收整個(gè)空間A。

對(duì)于復(fù)制算法而言矾瑰,因?yàn)橹恍柙L問所有的可達(dá)對(duì)象砖茸,將所有的可達(dá)對(duì)象復(fù)制完成后就回收整個(gè)空間,完全不用理會(huì)那些不可達(dá)對(duì)象脯倚,所以遍歷空間的成本較小渔彰,但需要巨大的復(fù)制成本和較多的內(nèi)存。

  • 標(biāo)記清除(mark-sweep):也就是不壓縮回收方式推正。垃圾回收器先從根開始訪問所有的可達(dá)對(duì)象恍涂,將他們標(biāo)記為可狀態(tài),然后再遍歷一次整個(gè)內(nèi)存區(qū)域植榕,對(duì)所有的沒有標(biāo)記為可達(dá)對(duì)象進(jìn)行垃圾回收處理再沧。

  • 標(biāo)記壓縮(mark-sweep-compact):這是壓縮回收方式,這種方式充分利用上述兩種算法的優(yōu)點(diǎn)尊残,垃圾回收器先從根開始訪問所有的可達(dá)對(duì)象炒瘸,將它們標(biāo)記為可達(dá)狀態(tài)淤堵。接下來垃圾回收器會(huì)將這些活動(dòng)對(duì)象搬遷在一起,這個(gè)過程也被稱為內(nèi)存壓縮顷扩,然后垃圾回收機(jī)制再次回收那些不可達(dá)對(duì)象所占用的內(nèi)存空間拐邪,這樣對(duì)避免了回收產(chǎn)生內(nèi)存碎片。

上面無論用哪種回收方式隘截,具體實(shí)現(xiàn)起來總是利弊參半扎阶。因此,實(shí)際垃圾回收時(shí)總是使用多種設(shè)計(jì)方式婶芭,也就是針對(duì)不同的情況采用不同的垃圾回收方式實(shí)現(xiàn)东臀。

現(xiàn)行的垃圾回收器用分代的方式來采用不用的回收設(shè)計(jì)。分代的基本思路是根據(jù)對(duì)象生存時(shí)間的長(zhǎng)短犀农,把堆內(nèi)存分成三代:

  • Young(新生代)
  • Old(老年代)
  • Permanent(永生代)

垃圾回收器會(huì)根據(jù)不同代的特點(diǎn)采用不同的回收算法惰赋,從而充分利用各種回收算法的優(yōu)點(diǎn)。

2.堆內(nèi)存的分代回收

分代回收的一個(gè)依據(jù)就是對(duì)象生存時(shí)間的長(zhǎng)短呵哨,然后根據(jù)不同代采取不同的垃圾回收策略赁濒。采用這種“分代回收”的策略基于如下兩點(diǎn)事實(shí)。

  • 絕大多數(shù)的對(duì)象不會(huì)被長(zhǎng)時(shí)間引用仇穗,這些對(duì)象在其Young期間就會(huì)被回收流部。
  • 很老的對(duì)象(生存時(shí)間很長(zhǎng))和很新的對(duì)象(生存時(shí)間很短)之間很少存在互相引用的情況。

對(duì)于Young代的對(duì)象而言纹坐,大部分對(duì)象都會(huì)很快進(jìn)入不可達(dá)狀態(tài)枝冀,只要少量的對(duì)象能熬到垃圾回收?qǐng)?zhí)行,而垃圾回收器只需保留Young代中處于可達(dá)狀態(tài)的對(duì)象耘子,如果采用復(fù)制算法只需要少量的復(fù)制成本果漾,因此大部分垃圾回收器對(duì)Young代都采用復(fù)制算法。

  1. Young代

對(duì)Young代采用復(fù)制算法只需遍歷那些處于可達(dá)狀態(tài)的對(duì)象谷誓,而且這些對(duì)象的數(shù)量較少绒障,可復(fù)制成本也不大,因此可以充分發(fā)揮復(fù)制算法的優(yōu)點(diǎn)捍歪。

Young代由一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)構(gòu)成户辱。絕大多數(shù)對(duì)象先分配到Eden區(qū)中(有一些大的對(duì)象可能會(huì)直接被分配到old代中),Survivor區(qū)中的對(duì)象都至少在Young代中經(jīng)歷過一次垃圾回收糙臼,所以這些對(duì)象在被轉(zhuǎn)移到old代之前會(huì)先保留在Survivor空間中庐镐。同一時(shí)間兩個(gè)Sunrtvor空間中有一個(gè)用來保存對(duì)象,而另一個(gè)是空的变逃,用來在下次垃圾回收時(shí)保存Young代中的對(duì)象必逆。每次復(fù)制就是將Aden和第一個(gè)Survivpr區(qū)的可達(dá)對(duì)象復(fù)制到第二個(gè)Survivor區(qū),然后清空Eden與第一個(gè)Survivor區(qū)。

young.PNG

2.Old代

如果Young代中的對(duì)象經(jīng)過數(shù)次的垃圾回收依然沒有被回收掉名眉,即這個(gè)對(duì)象經(jīng)過足夠長(zhǎng)的時(shí)間還處于可達(dá)狀態(tài)粟矿,垃圾回收機(jī)制就會(huì)將這個(gè)對(duì)象轉(zhuǎn)移到Old代。

old.PNG

Old代的大部分對(duì)象都是“久經(jīng)考驗(yàn)”的老人了损拢,因此它們沒有那么容易被回收陌粹。而且隨著時(shí)間的流逝,Old代的對(duì)象會(huì)越來越多福压,因此Old代的空間要比Young代的空間更大申屹。出于這兩點(diǎn)考慮,具有如下特征:

  • Old垃圾回收的執(zhí)行頻率無須太高隧膏。因?yàn)楹苌儆袑?duì)象會(huì)死掉。
  • 每次對(duì)Old代執(zhí)行垃圾回收都需要更長(zhǎng)的時(shí)間來完成嚷那。

基于以上考慮胞枕,垃圾回收器一般會(huì)采用標(biāo)記壓縮算法,這個(gè)算法可以避免復(fù)制Old代的大量對(duì)象魏宽,而且Old代的對(duì)象不會(huì)很快死亡腐泻,回收過程不會(huì)大量的產(chǎn)生內(nèi)存碎片。因此相對(duì)比較劃算队询。

3.Permanent代
Permanent代主要用于裝載Class,方法等信息派桩,默認(rèn)為64MB,垃圾回收機(jī)制通常不會(huì)回收Permanent代的對(duì)象蚌斩。對(duì)于那些需要加載很多類的服務(wù)器程序铆惑,往往需要加大Permanent代的內(nèi)存,否則可能因?yàn)閮?nèi)存不足而導(dǎo)致程序終止送膳。

當(dāng)Young代的內(nèi)存將要用完時(shí)员魏,垃圾回收機(jī)制會(huì)對(duì)Young代進(jìn)行垃圾回收,垃圾回收機(jī)制會(huì)采用較高的頻率對(duì)Yn}rng代進(jìn)行掃描和回收叠聋。因?yàn)檫@種回收的系統(tǒng)開銷比較小撕阎,因此也被稱為次要回收(minor collection ).當(dāng)old代的內(nèi)存將要用完時(shí),垃圾回收機(jī)制會(huì)進(jìn)行全回收碌补,也就是對(duì)Young代和old代都要進(jìn)行回收虏束,此時(shí)回收成本就大得多了,因此也稱為主要
回收(major callectivn)厦章。

通常來說镇匀,Young代的內(nèi)存會(huì)先被回收,而且會(huì)使用專門的回收算法(復(fù)制算法)來回收Young代的內(nèi)存:對(duì)于Old代的回收頻率則要低得多闷袒,因此也會(huì)采用專門的回收算法坑律。如果需要進(jìn)行內(nèi)存壓縮,那么每個(gè)代都獨(dú)立地進(jìn)行壓縮。

3.常見的垃圾回收器

1.串行回收器

串行回收器通過對(duì)Young代和Old代的回收都是串行的(只使用一個(gè)CPU)晃择,而且垃圾回收?qǐng)?zhí)行期間會(huì)使的應(yīng)用程序產(chǎn)生暫停谒养。具體策略為,Young代采用串行復(fù)制算法夹厌,Old代采用串行標(biāo)記壓縮算法稽揭。

2.并行回收器

并行回收器對(duì)于Young代采用與串行回收器基本形似的回收算法,只是增加了多CPU并行的能力浪蹂,即同時(shí)啟動(dòng)多線程并行來執(zhí)行垃圾回收抵栈。線程數(shù)默認(rèn)問CPU個(gè)數(shù),當(dāng)計(jì)算機(jī)中的CPU很多時(shí)坤次,可以用-XX:ParallelGCThreads=size來減少并行線程的數(shù)目古劲。

3.并行壓縮回收器

并行壓縮回收器的改變主要體現(xiàn)在對(duì)Old代的回收上。系統(tǒng)首先將Old代劃分成幾個(gè)固定大小的區(qū)域缰猴。在Mark階段产艾,多個(gè)垃圾回收線程會(huì)并行標(biāo)記Old代中的可達(dá)對(duì)象。當(dāng)某個(gè)對(duì)象被標(biāo)記為可達(dá)對(duì)象時(shí)滑绒,還會(huì)更新對(duì)象所在區(qū)域的大小闷堡,以及該對(duì)象的位置信息。

接下來是summary階段疑故。summary階段直接操作Old代的區(qū)域杠览,而不是單個(gè)的對(duì)象。由于每次垃圾回收的壓縮都會(huì)在Old代的左邊部分存儲(chǔ)大量的可達(dá)對(duì)象纵势,對(duì)這樣的高密度可達(dá)對(duì)象的區(qū)域進(jìn)行壓縮往往很不劃算踱阿。所以summary階段會(huì)從最左邊的區(qū)域開始檢測(cè)每個(gè)區(qū)域的密度,當(dāng)檢測(cè)到某個(gè)區(qū)域中能回收的空間達(dá)到了某個(gè)數(shù)值時(shí)(也就是可達(dá)對(duì)象的密度較小時(shí))吨悍,垃圾回收器會(huì)判定該區(qū)域扫茅,以及該區(qū)域右邊的所有區(qū)域都應(yīng)該進(jìn)行回收,而該區(qū)域左邊的區(qū)域都會(huì)被標(biāo)識(shí)為密集區(qū)域育瓜,垃圾回收器既不會(huì)把新對(duì)象移動(dòng)到這些密集區(qū)域中葫隙,也不會(huì)對(duì)這些密集區(qū)域進(jìn)行壓縮;該區(qū)域和其右邊的所有區(qū)域都會(huì)被壓縮并回收空間。summary階段目前還是串行操作躏仇,雖然并行是可以實(shí)現(xiàn)的恋脚,但重要性不如對(duì)mark和壓縮階段的并行重要。

最后是compact階段焰手≡忝瑁回收器利用summary階段生成的數(shù)據(jù)識(shí)別出有哪些區(qū)域是需要裝填的,多個(gè)垃圾回收線程可以并行地將數(shù)據(jù)復(fù)制到這些區(qū)域中书妻。經(jīng)過這個(gè)過程后船响,Old代的一端會(huì)密集地存在大量的活動(dòng)對(duì)象,另一端則存在大塊的空閑塊。

4.并發(fā)標(biāo)識(shí)-清理(Mark-Sweep)回收器(CMS)

CMS回收器對(duì)Young代的回收方式和并行回收器的回收方式完全相同见间。由于對(duì)Young代的回收依然采用復(fù)制回收算法聊闯,因此垃圾回收時(shí)依然會(huì)導(dǎo)致程序暫停,除非依靠多CPU并行來提高垃圾回收的速度米诉。

通常來說菱蔬,建議適當(dāng)加大Young代的內(nèi)存。如果Young代的內(nèi)存足夠大就不用頻繁地進(jìn)行垃圾回收了史侣,而且增大垃圾回收的時(shí)間間隔后可以讓更多的位于Young代中的Java對(duì)象自己死掉拴泌,從而避免復(fù)制。但將Young代的內(nèi)存設(shè)得過大也有一個(gè)壞處:當(dāng)垃圾回收器回收Young代的內(nèi)存時(shí)惊橱,復(fù)制成本會(huì)顯著上升(復(fù)制算法必須等Young代滿了之后才開始回收)蚪腐,所以回收時(shí)會(huì)讓系統(tǒng)的暫停時(shí)間顯著加大。

CMS對(duì)Old代的回收多數(shù)是并發(fā)操作税朴,而不是并行操作削茁。垃圾回收開始時(shí)需要一個(gè)短暫的暫停,此階段稱為初始標(biāo)識(shí)(initial mark)階段掉房,這個(gè)階段僅僅標(biāo)識(shí)出那些被直接引用的可達(dá)對(duì)象。接下來進(jìn)入并發(fā)標(biāo)識(shí)階段( concurrent marking phase)慰丛,垃圾回收器會(huì)依據(jù)在初始標(biāo)識(shí)中發(fā)現(xiàn)的可達(dá)對(duì)象來尋找其他的可達(dá)對(duì)象卓囚。由于在并發(fā)標(biāo)識(shí)階段應(yīng)用程序也會(huì)同時(shí)在運(yùn)行,無法保證所有的可達(dá)對(duì)象都被標(biāo)識(shí)出來诅病,因此應(yīng)用程序會(huì)再次很短地暫停一下哪亿,多線程并行地重新標(biāo)識(shí)之前可能因?yàn)榫l(fā)而漏掉的對(duì)象,這個(gè)階段被稱為再標(biāo)識(shí)(remark)階段贤笆。

完成了再標(biāo)識(shí)以后蝇棉,所有的可達(dá)對(duì)象都已經(jīng)被標(biāo)識(shí)出來了,接下來就可以運(yùn)行并發(fā)清理操作了芥永。

4.內(nèi)存管理小技巧

  • 盡量使用直接量
    當(dāng)需要使用字符串篡殷,還有Byte,Short,Integer,Long,Float,Double,Boolean,Character包裝類的實(shí)例時(shí),程序不應(yīng)該采用new的方式來創(chuàng)建對(duì)象埋涧,而應(yīng)該直接采用直接量來創(chuàng)建它們板辽。
  • 使用StringBuilder和StringBuffer進(jìn)行字符串連接

String,StringBuilder,StringBuffer都可以代表字符串,其中String代表字符序列不可變的字符串棘催,而StringBuilder和StringBuffer都代表字符序列可變的字符串

如果程序使用多個(gè)String對(duì)象進(jìn)行字符串連接運(yùn)算劲弦,在運(yùn)行時(shí)將生產(chǎn)大量的臨時(shí)字符串,這些字符串會(huì)保存在內(nèi)存中從而到時(shí)程序性能下降醇坝。

  • 盡早釋放無用對(duì)象的引用

大部分時(shí)候邑跪,方法的局部引用變量所引用對(duì)象會(huì)隨著方法的結(jié)束而變成垃圾,因?yàn)榫植孔兞康纳嫫谙藓芏蹋?dāng)方法運(yùn)行結(jié)束時(shí)画畅,該方法內(nèi)的局部變量就結(jié)束了生存期限砸琅。因此大部分時(shí)候程序無須將局部引用變量顯示設(shè)為null.

public void info(){
    Object object=new Object();
    System.out.println(object.toString());
    System.out.println(object.hashCode());
    object=null;
}

上面的方法隨著info()方法執(zhí)行完成,obj引用變量的作用域就結(jié)束了夜赵,原來的obj所引用的對(duì)象就會(huì)變成垃圾明棍。因此object=null是沒有必要的。但是如下代碼:

public void info(){
    Object object=new Object();
    System.out.println(object.toString());
    System.out.println(object.hashCode());
    object=null;
    //執(zhí)行耗時(shí)寇僧,耗內(nèi)存操作
    //或者調(diào)用耗時(shí)摊腋,耗內(nèi)存的方法
}

上面因?yàn)樾枰獔?zhí)行耗內(nèi)存耗時(shí)的方法,可以盡早的釋放對(duì)Object對(duì)象的引用嘁傀。所以可能的是程序在執(zhí)行耗時(shí)兴蒸,耗內(nèi)存操作時(shí),obj之前所引用的Object對(duì)象可能被垃圾回收了细办。

  • 盡量少用靜態(tài)變量

從理論上來說橙凳,Java對(duì)象何時(shí)被回收由垃圾回收機(jī)制決定,對(duì)程序員來說是不確定的笑撞。由于垃圾回收機(jī)制判斷一個(gè)對(duì)象是否是垃圾的唯一標(biāo)準(zhǔn)是該對(duì)象是否有引用變量引用它岛啸,因此推薦盡早釋放對(duì)象的引用。

最好的情況是茴肥,某個(gè)對(duì)象被static變量所引用坚踩,那么垃圾回收機(jī)制通常是不會(huì)回收這個(gè)對(duì)象所占的內(nèi)存的。如下代碼:

class Person{
    static Obejct obj=new Object();
}

Obj變量是Person類的靜態(tài)變量瓤狐,因此它的生命周期與Person類同步瞬铸。在Person類不被卸載的情況下,Person類對(duì)應(yīng)的Class對(duì)象會(huì)常駐內(nèi)存础锐,直到程序運(yùn)行結(jié)束嗓节。因此,obj所引用的Object對(duì)象一旦被創(chuàng)建皆警,也會(huì)常駐內(nèi)存拦宣,知道程序結(jié)束。

  • 避免在經(jīng)常調(diào)用的方法信姓,循環(huán)中創(chuàng)建Java對(duì)象

經(jīng)常調(diào)用的方法和循環(huán)有一個(gè)共同特征:這些代碼段會(huì)被多次重復(fù)調(diào)用恢着。如下:

for(int i=0;i<10;i++){
    Object obj=new Object();
}

雖然上面的obj是局部變量,執(zhí)行完之后會(huì)失效财破,當(dāng)時(shí)要循環(huán)創(chuàng)建10次掰派,系統(tǒng)要不斷地分配空間,執(zhí)行初始化操作左痢。這些對(duì)象的生存時(shí)間又不長(zhǎng)靡羡,所以系統(tǒng)又要回收它們所占的內(nèi)存空間系洛,在這些操作中會(huì)消耗不少性能。

  • 緩存經(jīng)常使用的對(duì)象

如果有些對(duì)象需要被經(jīng)常使用略步,則可以考慮把這些對(duì)象用緩存池保存起來描扯,,這樣當(dāng)下次需要時(shí)就可直接拿出這些對(duì)象來用趟薄,典型的緩存就是數(shù)據(jù)連接池绽诚,數(shù)據(jù)連接池緩存了大量的數(shù)據(jù)庫連接,每次需要訪問數(shù)據(jù)庫是都可以直接去除數(shù)據(jù)庫連接杭煎。

除此之外恩够,系統(tǒng)的一些常用基礎(chǔ)信息也可以通過緩存的方式存起來。實(shí)現(xiàn)緩存一般有兩種方式:

1.使用HashMap進(jìn)行緩存(不宜存儲(chǔ)過多數(shù)據(jù)羡铲,從而內(nèi)存過大蜂桶,導(dǎo)致性能下降)

2.直接使用某些開源項(xiàng)目進(jìn)行緩存

  • 盡量不要使用finalize()方法

當(dāng)一個(gè)對(duì)象失去引用之后,垃圾回收器準(zhǔn)備回收該對(duì)象之前也切,垃圾回收機(jī)制會(huì)先調(diào)用改對(duì)象的finalize()方法來執(zhí)行資源清理扑媚。處于這種考慮,可能有些開發(fā)者會(huì)考慮使用finalize()方法來進(jìn)行資源清理雷恃。

實(shí)際上疆股,將資源清理放在finalize()方法中完成是非常拙劣的選擇。根據(jù)前面介紹的垃圾回收算法倒槐,垃圾回收機(jī)制工作量已經(jīng)夠大了押桃,尤其是回收Young代內(nèi)存時(shí), 大都會(huì)引起應(yīng)用程序暫停导犹,使得用戶難以忍受。

在垃圾回收器本身已經(jīng)嚴(yán)重制約應(yīng)用程序性能的情況下羡忘,如果再選擇使用finalize()方法進(jìn)行資源清理谎痢,無疑是一種火上澆油的行為,這將導(dǎo)致垃圾回收器的負(fù)擔(dān)更大卷雕,導(dǎo)致程序運(yùn)行效率更差节猿。

  • 考慮使用SoftReference

當(dāng)程序需要?jiǎng)?chuàng)建長(zhǎng)度很大的數(shù)組時(shí),可以考慮使用SoftReference來包裝數(shù)組元素漫雕,而不是直接讓數(shù)組元素來引用對(duì)象滨嘱。

SoftReference是一個(gè)很好的選擇,當(dāng)內(nèi)存充足時(shí)不回收數(shù)據(jù)浸间,當(dāng)內(nèi)存不充足時(shí)釋放軟引用所引用的對(duì)象太雨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市魁蒜,隨后出現(xiàn)的幾起案子囊扳,更是在濱河造成了極大的恐慌吩翻,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锥咸,死亡現(xiàn)場(chǎng)離奇詭異狭瞎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)搏予,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門熊锭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雪侥,你說我怎么就攤上這事碗殷。” “怎么了校镐?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵亿扁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鸟廓,道長(zhǎng)从祝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任引谜,我火速辦了婚禮牍陌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘员咽。我一直安慰自己毒涧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布贝室。 她就那樣靜靜地躺著契讲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滑频。 梳的紋絲不亂的頭發(fā)上捡偏,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音峡迷,去河邊找鬼银伟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绘搞,可吹牛的內(nèi)容都是我干的彤避。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夯辖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼琉预!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蒿褂,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤模孩,失蹤者是張志新(化名)和其女友劉穎尖阔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榨咐,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡介却,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了块茁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齿坷。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖数焊,靈堂內(nèi)的尸體忽然破棺而出永淌,到底是詐尸還是另有隱情,我是刑警寧澤佩耳,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布遂蛀,位于F島的核電站,受9級(jí)特大地震影響干厚,放射性物質(zhì)發(fā)生泄漏李滴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一蛮瞄、第九天 我趴在偏房一處隱蔽的房頂上張望所坯。 院中可真熱鬧,春花似錦挂捅、人聲如沸芹助。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽状土。三九已至,卻和暖如春伺糠,著一層夾襖步出監(jiān)牢的瞬間蒙谓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工退盯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泻肯。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓渊迁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親灶挟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琉朽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡(jiǎn)欲明心閱讀 89,492評(píng)論 17 311
  • 在Java中稚铣,它的內(nèi)存管理包括兩方面:內(nèi)存分配(創(chuàng)建Java對(duì)象的時(shí)候)和內(nèi)存回收箱叁,這兩方面工作都是由JVM自動(dòng)完...
    juexin閱讀 289評(píng)論 0 1
  • 1.一些概念 1.1.數(shù)據(jù)類型 Java虛擬機(jī)中墅垮,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型「基本類型的變量保存原始...
    落落落落大大方方閱讀 4,540評(píng)論 4 86
  • 烤瓷牙出現(xiàn)了一些問題算色,可以通過修復(fù)的方式得到解決。那么杭州烤瓷牙修復(fù)是如何化腐朽為神奇使牙齒重新變得的美麗螟够,相信這...
    杭州時(shí)光閱讀 484評(píng)論 0 0
  • 今天值得給自己點(diǎn)贊的事灾梦,開車回了趟固陽,跑了一天妓笙,回來已經(jīng)錯(cuò)過了預(yù)約瑜伽的時(shí)間若河,哎喲,心里矛盾呀寞宫,累了一天萧福,還...
    史真如閱讀 97評(píng)論 0 0