JAVA性能優化,讓程序更快更穩定

本文主要通過分析雲系統遭遇的java進程內存溢出的問題,介紹java進程佔用系統內存高時的排查方案及建議的解決方案,同時延伸拓展java進程佔用系統內存高的原因以及對於linux操作系統的內存管理和優化方案。

【雲環境系統java進程內存佔用高】

系統的運維值班人員發現該系統的uc註冊頁面訪問超時,根據監控反饋的報錯地址,登錄相應的UC註冊網頁時,發現提示如下:網站維護中。

檢查該uc註冊業務對應的進程後,發現進程運行正常,檢查服務的日誌情況,然而並發現沒有異常。

最後懷疑可能是該服務器的內存溢出,可能是產品對連接到服務端的線程沒做控制,導致已結束、假死或超時的線程未釋放內存,或者上述線程釋放內存之後,沒有進行內存整理,產生碎片,然後越積越大,然後就爆炸了。

但是查看服務器的內存情況如下:

通過以上查詢發現,服務器內存總共4G,可用的還有300M左右,筆者查詢相關資料後使用jmap -dump:format=b,file=ucweb.bin ${pid}的方法dump相應Java進程運行過程中佔用的內存情況,然後導出爲二進制文件,需要注意的是,這個對性能有影響,其中format=b是通過二進制的意思,通過這個命令把內存結構dump到二進制文件中,然後結合分析軟件eclipse來獲取具體情況。

也可以通過jmap –heap ${pid}命令來查看該java進程運行時所打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況。

具體操作方法:

1、使用如下命令查看該業務進程的pid

2、使用jmap –heap 4665來查看該進程的具體使用情況如下:

其中詳細描述了該進程的內存佔用和配置情況:Heap Configuration是#堆配置情況、Heap Usage: ##堆使用情況、Eden Space: ##伊甸區、From Space: ##survior1區、To Space: ##survior2區、concurrent mark-sweep generation: ##老生代使用情況和Perm Generation: ##perm區使用情況。

▲使用ps命令也可以查看該進程的部分資源信息:

ps –p 4655 –o vsz,rss

從上面ps輸出的VSZ和RSS分別是3.4G和0.67G,具體分析如下:VSZ是指已分配的線性空間大小,這個大小通常並不等於程序實際用到的內存大小,產生這個空間的可能性很多,比如內存映射,共享的動態庫,或者向系統申請了更多的堆,都會擴展線性空間大小;RSZ是Resident Set Size,常駐內存大小,即進程實際佔用的物理內存大小。

對於JVM的幾個GC堆和GC也可以使用Jstat這個命令查看:

jstat -gcutil 4655 100 5

其中字段如下:

S0: 年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比。

S1: 年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比。

E: 年輕代中Eden(伊甸園)已使用的佔當前容量百分比。

O: old代已使用的佔當前容量百分比。

P: perm代已使用的佔當前容量百分比。

YGC: 從應用程序啓動到採樣時年輕代中gc次數。

YGCT: 從應用程序啓動到採樣時年輕代中gc所用時間(s)。

FGC: 從應用程序啓動到採樣時old代(全gc)gc次數。

FGCT: 從應用程序啓動到採樣時old代(全gc)gc所用時間(s)。

GCT: 從應用程序啓動到採樣時gc用的總時間(s)。

通過以上顯示結果得出此應用程序從啓動到採樣時具體的參數,如Perm space當前容量的大小和Perm space已經使用的大小,從而具體來定位是程序在進程運行時佔用的內存大小,同時根據這個對該程序在啓動的腳本catalina.sh中對的分配空間進行合理的配置:

JAVA_OPTS="-Xms1024m -Xmx2024m -XX:PermSize=128m -XX:MaxPermSize=256m"

最後通過進程的內存佔用資源分析後:是由於永久代內存設置沒生效,還是使用默認值,導致內存滿了,然後觸發fully gc。

【Java類程序的進程優化及處理方案】

結合以上的案例和分析,大致瞭解java進程異常時如何進行分析佔用的內存和性能資源情況,這部分從java啓動時的資源分配到運行時的詳細性能跟蹤及根據相應機器的配置合理分配對應的內存空間。

一.java進程在啓動前的資源分配

只要接觸過IT行業的人都知道“java進程運行時佔用的內存很大”,很多程序員都有類似的遭遇,而且會認爲運行Java程序時使用-Xmx和-Xms參數指定的就是程序將會佔用的內存,但實際上只是Java堆對象佔用的內存,堆只是影響Java程序佔用內存數量的一個因素。要想了解java進程佔用內存大的原因,可以先了解是哪些因素會影響到內存的佔用。

大致來說,JAVA進程包含如下因素:對象(Objects)、類(Classes)、線程(Threads)、本地數據結構(Native data structures)、本地代碼(Native code)。而且隨着程序啓動和運行後,上面的這些因素對內存佔用的影響又實時根據應用程序、運行業務系統環境和不同的操作系統類型(windows、linux或mac)的不同而發生變化,因此該如何計算總的內存佔用量?

想通過各種計算方式使用一個準確的數字是很困難的,因爲程序員很難控制自己服務器上的特定參數,但是對照上面的影響因素能控制以下部分:

堆大小:-Xmx

類佔用的內存:-XX:MaxPermSize

線程棧:-Xss控制每個線程佔用的內存

Xmx是java進程啓動的一個配置項,主要用於設置相應業務應用程序(不是整個jvm)運行時能夠使用的最大內存數。如果該程序運行的服務器配置不高,但程序運行會佔用很大內存時,那就需要修改缺省的設置;特別是配置tomcat的程序時,如果流量、程序都不低的話就需要修改這個缺省值。當然不要大得超過服務器的內存,否則就會出現程序一運行,服務器就掛掉的問題(內存爆掉);反之,如果這個值設置的比較低,每次運行的時候,就會出現內存不夠的情況。

Xms是設置內存的另一個重要參數,用它來設置該程序進行初始化時所需要的內存棧的大小。增加這個值的話,程序的啓動性能會得到提高。不過同樣有前面的限制,並受到Xmx的限制。

XX:MaxPermSize設置過小會導致java.lang.OutOfMemoryError: PermGen space 會導致內存溢出。

▲內存溢出

1.這一部分內存用於存放Class和Meta的信息,Class在被Load時會放入PermGen space區域,它和存放Instance的Heap區域不同。

2.GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS 的話,就很可能出現PermGen space錯誤。

結合以上發現:一般情況下當棧設置的太小時會導致StackOverflow異常,程序出錯。

計算內存佔用情況的公式如下:

(-Xmx) + (-XX:MaxPermSize) + 線程數 * (-Xss) + 其它內存

其它內存取決於本地代碼佔用的內存,如NIO、socket緩衝區、JNI等。它一般大約是jvm內存的5%左右。

所以假設我們有下面的JVM參數和100個線程:

-Xmx1024m -XX:MaxPermSize=256m -Xss512k

因此在java程序啓動的過程中,可以在啓動腳本catalina.sh中定義如上參數的大小:

JAVA_OPTS="-Xms1024m -Xmx2024m -XX:PermSize=128m -XX:MaxPermSize=256m"

二.如何在JAVA運行過程中查看實時性能

結合以上部分對異常進程的分析和配置,可以瞭解到最常見的java進程性能資源的分析命令和具體的查看方式,下面大致介紹2種:Jmap和Jsata。

Jmap是一個java進程內存最常見的一個命令,它可以輸出所有內存中對象的性能信息,特別是可以將VM 中的heap以二進制形式輸出成文本file.bin。打印出指定Java進程(使用pid來區分)內存中的所有‘對象’的情況(如:產生那些對象,及其數量),然後使用分析軟件eclipse或者其他的分析工具來分析輸出的文本file.bin。

同樣Jamp指令有不同的參數和具體的使用方式,這裏只是簡單說明一下。

例如該java進程的pid信息如下:

jmap –finalizerinfo 4655 -finalizerinfo 打印正等候回收的對象信息 。

jmap –heap 4655 -heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況。

jmap –histo 4655 -histo[:live] 打印每個class的實例數目,內存佔用,類全名信息。VM的內部類名字開頭會加上前綴”*”. 如果live子參數加上後,只統計活的對象數量。

jmap -permstat 4655 -permstat 打印classload和jvm heap永久代的信息,包含每個classloader的名字、活潑性、地址、父classloader和加載的class數量。另外,內部String的數量和佔用內存數也會打印出來。

Jstat(Java Virtual Machine statistics monitoring tool)是JDK本身自帶的一個輕量級小工具,目錄一般在Java的bin下,主要利用JVM內建的指令對Java應用程序的資源和性能進行實時的命令行監控,也包括對Heap size和垃圾回收狀況的監控。

Jstat的功能強大之處體現於衆多的可選項,從而可以詳細查看堆內各個部分的使用量以及加載類的數量,可以加上具體進程的id來查看相應的具體資源情況,這裏也是簡單說明一下。

jstat –class 4665 顯示加載class的數量,及所佔空間等信息。

jstat -compiler 4665 顯示VM實時編譯的數量等信息。

jstat -gc 4665 顯示gc的信息,查看gc的次數和時間。

jstat -gccapacity 4655 顯示VM內存中三代(young,old,perm)對象的使用和佔用大小。

jstat -gcutil 4655 統計gc信息。

jstat -gcnew 4655 顯示年輕代對象的信息。

jstat –gcold 4655 顯示old代對象的信息。

jstat -gcoldcapacity 4655 顯示old代對象的信息及其佔用量。

jstat –gcpermcapacity 4655 顯示perm對象的信息及其佔用量。

jstat -printcompilation 4655 顯示當前VM執行的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章