日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
面向GC的Java編程

Java程序員在編碼過(guò)程中通常不需要考慮內(nèi)存問(wèn)題,JVM經(jīng)過(guò)高度優(yōu)化的GC機(jī)制大部分情況下都能夠很好地處理堆(Heap)的清理問(wèn)題。以至于許多Java程序員認(rèn)為,我只需要關(guān)心何時(shí)創(chuàng)建對(duì)象,而回收對(duì)象,就交給GC來(lái)做吧!甚至有人說(shuō),如果在編程過(guò)程中頻繁考慮內(nèi)存問(wèn)題,是一種退化,這些事情應(yīng)該交給編譯器,交給虛擬機(jī)來(lái)解決。

站在用戶(hù)的角度思考問(wèn)題,與客戶(hù)深入溝通,找到丹鳳網(wǎng)站設(shè)計(jì)與丹鳳網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶(hù)體驗(yàn)好的作品,建站類(lèi)型包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、空間域名、虛擬主機(jī)、企業(yè)郵箱。業(yè)務(wù)覆蓋丹鳳地區(qū)。

這話(huà)其實(shí)也沒(méi)有太大問(wèn)題,的確,大部分場(chǎng)景下關(guān)心內(nèi)存、GC的問(wèn)題,顯得有點(diǎn)“杞人憂(yōu)天”了,高老爺說(shuō)過(guò):

過(guò)早優(yōu)化是萬(wàn)惡之源。

但另一方面,什么才是“過(guò)早優(yōu)化”?

If we could do things right for the first time, why not?

事實(shí)上JVM的內(nèi)存模型( JMM )理應(yīng)是Java程序員的基礎(chǔ)知識(shí),處理過(guò)幾次JVM線(xiàn)上內(nèi)存問(wèn)題之后就會(huì)很明顯感受到,很多系統(tǒng)問(wèn)題,都是內(nèi)存問(wèn)題。

對(duì)JVM內(nèi)存結(jié)構(gòu)感興趣的同學(xué)可以看下 淺析Java虛擬機(jī)結(jié)構(gòu)與機(jī)制 這篇文章,本文就不再贅述了,本文也并不關(guān)注具體的GC算法,相關(guān)的文章汗牛充棟,隨時(shí)可查。

另外,不要指望GC優(yōu)化的這些技巧,可以對(duì)應(yīng)用性能有成倍的提高,特別是對(duì)I/O密集型的應(yīng)用,或是實(shí)際落在YoungGC上的優(yōu)化,可能效果只是幫你減少那么一點(diǎn)YoungGC的頻率。

但我認(rèn)為,優(yōu)秀程序員的價(jià)值,不在于其所掌握的幾招屠龍之術(shù),而是在細(xì)節(jié)中見(jiàn)真著,就像前面說(shuō)的,如果我們可以一次把事情做對(duì),并且做好,在允許的范圍內(nèi)盡可能追求卓越,為什么不去做呢?

一、GC分代的基本假設(shè)

大部分GC算法,都將堆內(nèi)存做分代(Generation)處理,但是為什么要分代呢,又為什么不叫內(nèi)存分區(qū)、分段,而要用面向時(shí)間、年齡的“代”來(lái)表示不同的內(nèi)存區(qū)域?

GC分代的基本假設(shè)是:

絕大部分對(duì)象的生命周期都非常短暫,存活時(shí)間短。

而這些短命的對(duì)象,恰恰是GC算法需要首先關(guān)注的。所以在大部分的GC中,YoungGC(也稱(chēng)作MinorGC)占了絕大部分,對(duì)于負(fù)載不高的應(yīng)用,可能跑了數(shù)個(gè)月都不會(huì)發(fā)生FullGC。

基于這個(gè)前提,在編碼過(guò)程中,我們應(yīng)該盡可能地縮短對(duì)象的生命周期。在過(guò)去,分配對(duì)象是一個(gè)比較重的操作,所以有些程序員會(huì)盡可能地減少new對(duì)象的次數(shù),嘗試減小堆的分配開(kāi)銷(xiāo),減少內(nèi)存碎片。

但是,短命對(duì)象的創(chuàng)建在JVM中比我們想象的性能更好,所以,不要吝嗇new關(guān)鍵字,大膽地去new吧。

當(dāng)然前提是不做無(wú)謂的創(chuàng)建,對(duì)象創(chuàng)建的速率越高,那么GC也會(huì)越快被觸發(fā)。

結(jié)論:

分配小對(duì)象的開(kāi)銷(xiāo)分享小,不要吝嗇去創(chuàng)建。

GC最喜歡這種小而短命的對(duì)象。

讓對(duì)象的生命周期盡可能短,例如在方法體內(nèi)創(chuàng)建,使其能盡快地在YoungGC中被回收,不會(huì)晉升(romote)到年老代(Old Generation)。

二、對(duì)象分配的優(yōu)化

基于大部分對(duì)象都是小而短命,并且不存在多線(xiàn)程的數(shù)據(jù)競(jìng)爭(zhēng)。這些小對(duì)象的分配,會(huì)優(yōu)先在線(xiàn)程私有的 TLAB 中分配,TLAB中創(chuàng)建的對(duì)象,不存在鎖甚至是CAS的開(kāi)銷(xiāo)。

TLAB占用的空間在Eden Generation。

當(dāng)對(duì)象比較大,TLAB的空間不足以放下,而JVM又認(rèn)為當(dāng)前線(xiàn)程占用的TLAB剩余空間還足夠時(shí),就會(huì)直接在Eden Generation上分配,此時(shí)是存在并發(fā)競(jìng)爭(zhēng)的,所以會(huì)有CAS的開(kāi)銷(xiāo),但也還好。

當(dāng)對(duì)象大到Eden Generation放不下時(shí),JVM只能?chē)L試去Old Generation分配,這種情況需要盡可能避免,因?yàn)橐坏┰贠ld Generation分配,這個(gè)對(duì)象就只能被Old Generation的GC或是FullGC回收了。

三、不可變對(duì)象的好處

GC算法在掃描存活對(duì)象時(shí)通常需要從ROOT節(jié)點(diǎn)開(kāi)始,掃描所有存活對(duì)象的引用,構(gòu)建出對(duì)象圖。

不可變對(duì)象對(duì)GC的優(yōu)化,主要體現(xiàn)在Old Generation中。

可以想象一下,如果存在Old Generation的對(duì)象引用了Young Generation的對(duì)象,那么在每次YoungGC的過(guò)程中,就必須考慮到這種情況。

Hotspot JVM為了提高YoungGC的性能,避免每次YoungGC都掃描Old Generation中的對(duì)象引用,采用了 卡表(Card Table) 的方式。

簡(jiǎn)單來(lái)說(shuō),當(dāng)Old Generation中的對(duì)象發(fā)生對(duì)Young Generation中的對(duì)象產(chǎn)生新的引用關(guān)系或釋放引用時(shí),都會(huì)在卡表中響應(yīng)的標(biāo)記上標(biāo)記為臟(dirty),而YoungGC時(shí),只需要掃描這些dirty的項(xiàng)就可以了。

可變對(duì)象對(duì)其它對(duì)象的引用關(guān)系可能會(huì)頻繁變化,并且有可能在運(yùn)行過(guò)程中持有越來(lái)越多的引用,特別是容器。這些都會(huì)導(dǎo)致對(duì)應(yīng)的卡表項(xiàng)被頻繁標(biāo)記為dirty。

而不可變對(duì)象的引用關(guān)系非常穩(wěn)定,在掃描卡表時(shí)就不會(huì)掃到它們對(duì)應(yīng)的項(xiàng)了。

注意,這里的不可變對(duì)象,不是指僅僅自身引用不可變的final對(duì)象,而是真正的Immutable Objects。

四、引用置為null的傳說(shuō)

早期的很多Java資料中都會(huì)提到在方法體中將一個(gè)變量置為null能夠優(yōu)化GC的性能,類(lèi)似下面的代碼:

 
 
  1. List list = new ArrayList();
  2. // some code
  3. list = null; // help GC

事實(shí)上這種做法對(duì)GC的幫助微乎其微,有時(shí)候反而會(huì)導(dǎo)致代碼混亂。

我記得幾年前 @rednaxelafx 在HLL VM小組中詳細(xì)論述過(guò)這個(gè)問(wèn)題,原帖我沒(méi)找到,結(jié)論基本就是:

在一個(gè)非常大的方法體內(nèi),對(duì)一個(gè)較大的對(duì)象,將其引用置為null,某種程度上可以幫助GC。

大部分情況下,這種行為都沒(méi)有任何好處。

所以,還是早點(diǎn)放棄這種“優(yōu)化”方式吧。

GC比我們想象的更聰明。

五、手動(dòng)檔的GC

在很多Java資料上都有下面兩個(gè)奇技淫巧:

通過(guò)Thread.yield()讓出CPU資源給其它線(xiàn)程。

通過(guò)System.gc()觸發(fā)GC。

事實(shí)上JVM從不保證這兩件事,而System.gc()在JVM啟動(dòng)參數(shù)中如果允許顯式GC,則會(huì)觸發(fā)FullGC,對(duì)于響應(yīng)敏感的應(yīng)用來(lái)說(shuō),幾乎等同于自殺。

So,讓我們牢記兩點(diǎn):

Never use Thread.yield()。

Never use System.gc()。除非你真的需要回收Native Memory。

第二點(diǎn)有個(gè)Native Memory的例外,如果你在以下場(chǎng)景:

· 使用了NIO或者NIO框架(Mina/Netty)

· 使用了DirectByteBuffer分配字節(jié)緩沖區(qū)

· 使用了MappedByteBuffer做內(nèi)存映射

由于Native Memory只能通過(guò)FullGC(或是CMS GC)回收,所以除非你非常清楚這時(shí)真的有必要,否則不要輕易調(diào)用System.gc(),且行且珍惜。

另外為了防止某些框架中的System.gc調(diào)用(例如NIO框架、Java RMI),建議在啟動(dòng)參數(shù)中加上-XX:+DisableExplicitGC來(lái)禁用顯式GC。

這個(gè)參數(shù)有個(gè)巨大的坑,如果你禁用了System.gc(),那么上面的3種場(chǎng)景下的內(nèi)存就無(wú)法回收,可能造成OOM,如果你使用了CMS GC,那么可以用這個(gè)參數(shù)替代:-XX:+ExplicitGCInvokesConcurrent。

關(guān)于System.gc(),可以參考 @bluedavy 的幾篇文章:

· CMS GC會(huì)不會(huì)回收Direct ByteBuffer的內(nèi)存

· 說(shuō)說(shuō)在Java啟動(dòng)參數(shù)上我犯的錯(cuò)

· java.lang.OutOfMemoryError:Map failed

六、指定容器初始化大小

Java容器的一個(gè)特點(diǎn)就是可以動(dòng)態(tài)擴(kuò)展,所以通常我們都不會(huì)去考慮初始大小的設(shè)置,不夠了反正會(huì)自動(dòng)擴(kuò)容唄。

但是擴(kuò)容不意味著沒(méi)有代價(jià),甚至是很高的代價(jià)。

例如一些基于數(shù)組的數(shù)據(jù)結(jié)構(gòu),例如StringBuilder、StringBuffer、ArrayList、HashMap等等,在擴(kuò)容的時(shí)候都需要做ArrayCopy,對(duì)于不斷增長(zhǎng)的結(jié)構(gòu)來(lái)說(shuō),經(jīng)過(guò)若干次擴(kuò)容,會(huì)存在大量無(wú)用的老數(shù)組,而回收這些數(shù)組的壓力,全都會(huì)加在GC身上。

這些容器的構(gòu)造函數(shù)中通常都有一個(gè)可以指定大小的參數(shù),如果對(duì)于某些大小可以預(yù)估的容器,建議加上這個(gè)參數(shù)。

可是因?yàn)槿萜鞯臄U(kuò)容并不是等到容器滿(mǎn)了才擴(kuò)容,而是有一定的比例,例如HashMap的擴(kuò)容閾值和負(fù)載因子(loadFactor)相關(guān)。

Google Guava框架對(duì)于容器的初始容量提供了非常便捷的工具方法,例如:

 
 
  1. Lists.newArrayListWithCapacity(initialArraySize);
  2. Lists.newArrayListWithExpectedSize(estimatedSize);
  3. Sets.newHashSetWithExpectedSize(expectedSize);
  4. Maps.newHashMapWithExpectedSize(expectedSize);

這樣我們只要傳入預(yù)估的大小即可,容量的計(jì)算就交給Guava來(lái)做吧。

反例:

如果采用默認(rèn)無(wú)參構(gòu)造函數(shù),創(chuàng)建一個(gè)ArrayList,不斷增加元素直到OOM,那么在此過(guò)程中會(huì)導(dǎo)致:

多次數(shù)組擴(kuò)容,重新分配更大空間的數(shù)組

多次數(shù)組拷貝

內(nèi)存碎片

七、對(duì)象池

為了減少對(duì)象分配開(kāi)銷(xiāo),提高性能,可能有人會(huì)采取對(duì)象池的方式來(lái)緩存對(duì)象集合,作為復(fù)用的手段。

但是對(duì)象池中的對(duì)象由于在運(yùn)行期長(zhǎng)期存活,大部分會(huì)晉升到Old Generation,因此無(wú)法通過(guò)YoungGC回收。

并且通常……沒(méi)有什么效果。

對(duì)于對(duì)象本身:

如果對(duì)象很小,那么分配的開(kāi)銷(xiāo)本來(lái)就小,對(duì)象池只會(huì)增加代碼復(fù)雜度。

如果對(duì)象比較大,那么晉升到Old Generation后,對(duì)GC的壓力就更大了。

從線(xiàn)程安全的角度考慮,通常池都是會(huì)被并發(fā)訪問(wèn)的,那么你就需要處理好同步的問(wèn)題,這又是一個(gè)大坑,并且同步帶來(lái)的開(kāi)銷(xiāo),未必比你重新創(chuàng)建一個(gè)對(duì)象小。

對(duì)于對(duì)象池,唯一合適的場(chǎng)景就是當(dāng)池中的每個(gè)對(duì)象的創(chuàng)建開(kāi)銷(xiāo)很大時(shí),緩存復(fù)用才有意義,例如每次new都會(huì)創(chuàng)建一個(gè)連接,或是依賴(lài)一次RPC。

比如說(shuō):

· 線(xiàn)程池

· 數(shù)據(jù)庫(kù)連接池

· TCP連接池

即使你真的需要實(shí)現(xiàn)一個(gè)對(duì)象池,也請(qǐng)使用成熟的開(kāi)源框架,例如Apache Commons Pool。

另外,使用JDK的ThreadPoolExecutor作為線(xiàn)程池,不要重復(fù)造輪子,除非當(dāng)你看過(guò)AQS的源碼后認(rèn)為你可以寫(xiě)得比Doug Lea更好。

八、對(duì)象作用域

盡可能縮小對(duì)象的作用域,即生命周期。

如果可以在方法內(nèi)聲明的局部變量,就不要聲明為實(shí)例變量。

除非你的對(duì)象是單例的或不變的,否則盡可能少地聲明static變量。

九、各類(lèi)引用

java.lang.ref.Reference有幾個(gè)子類(lèi),用于處理和GC相關(guān)的引用。JVM的引用類(lèi)型簡(jiǎn)單來(lái)說(shuō)有幾種:

· Strong Reference,最常見(jiàn)的引用

· Weak Reference,當(dāng)沒(méi)有指向它的強(qiáng)引用時(shí)會(huì)被GC回收

· Soft Reference,只當(dāng)臨近OOM時(shí)才會(huì)被GC回收

· Phantom Reference,主要用于識(shí)別對(duì)象被GC的時(shí)機(jī),通常用于做一些清理工作

當(dāng)你需要實(shí)現(xiàn)一個(gè)緩存時(shí),可以考慮優(yōu)先使用WeakHashMap,而不是HashMap,當(dāng)然,更好的選擇是使用框架,例如Guava Cache。

***,再次提醒,以上的這些未必可以對(duì)代碼有多少性能上的提升,但是熟悉這些方法,是為了幫助我們寫(xiě)出更卓越的代碼,和GC更好地合作。


網(wǎng)頁(yè)標(biāo)題:面向GC的Java編程
本文地址:http://www.dlmjj.cn/article/coddjdo.html