高性能Java代碼之內(nèi)存管理
本文通過幾個方面,來介紹Java代碼的內(nèi)存管理。
有的代碼,GC根本就回收不了,直接系統(tǒng)掛掉。GC是一段程序,不是智能,他只回收他認為的垃圾,而不是回收你認為的垃圾。
GC垃圾回收:
Grabage Collection相信學(xué)過JAVA的人都知道這個是什么意思.但是他是如何工作的呢?
首先,JVM在管理內(nèi)存的時候?qū)τ谧兞康墓芾砜偸欠中聦ο蠛屠蠈ο?。新對象也就是開發(fā)者new出來的對象,但是由于生命周期短,那么他占用的內(nèi)存并不是馬上釋放,而是被標記為老對象,這個時候該對象還是要存在一段時間。然后由JVM決定他是否是垃圾對象,并進行回收。
所以我們可以知道,垃圾內(nèi)存并不是用完了馬上就被釋放,所以就會產(chǎn)生內(nèi)存釋放不及時的現(xiàn)象,從而降低了內(nèi)存的使用。而當程序浩大的時候。這種現(xiàn)象更為明顯,并且GC的工作也是需要消耗資源的。所以,也就會產(chǎn)生內(nèi)存浪費。
JVM中的對象生命周期里談內(nèi)存回收:
對象的生命周期一般分為7個階段:創(chuàng)建階段,應(yīng)用階段,不可視階段,不可到達階段,可收集階段,終結(jié)階段,釋放階段。
創(chuàng)建階段:首先大家看一下,如下兩段代碼:
test1:
- for( int i=0; i<10000; i++)
- Object obj=new Object();
test2:
- Object obj=null;
- for( int i=0; i<10000; i++)
- obj=new Object();
這兩段代碼都是相同的功能,但是顯然test2的性能要比test1性能要好,內(nèi)存使用率要高,這是為什么呢?原因很簡單,test1每次執(zhí)行for循環(huán)都要創(chuàng)建一個Object的臨時對象,但是這些臨時對象由于JVM的GC不能馬上銷毀,所以他們還要存在很長時間,而test2則只是在內(nèi)存中保存一份對象的引用,而不必創(chuàng)建大量新臨時變量,從而降低了內(nèi)存的使用。
另外不要對同一個對象初始化多次。例如:
- public class A{
- private Hashtable table = new Hashtable();
- public A(){ table = new Hashtable();
- // 這里應(yīng)該去掉,因為table已經(jīng)被初始化.
- }
- }
這樣就new了兩個Hashtable,但是卻只使用了一個。另外一個則沒有被引用.而被忽略掉.浪費了內(nèi)存.并且由于進行了兩次new操作.也影響了代碼的執(zhí)行速度。
應(yīng)用階段:即該對象至少有一個引用在維護他.
不可視階段:即超出該變量的作用域。這里有一個很好的做法,因為JVM在GC的時候并不是馬上進行回收,而是要判斷對象是否被其他引用在維護.所以,這個時候如果我們在使用完一個對象以后對其obj=null或者obj.doSomething()操作,將其標記為空,可以幫助JVM及時發(fā)現(xiàn)這個垃圾對象.
不可到達階段:就是在JVM中找不到對該對象的直接或者間接的引用。
可收集階段,終結(jié)階段,釋放階段:此為回收器發(fā)現(xiàn)該對象不可到達,finalize方法已經(jīng)被執(zhí)行,或者對象空間已被重用的時候。
JAVA的析構(gòu)方法:
可能不會有人相信,JAVA有析構(gòu)函數(shù)? 是的,有。因為JAVA所有類都繼承至Object類,而finalize就是Object類的一個方法,這個方法在JAVA中就是類似于C++析構(gòu)函數(shù).一般來說可以通過重載finalize方法的形式才釋放類中對象.如:
- public class A{
- public Object a;
- public A(){ a = new Object ;}
- protected void finalize() throws java.lang.Throwable{
- a = null; // 標記為空,釋放對象
- super.finalize(); // 遞歸調(diào)用超類中的finalize方法.
- }
- }
當然,什么時候該方法被調(diào)用是由JVM來決定的\
一般來說,我們需要創(chuàng)建一個destory的方法來顯式的調(diào)用該方法.然后在finalize也對該方法進行調(diào)用,實現(xiàn)雙保險的做法.
由于對象的創(chuàng)建是遞歸式的,也就是先調(diào)用超級類的構(gòu)造,然后依次向下遞歸調(diào)用構(gòu)造函數(shù),所以應(yīng)該避免在類的構(gòu)造函數(shù)中初始化變量,這樣可以避免不必要的創(chuàng)建對象造成不必要的內(nèi)存消耗.當然這里也就看出來接口的優(yōu)勢.
數(shù)組的創(chuàng)建:
由于數(shù)組需要給定一個長度,所以在不確定數(shù)據(jù)數(shù)量的時候經(jīng)常會創(chuàng)建過大,或過小的數(shù)組的現(xiàn)象.造成不必要的內(nèi)存浪費,所以可以通過軟引用的方式來告訴JVM及時回收該內(nèi)存.(軟引用,具體查資料).
例如:
- Object obj = new char[10000000000000000];
- SoftReference ref = new SoftReference(obj);
共享靜態(tài)存儲空間:
我們都知道靜態(tài)變量在程序運行期間其內(nèi)存是共享的,因此有時候為了節(jié)約內(nèi)存工件,將一些變量聲明為靜態(tài)變量確實可以起到節(jié)約內(nèi)存空間的作用.但是由于靜態(tài)變量生命周期很長,不易被系統(tǒng)回收,所以使用靜態(tài)變量要合理,不能盲目的使用.以免適得其反。
因此建議在下面情況下使用:
1,變量所包含的對象體積較大,占用內(nèi)存過多.
2,變量所包含對象生命周期較長.
3,變量所包含數(shù)據(jù)穩(wěn)定.
4,該類的對象實例有對該變量所包含的對象的共享需求.(也就是說是否需要作為全局變量).
對象重用與GC:
有的時候,如數(shù)據(jù)庫操作對象,一般情況下我們都需要在各個不同模塊間使用,所以這樣的對象需要進行重用以提高性能.也有效的避免了反復(fù)創(chuàng)建對象引起的性能下降.
一般來說對象池是一個不錯的注意.如下:
- public abstarct class ObjectPool{
- private Hashtable locked,unlocked;
- private long expirationTime;
- abstract Object create();
- abstract void expire( Object o);
- abstract void validate( Object o);
- synchronized Object getObject(){...};
- synchronized void freeObject(Object o){...};
- }
這樣我們就完成了一個對象池,我們可以將通過對應(yīng)的方法來存取刪除所需對象.來維護這快內(nèi)存提高內(nèi)存重用.
當然也可以通過調(diào)用System.gc()強制系統(tǒng)進行垃圾回收操作.當然這樣的代價是需要消耗一些cpu資源.
不要提前創(chuàng)建對象:
盡量在需要的時候創(chuàng)建對象,重復(fù)的分配,構(gòu)造對象可能會因為垃圾回收做額外的工作降低性能.
JVM內(nèi)存參數(shù)調(diào)優(yōu):
強制內(nèi)存回收對于系統(tǒng)自動的內(nèi)存回收機制會產(chǎn)生負面影響,會加大系統(tǒng)自動回收的處理時間,所以應(yīng)該盡量避免顯式使用System.gc(),
JVM的設(shè)置可以提高系統(tǒng)的性能.例如:
java -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -Xms512m -Xmx512m
具體可以查看java幫助文檔.我們主要介紹程序設(shè)計方面的性能提高.
JAVA程序設(shè)計中有關(guān)內(nèi)存管理的其他經(jīng)驗:
根據(jù)JVM內(nèi)存管理的工作原理,可以通過一些技巧和方式讓JVM做GC處理時更加有效.,從而提高內(nèi)存使用和縮短GC的執(zhí)行時間.
1,盡早釋放無用對象的引用.即在不使用對象的引用后設(shè)置為空,可以加速GC的工作.(當然如果是返回值.....)
2,盡量少用finalize函數(shù),此函數(shù)是JAVA給程序員提供的一個釋放對象或資源的機會,但是卻會加大GC工作量.
3,如果需要使用到圖片,可以使用soft應(yīng)用類型,它可以盡可能將圖片讀入內(nèi)存而不引起OutOfMemory.
4,注意集合數(shù)據(jù)類型的數(shù)據(jù)結(jié)構(gòu),往往數(shù)據(jù)結(jié)構(gòu)越復(fù)雜,GC工作量更大,處理更復(fù)雜.
5,盡量避免在默認構(gòu)造器(構(gòu)造函數(shù))中創(chuàng)建,初始化大量的對象.
6,盡量避免強制系統(tǒng)做垃圾回收.會增加系統(tǒng)做垃圾回收的最終時間降低性能.
7,盡量避免顯式申請數(shù)組,如果不得不申請數(shù)組的話,要盡量準確估算數(shù)組大小.
8,如果在做遠程方法調(diào)用.要盡量減少傳遞的對象大小.或者使用瞬間值避免不必要數(shù)據(jù)的傳遞.
9,盡量在合適的情況下使用對象池來提高系統(tǒng)性能減少內(nèi)存開銷,當然,對象池不能過于龐大,會適得其反.
【編輯推薦】