Java虛擬機器記憶體優化實踐

2016-03-01    分類:JAVA開發、程式設計開發、首頁精華0人評論發表於2016-03-01

前面一篇文章介紹了Java虛擬機器的體系結構和記憶體模型,既然提到記憶體,就不得不說到記憶體洩露。眾所周知,Java是從C++的基礎上發展而來的,而C++程式的很大的一個問題就是記憶體洩露難以解決,儘管Java的JVM有一套自己的垃圾回收機制來回收記憶體,在許多情況下並不需要java程式開發人員操太多的心,但也是存在洩露問題的,只是比C++小一點。比如說,程式中存在被引用但無用的物件:程式引用了該物件,但後續不會或者不能再使用它,那麼它佔用的記憶體空間就浪費了。

我們先來看看GC是如何工作的:監控每一個物件的執行狀態,包括物件的申請、引用、被引用、賦值等,當該物件不再被引用時,釋放物件(GC本文的重點,不做過多闡述)。很多Java程式設計師過分依賴GC,但問題的關鍵是無論JVM的垃圾回收機制做得多好,記憶體總歸是有限的資源,因此就算GC會為我們完成了大部分的垃圾回收,但適當地注意編碼過程中的記憶體優化還是很必要的。這樣可以有效的減少GC次數,同時提升記憶體利用率,最大限度地提高程式的效率。

總體而言,Java虛擬機器的記憶體優化應從兩方面著手:Java虛擬機器和Java應用程式。前者指根據應用程式的設計通過虛擬機器引數控制虛擬機器邏輯記憶體分割槽的大小以使虛擬機器的記憶體與程式對記憶體的需求相得益彰;後者指優化程式演算法,降低GC負擔,提高GC回收成功率。

通過引數優化虛擬機器記憶體的引數如下所示:

Xms

初始Heap大小

Xmx

java heap最大值

Xmn

young generation的heap大小

Xss

每個執行緒的Stack大小

上面是三個比較常用的引數,還有一些:

XX:MinHeapFreeRatio=40

Minimum percentage of heap free after GC to avoid expansion.

XX:MaxHeapFreeRatio=70

Maximum percentage of heap free after GC to avoid shrinking.

XX:NewRatio=2

Ratio of new/old generation sizes. [Sparc -client:8; x86 -server:8; x86 -client:12.]-client:8 (1.3.1+), x86:12]

XX:NewSize=2.125m

Default size of new generation (in bytes) [5.0 and newer: 64 bit VMs are scaled 30% larger; x86:1m; x86, 5.0 and older: 640k]

XX:MaxNewSize=

Maximum size of new generation (in bytes). Since 1.4, MaxNewSize is computed as a function of NewRatio.

XX:SurvivorRatio=25

Ratio of eden/survivor space size [Solaris amd64: 6; Sparc in 1.3.1: 25; other Solaris platforms in 5.0 and earlier: 32]

XX:PermSize=

Initial size of permanent generation

XX:MaxPermSize=64m

Size of the Permanent Generation. [5.0 and newer: 64 bit VMs are scaled 30% larger; 1.4 amd64: 96m; 1.3.1 -client: 32m.]

下面所說通過優化程式演算法來提高記憶體利用率,並降低記憶體風險,完全是經驗之談,僅供參考,如有不妥,請指正,謝謝!

1.儘早釋放無用物件的引用(XX = null;)

看一段程式碼:

public List<PageData> parse(HtmlPage page) {  
        List<PageData> list = null;          
        try {  
            List valueList = page.getByXPath(config.getContentXpath());  
            if (valueList == null || valueList.isEmpty()) {  
                return list;  
            }  
            //需要時才建立物件,節省記憶體,提高效率  
            list = new ArrayList<PageData>();  
            PageData pageData = new PageData();  
            StringBuilder value = new StringBuilder();  
            for (int i = 0; i < valueList.size(); i++) {  
                HtmlElement content = (HtmlElement) valueList.get(i);  
                DomNodeList<HtmlElement> imgs = content.getElementsByTagName("img");  
                if (imgs != null && !imgs.isEmpty()) {  
                    for (HtmlElement img : imgs) {  
                        try {  
                            HtmlImage image = (HtmlImage) img;  
                            String path = image.getSrcAttribute();  
                            String format = path.substring(path.lastIndexOf("."), path.length());  
                            String localPath = "D:/images/" + MD5Helper.md5(path).replace("\\", ",").replace("/", ",") + format;  
                            File localFile = new File(localPath);  
                            if (!localFile.exists()) {  
                                localFile.createNewFile();  
                                image.saveAs(localFile);  
                            }  
                            image.setAttribute("src", "file:///" + localPath);  
                            localFile = null;  
                            image = null;  
                            img = null;  
                        } catch (Exception e) {  
                        }  
                    }  
                    //這個物件以後不會在使用了,清除對其的引用,等同於提前告知GC,該物件可以回收了  
                    imgs = null;  
                }  
                String text = content.asXml();  
                value.append(text).append("<br/>");  
                valueList=null;  
                content = null;  
                text = null;  
            }  
            pageData.setContent(value.toString());  
            pageData.setCharset(page.getPageEncoding());             
            list.add(pageData);  
            //這裡 pageData=null; 是沒用的,因為list仍然持有該物件的引用,GC不會回收它  
            value=null;  
            //這裡可不能 list=null; 因為list是方法的返回值,否則你從該方法中得到的返回值永遠為空,而且這種錯誤不易被發現、排除  
        } catch (Exception e) {              
        }          
        return list;  
}

2.謹慎使用集合資料型別,如陣列,樹,圖,連結串列等資料結構,這些資料結構對GC來說回收更復雜。

3.避免顯式申請陣列空間,不得不顯式申請時,儘量準確估計其合理值。

4.儘量避免在類的預設構造器中建立、初始化大量的物件,防止在呼叫其自類的構造器時造成不必要的記憶體資源浪費

5.儘量避免強制系統做垃圾記憶體的回收,增長系統做垃圾回收的最終時間

6.儘量做遠端方法呼叫類應用開發時使用瞬間值變數,除非遠端呼叫端需要獲取該瞬間值變數的值。

7.儘量在合適的場景下使用物件池技術以提高系統效能

相關文章