標籤:
小猿做了兩年的c++,上個月竟然被調到java項目,於是第一篇隨筆就想八一八java的記憶體最佳化。
首先最佳化這種事,肯定是應該放到最後去做的,不過在寫代碼的過程中養成良好的習慣也是很重要的。在這裡先推薦一本書《編寫高品質代碼:改善Java程式的151個建議.秦小波》。
首先,在寫代碼的時候,盡量少用對象,能用基本變數代替的就用基本變數,這點下面會舉例。
其次,很多時候你想做一個功能,寫一段代碼,不是用時間換空間就是用空間換時間。要根據這個功能到底是看中時間,還是看中空間,常訪問到的必然是要放到記憶體裡的,但是是不是可以進行壓縮這個也要看對效率是否有影響。
其他的就不多說,相信各位都有自己的好習慣。
主要想說說記憶體最佳化。
小猿現在做的項目需要解析大量資料儲存起來,所以如何節省記憶體非常重要,不然匯入一個100M的檔案,就佔用1G的記憶體,客戶簡直要瘋掉了。
於是小猿進行第一步,排查記憶體的佔用情況。
首先先使用的工具是jdk內建的,jconsole.exe。
用這個軟體可以清晰的看到你程式記憶體、CPU、線程的情況。
剛開始小猿發現明明自己程式堆使用記憶體量沒有那麼大啊,怎麼全部加起來和工作管理員的不一致!再細觀察之,程式的eden space佔用量很小。原來是沒有設定eden space的參數,這個要到啟動設定檔去設定:-Xmn,-XX:NewSize,-XX:MaxNewSize,-XX:NewRatio這些項都可以根據自己的需要去設定。
因為如果沒有強制變數直接申請在old gen,變數是先申請在eden gen的,然後經過gc之後,如果這個變數倖存下來,就進入survivor區,然後再經過幾次gc,變數就存在old gen區了。
事實上程式啟動穩定之後,可能大部分變數都已經到了old gen區去了,如果你的eden區記憶體配置過大,總量減去survivor減去eden之後剩下的old區就會不夠用了,這個時候虛擬機器幹什麼呢,虛擬機器會自動根據你設定的-Xms和-Xmx去擴容,於是你其實虛擬機器獲得的系統記憶體裡有很多是空閑記憶體,造成工作管理員裡看到的記憶體比你實際使用的大得多。
於是小猿第一步調好了啟動參數,記憶體果珍降了一些,但其實這是假象,虛擬機器裡實際佔用的記憶體還是沒有變。
小猿想知道更詳細的記憶體使用量,這就需要MemoryAnalyzer這個工具了,但是使用這個工具前,得產生程式的dump檔案,天哪我竟然百度出來的方法有利用增加啟動參數+HeapDumpOnOutOfMemoryError並且手動增加異常OutOfMemory來產生dump檔案。
好吧小猿異常出來了,機子down掉了,好心塞。
終於找到了好的方法,其實jdk內建的jconsole就可以產生的。
在MBean->com.sun.management->HotSpotDiagnostic->操作->dumpHeap,把p0的值改成你產生dump檔案的路徑,點dumpHeap就可以。不過dump檔案的尾碼可不是dump!!!是hprof!!!
產生之後,用MemoryAnalyzer開啟,leak Suspects之後,將會看到一個神奇的餅圖。裡面顯示的就是當前哪些instance佔用多少記憶體,有個details,點之。
話說打不開MemoryAnalyze的孩紙你們jdk安了麼,java環境變數都配好了麼。
看到詳細的記憶體佔用資訊,有多少個object等等。
小猿這下終於發現可以最佳化的地方了。
之前就對java的String心存怨念,這下怨念更深了,就是之前的那句,能用基本類型就用基本類型。
小猿發現,有100000個String的object,還有100000個char[]的object。
好吧,你用String存一個字串,其實是用他裡邊的char存字串,然後他自己還內建了各種親戚。
你存一個“0”,String給你的對象大小可是比1Byte大得多!
所以你可以調String的toCharArray()方法轉成char[]儲存。
這裡再八一八什麼時候用String,什麼時候用StringBuffer,什麼時候用char。
其實定義一個常用的常量字串用String那是極好的。
比如
String strTemp = “01”;
String strTemp2 = “01”;
這樣定義的話,strTemp和strTemp2實際上是共用了一個對象,只不過這個對象的引用是2!
所以這樣定義10000000000個也只是佔了一個String對象,這是String特有的常量池。
那為什麼還要用StringBuffer和StringBuild呢?
因為StringBuffer和StringBuild的append比String的+要好!
而且String本身設計的時候就是按常量去設計的,而StringBuffer和StringBuild才是真正可改變的字串。
但是如果程式要儲存大量的沒有規則的字串,這個時候就建議轉成char來存。
這隻是字串類型,其他的int等也是這樣的原則,盡量用基本變數儲存。
縱觀高大上的java,宣稱沒有記憶體泄露的java,如果我們使用不當,是會造成記憶體浪費的。
雖然退出程式的那一刻記憶體都會被正確的釋放掉,但是我們有時候更關心運行中的記憶體使用量情況。
只要一個變數的引用計數不為0,gc就無法回收他,也許你這個object暫時沒用了,卻把他加到一個到程式結束才能被釋放的arraylist裡去,那這塊記憶體在運行中就被浪費掉了。
java是不存在記憶體泄露,但是釋放的時機也很重要,一個對象對我們來說其實沒用了,卻被不小心把這個對象的鑰匙借給了一個生命週期比他長的對象,對我們來說就是記憶體泄露。
可以好好的看看MemoryAnalyze,分析下現在存在的對象,是不是真的都有用,如果有無用的,一定是被哪裡引用了。
小猿終於完成了第一篇java的隨筆。java速成一個月之後,下個月就要轉戰html5了,勿念…………………………
java記憶體最佳化牛刀小試