小猿做了兩年的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了,勿念…………………………