1. 程式人生 > >高效能Java程式碼之 記憶體管理

高效能Java程式碼之 記憶體管理

http://soft.chinabyte.com/database/8/11981008.shtml

    更甚者你寫的程式碼,GC根本就回收不了,直接系統掛掉。GC是一段程式,不是智慧,他只回收他認為的垃圾,而不是回收你認為的垃圾。
    GC垃圾回收:
    Grabage Collection相信學過JAVA的人都知道這個是什麼意思.但是他是如何工作的呢?
    首先,JVM在管理記憶體的時候對於變數的管理總是分新物件和老物件。新物件也就是開發者new出來的物件,但是由於生命週期短,那麼他佔用的記憶體並不是馬上釋放,而是被標記為老物件,這個時候該物件還是要存在一段時間。然後由JVM決定他是否是垃圾物件,並進行回收。


    所以我們可以知道,垃圾記憶體並不是用完了馬上就被釋放,所以就會產生記憶體釋放不及時的現象,從而降低了記憶體的使用。而當程式浩大的時候。這種現象更為明顯,並且GC的工作也是需要消耗資源的。所以,也就會產生記憶體浪費。
    JVM中的物件生命週期裡談記憶體回收:
    物件的生命週期一般分為7個階段:建立階段,應用階段,不可視階段,不可到達階段,可收集階段,終結階段,釋放階段。
    建立階段:首先大家看一下,如下兩段程式碼:
    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效能要好,記憶體使用率要高,這是為什麼呢?原因很簡單,test1每次執行for迴圈都要建立一個Object的臨時物件,但是這些臨時物件由於JVM的GC不能馬上銷燬,所以他們還要存在很長時間,而test2則只是在記憶體中儲存一份物件的引用,而不必建立大量新臨時變數,從而降低了記憶體的使用。
    另外不要對同一個物件初始化多次。例如:
    public class A{
    private Hashtable table = new Hashtable();

    public A(){   table = new Hashtable();
    // 這裡應該去掉,因為table已經被初始化.
    }
    }
    這樣就new了兩個Hashtable,但是卻只使用了一個。另外一個則沒有被引用.而被忽略掉.浪費了記憶體.並且由於進行了兩次new操作.也影響了程式碼的執行速度。
    應用階段:即該物件至少有一個引用在維護他.
    不可視階段:即超出該變數的作用域。這裡有一個很好的做法,因為JVM在GC的時候並不是馬上進行回收,而是要判斷物件是否被其他引用在維護.所以,這個時候如果我們在使用完一個物件以後對其obj=null或者obj.doSomething()操作,將其標記為空,可以幫助JVM及時發現這個垃圾物件.
    不可到達階段:就是在JVM中找不到對該物件的直接或者間接的引用。
    可收集階段,終結階段,釋放階段:此為回收器發現該物件不可到達,finalize方法已經被執行,或者物件空間已被重用的時候。
    JAVA的析構方法:
    可能不會有人相信,JAVA有解構函式? 是的,有。因為JAVA所有類都繼承至Object類,而finalize就是Object類的一個方法,這個方法在JAVA中就是類似於C++解構函式.一般來說可以通過過載finalize方法的形式才釋放類中物件.如:
    public class A{
    public Object a;
    public A(){ a = new Object ;}
    protected void finalize() throws java.lang.Throwable{
    a = null; // 標記為空,釋放物件
    super.finalize(); // 遞迴呼叫超類中的finalize方法.
    }
    }
    當然,什麼時候該方法被呼叫是由JVM來決定的.......................
    一般來說,我們需要建立一個destory的方法來顯式的呼叫該方法.然後在finalize也對該方法進行呼叫,實現雙保險的做法.
    由於物件的建立是遞迴式的,也就是先呼叫超級類的構造,然後依次向下遞迴呼叫建構函式,所以應該避免在類的建構函式中初始化變數,這樣可以避免不必要的建立物件造成不必要的記憶體消耗.當然這裡也就看出來介面的優勢.
    陣列的建立:
    由於陣列需要給定一個長度,所以在不確定資料數量的時候經常會建立過大,或過小的陣列的現象.造成不必要的記憶體浪費,所以可以通過軟引用的方式來告訴JVM及時回收該記憶體.(軟引用,具體查資料).
    例如:
    Object obj = new char[10000000000000000];
    SoftReference ref = new SoftReference(obj);
    共享靜態儲存空間:
    我們都知道靜態變數在程式執行期間其記憶體是共享的,因此有時候為了節約記憶體工件,將一些變數宣告為靜態變數確實可以起到節約記憶體空間的作用.但是由於靜態變數生命週期很長,不易被系統回收,所以使用靜態變數要合理,不能盲目的使用.以免適得其反。
    因此建議在下面情況下使用:
    1,變數所包含的物件體積較大,佔用記憶體過多.
    2,變數所包含物件生命週期較長.
    3,變數所包含資料穩定.
    4,該類的物件例項有對該變數所包含的物件的共享需求.(也就是說是否需要作為全域性變數).
    物件重用與GC:
    有的時候,如資料庫操作物件,一般情況下我們都需要在各個不同模組間使用,所以這樣的物件需要進行重用以提高效能.也有效的避免了反覆建立物件引起的效能下降.
    一般來說物件池是一個不錯的注意.如下:
    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){...};
    }
    這樣我們就完成了一個物件池,我們可以將通過對應的方法來存取刪除所需物件.來維護這快記憶體提高記憶體重用.
    當然也可以通過呼叫System.gc()強制系統進行垃圾回收操作.當然這樣的代價是需要消耗一些cpu資源.
    不要提前建立物件:
    儘量在需要的時候建立物件,重複的分配,構造物件可能會因為垃圾回收做額外的工作降低效能.
    JVM記憶體引數調優:
    強制記憶體回收對於系統自動的記憶體回收機制會產生負面影響,會加大系統自動回收的處理時間,所以應該儘量避免顯式使用System.gc(),
    JVM的設定可以提高系統的效能.例如:
    java -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -Xms512m -Xmx512m
    具體可以檢視java幫助文件.我們主要介紹程式設計方面的效能提高.
    JAVA程式設計中有關記憶體管理的其他經驗:
    根據JVM記憶體管理的工作原理,可以通過一些技巧和方式讓JVM做GC處理時更加有效.,從而提高記憶體使用和縮短GC的執行時間.
    1,儘早釋放無用物件的引用.即在不使用物件的引用後設置為空,可以加速GC的工作.(當然如果是返回值.....)
    2,儘量少用finalize函式,此函式是JAVA給程式設計師提供的一個釋放物件或資源的機會,但是卻會加大GC工作量.
    3,如果需要使用到圖片,可以使用soft應用型別,它可以儘可能將圖片讀入記憶體而不引起OutOfMemory.
    4,注意集合資料型別的資料結構,往往資料結構越複雜,GC工作量更大,處理更復雜.
    5,儘量避免在預設構造器(建構函式)中建立,初始化大量的物件.
    6,儘量避免強制系統做垃圾回收.會增加系統做垃圾回收的最終時間降低效能.
    7,儘量避免顯式申請陣列,如果不得不申請陣列的話,要儘量準確估算陣列大小.
    8,如果在做遠端方法呼叫.要儘量減少傳遞的物件大小.或者使用瞬間值避免不必要資料的傳遞.
    9,儘量在合適的情況下使用物件池來提高系統性能減少記憶體開銷,當然,物件池不能過於龐大,會適得其反.