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提供的-Xms -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;
JVM使用 -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: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 重起tomcat服務,設置生效。
3. 查看JVM內存信息
Runtime.getRuntime().maxMemory(); // 最大可用內存,對應-Xmx 默認值為物理內存的1/4,設置不能高于計算機物理內存
Runtime.getRuntime().freeMemory(); // 當前JVM空閑內存,因為JVM只有在需要內存時才占用物理內存使用,所以freeMemory()的值一般情況下都很小
//而JVM實際可用內存并不等于freeMemory(),而應該等于maxMemory()-totalMemory()+freeMemory()。
Runtime.getRuntime().totalMemory(); // 當前JVM占用的內存總數,其值相當于當前JVM已使用的內存及freeMemory()的總和,會隨著JVM使用內存的增加而增加
四、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等。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
