qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請?jiān)L問 http://qaseven.github.io/

          關(guān)于Java性能方面的9個(gè)謬論

            Java性能問題被冠以某種黑暗魔法的稱謂。一部分是因?yàn)槠淦脚_(tái)的復(fù)雜性,在很多情況下,無法定位其性能問題根源。然而,在以前對于 Java性能的技巧,有一種趨向:認(rèn)為其由人們的智慧,經(jīng)驗(yàn)構(gòu)成,而不是應(yīng)用統(tǒng)計(jì)和實(shí)證推理。在這篇文章中,我希望去驗(yàn)證一些最荒謬的技術(shù)神話。

            1、Java運(yùn)行慢

            在所有最過時(shí)的Java性能謬論當(dāng)中,這可能是最明顯的言論。

            是的,在90年代和20年代初期,Java確實(shí)有點(diǎn)慢。

            然而,在那之后,我們有超過10年的時(shí)間來改進(jìn)虛擬機(jī)和JIT技術(shù),現(xiàn)在Java整個(gè)體系的性能已經(jīng)快的令人驚訝。

            在6個(gè)單獨(dú)的web性能測試基準(zhǔn)中,Java框架占據(jù)了24個(gè)當(dāng)中的22個(gè)前四的位置。

            JVM性能分析組件的使用不僅優(yōu)化了通用的代碼路徑,而且在優(yōu)化那些嚴(yán)重領(lǐng)域也很有成效。JIT編譯代碼的速度在大多數(shù)情況下跟C++一樣快了。

            盡管這樣,關(guān)于Java運(yùn)行慢的言論還是存在,估計(jì)是由于歷史原因造成的偏見,這個(gè)偏見來自當(dāng)時(shí)那些使用Java早期版本的人們。

            我們建議,在匆忙下結(jié)論之前先保留意見和評估一下最新的性能結(jié)果。

            2、單行java代碼意味著任何事都是孤立的

            考慮以下一小段代碼:

            MyObject obj = new MyObject();

            對一個(gè)Java開發(fā)者而言,很明顯能看出這行代碼需要分配一個(gè)對象和運(yùn)行一個(gè)對應(yīng)的構(gòu)造方法。

            從這來看,我們可以開始推出性能邊界。我們知道有一些精確數(shù)量的工作必須繼續(xù),因此基于我們的推測,我們可以計(jì)算出性能影響。

            這兒有個(gè)認(rèn)知偏見,那就是根據(jù)以往的經(jīng)驗(yàn),任何工作都需要被做。

            實(shí)際上,javac和JIT編譯器都可以優(yōu)化無效代碼。就拿JIT編譯器來說,代碼甚至可以基于數(shù)據(jù)分析而被優(yōu)化掉,在這種情況下,該行代碼將不會(huì)被運(yùn)行,因此它就不會(huì)有什么性能方面的影響。

            而且,在一些Java虛擬機(jī)(JVM)中,例如JRockit中,即使代碼路徑?jīng)]有完全失效,JIT編譯器為了避免分配對象甚至可以執(zhí)行分解對象操作。

            這段文字在這里的意義就是當(dāng)處理Java性能方面的問題時(shí),上下文很重要。而且過早的優(yōu)化可能會(huì)產(chǎn)生意料之外的結(jié)果。所以為了獲得最好的結(jié)果,不要試圖過早的優(yōu)化。與其不斷構(gòu)建你的代碼不如用性能調(diào)整技術(shù)去定位并且改正代碼性能的潛在危險(xiǎn)區(qū)。

            3、一個(gè)微基準(zhǔn)測試意味著你認(rèn)為它是什么

            正如以上看到的,推理一小段程序比分析應(yīng)用程序的整體性能更不準(zhǔn)確。盡管如此,開發(fā)者還是喜歡寫為基準(zhǔn)測試。有些人似乎從擺弄平臺(tái)的某些低層次方面獲取無窮無盡的內(nèi)心快感。

            理查德·費(fèi)曼曾說:“第一個(gè)原則是,不要欺騙自己,而且自己是最容易被騙的人”。沒有比編寫Java為基準(zhǔn)測試更切合這個(gè)的例子了。

            寫好微基準(zhǔn)測試是極其困難的。Java平臺(tái)很復(fù)雜,而且很多微基準(zhǔn)測試只對測量瞬態(tài)效應(yīng)或平臺(tái)的其他意外方面有效。

            例如,一個(gè)想當(dāng)然的微基準(zhǔn)測試頻繁地在測量子系統(tǒng)時(shí)間或垃圾回收,而不是在試圖捕捉效果時(shí)結(jié)束。

            只有當(dāng)開發(fā)者和團(tuán)隊(duì)有真正基準(zhǔn)需求的時(shí)候才需要寫微基準(zhǔn)測試。這些基準(zhǔn)測試應(yīng)該打包到項(xiàng)目(包括源代碼)隨項(xiàng)目一起發(fā)布,并且這些基準(zhǔn)測試應(yīng)該是可重現(xiàn)的并可提供給他人審閱和進(jìn)一步的審查。

            Java平臺(tái)的許多優(yōu)化結(jié)果都指的是只運(yùn)行單一基準(zhǔn)測試用例時(shí)所得到的統(tǒng)計(jì)結(jié)果。一個(gè)單獨(dú)的基準(zhǔn)測試必須要多次運(yùn)行才能得到一個(gè)比較趨近于真實(shí)答案的結(jié)果。

            如果你認(rèn)為到了必須要寫微基準(zhǔn)測試的時(shí)候,首先請讀一下Georges, Buytaert, Eeckhout所著的”Statistically Rigorous Java Performance Evaluation”。如果沒有統(tǒng)計(jì)學(xué)的知識,你會(huì)很容易誤入歧途的。

            網(wǎng)上有很多好的工具和社區(qū)來幫助你進(jìn)行基準(zhǔn)測試,例如Google的Caliper。如果你不得不寫基準(zhǔn)測試時(shí),不要埋頭苦干,你需要從他人那里汲取意見和經(jīng)驗(yàn)。

            4、算法慢是性能問題的最普遍原因

            在程序員(和普通大眾)中普遍存在一個(gè)錯(cuò)誤觀點(diǎn)就是他們總是理所當(dāng)然地認(rèn)為自己所負(fù)責(zé)的那部分系統(tǒng)才是最重要的。

            就Java性能這個(gè)問題來說,Java開發(fā)者認(rèn)為算法的質(zhì)量是性能問題的主要原因。開發(fā)者會(huì)考慮如何編碼,因此他們本性上就會(huì)潛意識地去考慮算法。

            實(shí)際上,當(dāng)處理現(xiàn)實(shí)中的性能問題時(shí),算法設(shè)計(jì)占用了解決基本問題不到10%的時(shí)間。

            相反,相對于算法,垃圾回收,數(shù)據(jù)庫訪問和配置錯(cuò)誤會(huì)更可能造成程序緩慢。

            大多數(shù)應(yīng)用程序處理相對少量的數(shù)據(jù),因此即使主算法有缺陷也不會(huì)導(dǎo)致嚴(yán)重的性能問題。因此,我們得承認(rèn)算法對于性能問題來說是次要的;因?yàn)樗惴◣淼牡托鄬τ谄渌糠衷斐傻挠绊憗碚f是相對較小的,大多的性能問題來自于應(yīng)用程序棧的其他部分。

            因此我們的最佳建議就是依靠經(jīng)驗(yàn)和產(chǎn)品數(shù)據(jù)來找到引起性能問題的真正原因。要?jiǎng)邮植杉瘮?shù)據(jù)而不是憑空猜測。

            5、緩存能解決一切問題

            “關(guān)于計(jì)算機(jī)科學(xué)的每一個(gè)問題都可以通過附加另外一個(gè)層面間接的方式被解決”

            這句程序員的格言,來至于David Wheeler(幸虧有因特網(wǎng),至少有另外兩位計(jì)算機(jī)科學(xué)家),是驚人的相似,特別是在web開發(fā)者中。

            通常出現(xiàn)這種謬論是因?yàn)楫?dāng)面對一個(gè)現(xiàn)有的,理解不夠透徹的架構(gòu)時(shí)出現(xiàn)的分析癱瘓。

            與其處理一個(gè)令人生畏的現(xiàn)存系統(tǒng),開發(fā)者經(jīng)常會(huì)選擇躲避它通過添加一個(gè)緩存并且抱著最大的希望。當(dāng)然,這個(gè)方法僅僅使整個(gè)架構(gòu)變的更復(fù)雜,并且對試圖理解產(chǎn)品架構(gòu)現(xiàn)狀的下一位開發(fā)者而言是一件很糟糕的事情。

            夸大的說,不規(guī)則架構(gòu)每次被寫入一行和一個(gè)子系統(tǒng)。然而,在許多情況下,更簡單的重構(gòu)架構(gòu)會(huì)有更好的性能,而且它們幾乎也更易于被理解。

            因此當(dāng)你評估是不是需要緩存時(shí),計(jì)劃去收集基本用法統(tǒng)計(jì)(缺失率,命中率等)去證明實(shí)際上緩存層是個(gè)附加值。

            6、所有應(yīng)用都要考慮到STW

            (譯注:“stop-the-world” 機(jī)制簡稱STW,即,在執(zhí)行垃圾收集算法時(shí),Java應(yīng)用程序的其他所有除了垃圾收集幫助器線程之外的線程都被掛起)

            Java平臺(tái)的一個(gè)存在事實(shí)是,所有應(yīng)用線程必須周期性的停止以便讓垃圾搜集器GC運(yùn)行。這有時(shí)被夸大為嚴(yán)重的弱點(diǎn),即使是在缺少真實(shí)證據(jù)的情況下。

           實(shí)證研究已經(jīng)說明,人類通常無法察覺到頻率超過每200毫秒一次的數(shù)字?jǐn)?shù)據(jù)的變化(例如價(jià)格變動(dòng))。

            因此對以人類作為首要用戶的應(yīng)用,一條有用的經(jīng)驗(yàn)就是200毫秒或低于200毫秒的 Stop-The-World (STW)停頓通常無需考慮。有些應(yīng)用(例如視頻流)需要比這個(gè)更低的GC波動(dòng),但是很多GUI應(yīng)用不是的。

            有少數(shù)應(yīng)用(比如低延遲交易,或者機(jī)械控制系統(tǒng))對200毫秒停頓是不可接受的。除非你的應(yīng)用屬于那個(gè)少數(shù),否則你的用戶察覺到任何由垃圾回收帶來的影響是不太可能的。

            值得注意的是,在具有比物理內(nèi)核更多應(yīng)用線程的系統(tǒng)中,操作系統(tǒng)任務(wù)計(jì)劃將會(huì)干涉對CPU的時(shí)間分片訪問。Stop-The-World聽起來嚇人,但實(shí)際上,每個(gè)應(yīng)用(無論是不是JVM)都必須處理對稀缺計(jì)算資源的內(nèi)容訪問。

            如果不做測量,JVM的方法對應(yīng)用性能帶來的額外影響具有何等意義將無法看清。

            總體來說,判斷停頓的次數(shù)實(shí)際對應(yīng)用的影響是通過打開GC日志的辦法。分析此日志(或者手工,或者用腳本或工具)來確定停頓的次數(shù)。然后再判定這些是否確實(shí)給你的應(yīng)用域帶來問題。最重要的是,問自己一個(gè)最尖銳的問題:有用戶確實(shí)抱怨了嗎?

            7、手工處理的對象池對很大范圍內(nèi)的應(yīng)用都是合適的

            對Stop-The-World停頓的壞感覺引起一個(gè)常見的應(yīng)對,即在java堆的范圍內(nèi),為應(yīng)用程序組發(fā)明它們自己的內(nèi)存管理技術(shù)。經(jīng)常這會(huì)歸結(jié)為實(shí)現(xiàn)一個(gè)對象池(或甚至是全面引用計(jì)數(shù))的方法,并且需要讓任何使用了領(lǐng)域?qū)ο蟮拇a參與進(jìn)來。

            這種技術(shù)幾乎總是被誤導(dǎo)。它通常具有自身久遠(yuǎn)以前的根源,那時(shí)對象定位代價(jià)昂貴,突然的變化被認(rèn)為是不重要的。但現(xiàn)在的世界已經(jīng)非常不同。

            現(xiàn)代的硬件具有難以想象的定位效率;近來桌面或服務(wù)器硬件的內(nèi)存容量至少達(dá)到了2到3GB。這是一個(gè)很大的數(shù)字;除了專業(yè)的使用情形,讓實(shí)際的應(yīng)用充滿那么大的容量不是很容易。

            對象池一般很難正確的實(shí)現(xiàn)(特別是有多個(gè)線程在工作的時(shí)候),并且有幾個(gè)消極的要求使得把它作為一般場景使用成為一個(gè)艱難選擇:

            ● 所有接觸到代碼的開發(fā)者都必須清楚對象池并正確的處理它

            ● “對池清醒”代碼與“對池不清醒”代碼之間的界限必須要通曉并明文規(guī)定

            ● 所有這些附加的復(fù)雜性必須保持最新,并定期評估

            ● 如果這里任何地方失敗了,無聲損壞的風(fēng)險(xiǎn)(類似C中的指針重用)將被再次引入

            總之,只有在GC停頓不能被接受,而且在調(diào)試與重構(gòu)過程中聰明的嘗試也不能縮減停頓到可接受水平的時(shí)候,對象池才可以使用。

            8、在GC中CMS總是比Parallel Old更好

            Oracle JDK默認(rèn)使用一個(gè)并行的,全部停止(stop-the-world STW)垃圾收集器來收集老年代的垃圾。

            另外一個(gè)選擇是并發(fā)標(biāo)記清除(CMS)收集器。這個(gè)收集器允許程序線程在大部分的GC周期中仍然繼續(xù)工作,但它需要付出一些代價(jià)和帶來一些警告。

            允許程序線程和GC線程一起運(yùn)行不可避免地導(dǎo)致對象表的變異同時(shí)又影響到對象的活躍性。這不得不在發(fā)生后進(jìn)行清楚,所以CMS實(shí)際上有兩個(gè)STW階段(通常非常短)。

           這會(huì)帶來一些后果:

            1)所有程序線程不得不放進(jìn)一個(gè)安全點(diǎn)并且在每次完全收集時(shí)停止兩次;

            2)在收集并發(fā)運(yùn)行地同時(shí),程序吞吐量會(huì)減少(通常是50%)

            3)在JVM從事通過CMS來收集垃圾的總體數(shù)據(jù)上(包括CPU周期)比并行收集更加高的。

            依據(jù)程序的情況這些成本或者是值得的或者又不是。但并沒有免費(fèi)的午餐。CMS收集器是一個(gè)卓越的工程品,但它不是萬能藥。

            所以在介紹前,CMS是你正確的GC策略,你得首先考慮Parallel Old的STW是不可接收的和不能調(diào)和的。最后,(我不能足夠地強(qiáng)調(diào)),確定所有的指標(biāo)都從相當(dāng)?shù)纳a(chǎn)系統(tǒng)上得到。

            9、增加堆內(nèi)存會(huì)解決你內(nèi)存溢出的問題

            當(dāng)一個(gè)應(yīng)用程序崩潰,GC中止運(yùn)行時(shí),許多應(yīng)用組會(huì)通過增加堆內(nèi)存來解決問題。在許多情況下,這可以很快解決問題,并爭取時(shí)間來考慮出一個(gè)更深的解決方案。然而,在沒有真正理解性能產(chǎn)生的根源時(shí),這種解決策略實(shí)際上會(huì)使情況更糟糕。

            試想一下,一個(gè)編碼很爛的應(yīng)用構(gòu)造了非常多的領(lǐng)域?qū)ο螅ㄉ芷诖蟾啪S持2,3秒)。如果內(nèi)存分配率足夠高,垃圾回收就會(huì)很快地執(zhí)行,并把這些領(lǐng)域?qū)ο蠓诺侥昀洗R坏┻M(jìn)入了老年代,對象就會(huì)立即死去,但直到下一次完全回收才會(huì)被垃圾回收器回收。

            如果這個(gè)應(yīng)用增加其堆內(nèi)存,那么我們能做的是增加空間,為了存放那些相對短期存在,然后消逝的領(lǐng)域?qū)ο?。這會(huì)使得 Stop-The-World 的時(shí)間更長,對應(yīng)用毫無益處。

            在改變堆內(nèi)存和或其他參數(shù)之前,理解一下對象的動(dòng)態(tài)分配和生命周期是很有必要的。沒做調(diào)查就行動(dòng),只會(huì)使事情更糟。在這里,垃圾回收器的老年分布信息是非常重要的。

            總結(jié)

            當(dāng)說道Java性能調(diào)優(yōu)時(shí)直覺通常會(huì)誤導(dǎo)人。我們需要經(jīng)驗(yàn)數(shù)據(jù)和工具來幫助我們具象化和了解平臺(tái)的特性。

            垃圾收集也許提供了這方面最好的例子。GC子系統(tǒng)對于調(diào)優(yōu)和生產(chǎn)數(shù)據(jù)指導(dǎo)調(diào)整有驚人的潛力,但對于生產(chǎn)程序它是很難去不借助工具來讓產(chǎn)生的數(shù)據(jù)有意義。

            運(yùn)行任何Java進(jìn)程,默認(rèn)都應(yīng)該最少有這些標(biāo)記:

            -verbose:gc(打印GC日志)

            -Xloggc:(更全面的GC日志)

            -XX:+PringGCDetail(更詳細(xì)的輸出)

            -XX:+PrintTenuringDistribution(顯示由JVM設(shè)定的保有閾值)

            然后使用工具來分析日志——手寫腳本和一些生成圖,或一個(gè)可視化工具如(開源的)GCViewer或JClarity Censum。

          posted on 2013-05-06 09:29 順其自然EVO 閱讀(169) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2013年5月>
          2829301234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 江川县| 霍州市| 灵寿县| 黑龙江省| 饶平县| 成都市| 苍溪县| 榆中县| 仪陇县| 浮山县| 许昌县| 六安市| 仁寿县| 湛江市| 馆陶县| 田东县| 洞口县| 石棉县| 三明市| 新郑市| 云龙县| 宜兰县| 鹤岗市| 齐齐哈尔市| 水富县| 亚东县| 伊金霍洛旗| 卢湾区| 岳池县| 双江| 仁寿县| 定州市| 道孚县| 南投县| 阿尔山市| 鄯善县| 黑河市| 潼南县| 巴林左旗| 区。| 青海省|