做一個吝嗇的Java程式員——面向GC的編程__演算法

來源:互聯網
上載者:User

原文連結:BAT直通車 ——做一個吝嗇的Java程式員——面向GC的編程

PS:歡迎訪問BAT直通車擷取BAT老司機最新經驗 前言

相比於C++開發Java開發要輕鬆的多,因為程式員不必關心比較trick的記憶體問題。JVM高度最佳化的GC機制能夠保證在絕大多數的情況下,系統能夠很好的處理記憶體問題。但完全依靠GC、編程中不注意相關使用細節,程式往往打不到理想的效能,所以要做一個吝嗇的Java程式員,編程的時候好好思考記憶體、記憶體回收相關點,有助於提升程式的效能。下面會羅列在工作中經常考慮的點。 1.容器初始化要指定大小

看到大部分的程式在使用容器、StringBuilder、StringBuffer的時候都是直接使用,沒有指定初始大小,這是菜鳥的體現。從記憶體、記憶體回收角度出發,這些容器和類在使用的時候一定要給定初始大小。

正是因為上面提到的容器、類可以動態擴充,所以通常我們都不會去考慮設定初始大小,反正不夠了會自動擴容。

但擴容是有代價,是有很高的記憶體代價的。大部分的擴容都是進行copy的,將老的資料重新copy一份加到擴容後的新的結構中,對於不斷擴容的情況,將會產生大量的無用的老的數組,這些數組佔據記憶體,也會增加GC的壓力。對於那些記憶體抖動比較厲害的,往往是發生了這種情況。

所以在使用上述這些容器、類的時候在建構函式中建議加上容量的預估值。 2.少用醜陋的引用置為null的方法

很多時候,我們會看到有人確實是面向GC的編程,在對象不用後將對象置為null,但這個真的有用嗎。

List<MyObject> alist = new ArrayList<MyObject>();.......alist = null;MyBigObject  myobj = new MyBigObject();...myobj = null;

答案是大部分情況這種操作是無用的,只會讓你的代碼變得更難看。

其實GC遠比你想象的聰明,這種操作,益處微乎其微,GC會自行探知到無用的對象。

但是如果是在一個比較大的方法內,這個方法可能很長(展開後幾百行的方法,有時也是常見的)、執行起來又比較耗時,這種情況提前將某些不用的大的對象置為null,這種操作某種情況下是有協助的。 3.慎用對象池

在看一些代碼最佳化的時候,發現很多人會使用對象池進行複用最佳化。這種最佳化出發點是好的,即通過減少對象的分配開銷來提高效能。但帶來的弊端就是由於對象池中的對象會長期存活,大部分對象會晉陞到Old Generation,因此無法通過YoungGC回收。

對於對象池的使用,如果對象本身就很小,初始化開銷並不大,那麼對象池只會增加代碼複雜度,這個就是典型的過度最佳化。如果恰巧對象本身就比較大,那麼晉陞到Old Generation後,對GC的壓力就更大了。再從安全執行緒的角度去考慮,一般情況下,池都會被並發的訪問,如果對象池不處理好並發問題,那麼系統或應用就回面臨相應問題。但處理線程同步同樣也是有開銷的,所以要平衡線程同步的開銷和對象建立的開銷來考慮。

使用對象池最合理的情境就是當建立對象的開銷比較大的時候,例如:

網路連結資料庫連結線程建立
4.手動GC真的有意義嗎。

經常能看到這樣兩行代碼

Thread.yield()System.gc();

前者是主動讓出CPU資源,後者是主動觸發GC,但真的管用嗎。

事實上JVM從保證這兩件事

更要命的是如果在JVM啟動參數中允許顯式GC,則觸發的是FullGC,是的你沒有看錯,就是FullGC,FullGC則意味著Stop the world!!!,這對於某些回應時間要求嚴格的應用來說無異於宕機。。。。

So,我們的結論是: 不要用Thread.yield() 不要用System.gc(),但Native Memory的GC除外

**註:**Native Memory只能通過FullGC或CMS GC回收,除非你清楚的知道什麼時候要調用,且知道其後果,才可以調用System.gc()。當你使用了像 用DirectByteBuffer分配位元組緩衝區 NIO或者NIO架構(如Netty) 用MappedByteBuffer做記憶體映射

這些情況會使用到較多的Native Memory,手動觸發切記要謹慎。安全起見,最好在JVM的啟動參數中加上-XX:+DisableExplicitGC來禁用顯式GC,但又存在矛盾情況,即如果禁用了System.gc(),那麼上面說的Native Memory就無法回收了,所以開發人員在使用的時候要考慮周全,且行且珍惜。 5.關注對象、屬性的範圍

從記憶體回收角度出發,盡量讓對象的聲明周期短一些,儘可能的縮短對象的範圍。請遵循如下建議:

如果可以在方法內聲明的局部變數,就不要聲明為執行個體變數。除非你的對象是單例的或不變的,否則儘可能少地聲明static變數。

局部變數在方法執行後就回被GC回收,所以在變數定義前優先考慮下變數的生命週期。

靜態變數都會在常量池中,記憶體回收不會處理常量池,所以要儘可能少的定義常量。 6.緩衝和引用

如果要自行實現緩衝,最好不用HashMap或ConcurrentHashMap,爭取用弱引用的WeakHashMap,最好是使用緩衝架構,Guava的Cache相對較好,建議使用。 7. 總結

上面提到的6點意見都是實際工作中常用的經驗性總結,在不同的情境中未必有多大的提升,但熟悉這些方法,從JVM記憶體和記憶體回收的角度去思考程式,對於寫出卓越的代碼非常有必要,Java路漫漫,且行且珍惜。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.