JVM記憶體溢位及合理配置

xusir發表於2015-01-04

Tomcat本身不能直接在計算機上執行,需要依賴於硬體基礎之上的作業系統和一個Java虛擬機器。Tomcat的記憶體溢位本質就是JVM記憶體溢位,所以在本文開始時,應該先對Java JVM有關記憶體方面的知識進行詳細介紹。

一、Java JVM記憶體介紹

JVM管理兩種型別的記憶體,堆和非堆。按照官方的說法:“Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。”“在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。簡單來說堆就是Java程式碼可及的記憶體,是留給開發人員使用的;非堆就是JVM留給自己用的,所以方法區、JVM內部處理或優化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法的程式碼都在非堆記憶體中,它和堆不同,執行期內GC不會釋放其空間。

(1). 堆記憶體分配 
JVM初始分配的記憶體由-Xms指定,預設是實體記憶體的1/64;JVM最大分配的記憶體由-Xmx指 定,預設是實體記憶體的1/4。預設空餘堆記憶體小於 40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆記憶體大於70%時,JVM會減少堆直到-Xms的最小限制。因此伺服器一般設定-Xms、 -Xmx相等以避免在每次GC 後調整堆的大小。可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行堆記憶體設定,一般的要將-Xms和-Xmx選項設定為相同,而-Xmn為1/4的-Xmx值,建議堆的最大值設定為可用記憶體的最大值的80%。

初始化堆的大小是JVM在啟動時向系統申請的記憶體的大小。一般而言,這個引數不重要。但是有的應用程式在大負載的情況下會急劇地佔用更多的記憶體,此時這個引數就是顯得非常重要,如果JVM啟動時設定使用的記憶體比較小而在這種情況下有許多物件進行初始化,JVM就必須重複地增加記憶體來滿足使用。由於這種原因,我們一般把-Xms和-Xmx設為一樣大,而堆的最大值受限於系統使用的實體記憶體。一般使用資料量較大的應用程式會使用持久物件,記憶體使用有可能迅速地增長。當應用程式需要的記憶體超出堆的最大值時JVM就會提示記憶體溢位,並且導致應用服務崩潰。所以,如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了實體記憶體或者作業系統的最大限制都會引起伺服器啟動不起來。

(2). 非堆記憶體分配 
也叫永久儲存的區域,用於存放Class和Meta資訊,Class在被Load的時候被放入該區域。它和存放類例項(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程式執行期對PermGen space進行清理。JVM使用-XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;由XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。 GC不會對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。

(3). JVM記憶體限制(最大值) 
首先JVM記憶體限制於實際的最大實體記憶體(廢話!,呵呵),假設實體記憶體無限大的話,JVM記憶體的最大值跟作業系統有很大的關係。簡單的說就32位處理器雖然可控記憶體空間有4GB,但是具體的作業系統會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統下為1.5G-2G,Linux系統 下為2G-3G),而64bit以上的處理器就不會有限制了。

二、三種記憶體溢位異常介紹

1. OutOfMemoryError: Java heap space  堆溢位

記憶體溢位主要存在問題就是出現在這個情況中。當在JVM中如果98%的時間是用於GC且可用的 Heap size 不足2%的時候將丟擲此異常資訊。

 2. OutOfMemoryError: PermGen space   非堆溢位(永久儲存區域溢位)

這種錯誤常見在web伺服器對JSP進行pre compile的時候。如果你的WEB APP下都用了大量的第三方jar, 其大小超過了jvm預設的大小(4M)那麼就會產生此錯誤資訊了。如果web app用了大量的第三方jar或者應用有太多的class檔案而恰好MaxPermSize設定較小,超出了也會導致這塊記憶體的佔用過多造成溢位,或者tomcat熱部署時侯不會清理前面載入的環境,只會將context更改為新部署的,非堆存的內容就會越來越多。

3. OutOfMemoryError: unable to create new native thread.   無法建立新的執行緒

這種現象比較少見,也比較奇怪,主要是和jvm與系統記憶體的比例有關。這種怪事是因為JVM已經被系統分配了大量的記憶體(比如1.5G),並且它至少要佔用可用記憶體的一半。

 

三、Java JVM記憶體配置

1. JVM記憶體分配設定的引數有四個

-Xmx    Java Heap最大值,預設值為實體記憶體的1/4;

-Xms    Java Heap初始值,Server端JVM最好將-Xms和-Xmx設為相同值,開發測試機JVM可以保留預設值;

-Xmn    Java Heap Young區大小,不熟悉最好保留預設值;

-Xss      每個執行緒的Stack大小,不熟悉最好保留預設值;

-XX:PermSize:設定記憶體的永久儲存區域; 

-XX:MaxPermSize:設定最大記憶體的永久儲存區域;

-XX:PermSize:設定記憶體的永久儲存區域;

-XX:NewSize:設定JVM堆的‘新生代’的預設大小;

-XX:MaxNewSize:設定JVM堆的‘新生代’的最大大小; 

2. 如何設定JVM的記憶體分配

(1)當在命令提示符下啟動並使用JVM時(只對當前執行的類Test生效):

java -Xmx128m -Xms64m -Xmn32m -Xss16m Test

(2)當在整合開發環境下(如eclipse)啟動並使用JVM時:

a. 在eclipse根目錄下開啟eclipse.ini,預設內容為(這裡設定的是執行當前開發工具的JVM記憶體分配):  -vmargs -Xms40m -Xmx256m -vmargs表示以下為虛擬機器設定引數,可修改其中的引數值,也可新增-Xmn,-Xss,另外,eclipse.ini內還可以設定非   堆記憶體,如:-XX:PermSize=56m,-XX:MaxPermSize=128m。

b. 開啟eclipse-視窗-首選項-Java-已安裝的JRE(對在當前開發環境中執行的java程式皆生效)  編輯當前使用的JRE,在預設VM引數中輸入:-Xmx128m -Xms64m -Xmn32m –Xss16m。

c. 開啟eclipse-執行-執行-Java應用程式(只對所設定的java類生效)  選定需設定記憶體分配的類-自變數,在VM自變數中輸入:-Xmx128m -Xms64m -Xmn32m -Xss16m  注:如果在同一開發環境中同時進行了b和c設定,則b設定生效,c設定無效,如:  開發環境的設定為:-Xmx256m,而類Test的設定為:-Xmx128m -Xms64m,則執行Test時生效的設定為:  -Xmx256m -Xms64m。

(3)當在伺服器環境下(如Tomcat)啟動並使用JVM時(對當前伺服器環境下所以Java程式生效):

a. 設定環境變數:  變數名:CATALINA_OPTS  變數值:-Xmx128m -Xms64m -Xmn32m -Xss16m。

b. 開啟Tomcat根目錄下的bin資料夾,編輯catalina.bat,將其中的%CATALINA_OPTS%(共有四處)替換為:-Xmx128m -Xms64m -Xmn32m -Xss16m。

c. 若沒有catalina.bat,只有tomcat.exe,tomcat6w.exe;則可以在啟動tomcat6w.exe 後 右鍵配置--Java--java option 下面輸入:

-Xmx256m –Xms64m

也可以找到登錄檔HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\TomcatService Manager\Tomcat6\Parameters\JavaOptions原值為 -Dcatalina.home="C:\ApacheGroup\Tomcat 6.0" -Djava.endorsed.dirs="C:\ApacheGroup\Tomcat 6.0\common\endorsed" -Xrs 加入  -Xms300m  -Xmx350m  (我的是加入-Xmx350m,tomcat才能啟動,加入-Xms300m  -Xmx350m反而tomcat都不能啟動)重起tomcat服務,設定生效。

3. 檢視JVM記憶體資訊

Runtime.getRuntime().maxMemory(); //最大可用記憶體,對應-Xmx 

Runtime.getRuntime().freeMemory(); //當前JVM空閒記憶體 

Runtime.getRuntime().totalMemory(); //當前JVM佔用的記憶體總數,其值相當於當前JVM已使用的記憶體及freeMemory()的總和 

關於maxMemory(),freeMemory()和totalMemory():maxMemory()為JVM的最大可用記憶體,可通過-Xmx設定,預設值為實體記憶體的1/4,設定不能高於計算機實體記憶體;  totalMemory()為當前JVM佔用的記憶體總數,其值相當於當前JVM已使用的記憶體及freeMemory()的總和,會隨著JVM使用記憶體的增加而增加;  freeMemory()為當前JVM空閒記憶體,因為JVM只有在需要記憶體時才佔用實體記憶體使用,所以freeMemory()的值一般情況下都很小,而JVM實際可用記憶體並不等於freeMemory(),而應該等於maxMemory()-totalMemory()+freeMemory()。

4. 例項,以下給出1G記憶體環境下java jvm 的引數設定參考

JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true "

大型的web工程,用tomcat預設分配的記憶體空間無法啟動,如果不是在myeclipse中啟動tomcat可以對tomcat這樣設定:

TOMCAT_HOME\bin\catalina.bat 中新增這樣一句話:

set JAVA_OPTS= -Xmx1024M -Xms512M -XX:MaxPermSize=256m

如果要在myeclipse中啟動,上述的修改就不起作用了,可如下設定:

Myeclipse->preferences->myeclipse->servers->tomcat->tomcat×.×->JDK皮膚中的

Optional Java VM arguments中新增:-Xmx1024M -Xms512M -XX:MaxPermSize=256m

對於單獨的.class,可以用下面的方法對Test執行時的jvm記憶體進行設定。 java -Xms64m -Xmx256m Test -Xms是設定記憶體初始化的大小 -Xmx是設定最大能夠使用記憶體的大小。

四、JVM記憶體配置與GC

需要考慮的是Java提供的垃圾回收機制。JVM的堆大小決定了JVM花費在收集垃圾上的時間和頻度。收集垃圾可以接受的速度與應用有關,應該通過分析實際的垃圾收集的時間和頻率來調整。如果堆的大小很大,那麼完全垃圾收集就會很慢,但是頻度會降低。如果你把堆的大小和記憶體的需要一致,完全收集就很快,但是會更加頻繁。調整堆大小的的目的是最小化垃圾收集的時間,以在特定的時間內最大化處理客戶的請求。在基準測試的時候,為保證最好的效能,要把堆的大小設大,保證垃圾收集不在整個基準測試的過程中出現。如果系統花費很多的時間收集垃圾,請減小堆大小。一次完全的垃圾收集應該不超過 3-5 秒。如果垃圾收整合為瓶頸,那麼需要指定堆的大小,檢查垃圾收集的詳細輸出,研究垃圾收集引數對效能的影響。一般說來,你應該使用實體記憶體的 80% 作為堆大小。當增加處理器時,記得增加記憶體,因為分配可以並行進行,而垃圾收集不是並行的。

Java Heap分為3個區:

1.Young 2.Old 3.Permanent。Young儲存剛例項化的物件。當該區被填滿時,GC會將物件移到Old區。Permanent區則負責儲存反射物件,本文不討論該區。

JVM有2個GC執行緒: 
第一個執行緒負責回收Heap的Young區; 
第二個執行緒在Heap不足時,遍歷Heap,將Young 區升級為Older區,Older區的大小等於-Xmx減去-Xmn,不能將-Xms的值設的過大,因為第二個執行緒被迫執行會降低JVM的效能。

為什麼一些程式頻繁發生GC?有如下原因: 
1. 程式內呼叫了System.gc()或Runtime.gc()。 
2. 一些中介軟體軟體呼叫自己的GC方法,此時需要設定引數禁止這些GC。 
3. Java的Heap太小,一般預設的Heap值都很小。 
4. 頻繁例項化物件,Release物件 此時儘量儲存並重用物件,例如使用StringBuffer()和String()。

如果你發現每次GC後,Heap的剩餘空間會是總空間的50%,這表示你的Heap處於健康狀態許多Server端的Java程式每次GC後最好能有65%的剩餘空間。

經驗之談: 
1.Server端JVM最好將-Xms和-Xmx設為相同值。為了優化GC,最好讓-Xmn值約等於-Xmx的1/3。 
2.一個GUI程式最好是每10到20秒間執行一次GC,每次在半秒之內完成。

注意: 
1.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。並且GC執行時,所有的使用者執行緒將暫停,也就是GC期間,Java應用程式不做任何工作。
2.Heap大小並不決定程式的記憶體使用量。程式的記憶體使用量要大於-Xmx定義的值,因為Java為其他任務分配記憶體,例如每個執行緒的Stack等。

相關文章