Tomcat本身不能直接在計(jì)算機(jī)上運(yùn)行,需要依賴于硬件基礎(chǔ)之上的操作系統(tǒng)和一個(gè)Java虛擬機(jī)。Tomcat的內(nèi)存溢出本質(zhì)就是JVM內(nèi)存溢出,所以在本文開始時(shí),應(yīng)該先對(duì)Java JVM有關(guān)內(nèi)存方面的知識(shí)進(jìn)行詳細(xì)介紹。 一、Java JVM內(nèi)存介紹 JVM管理兩種類型的內(nèi)存,堆和非堆。按照官方的說法:“Java 虛擬機(jī)具有一個(gè)堆,堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的內(nèi)存均從此處分配。堆是在 Java 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的。”“在JVM中堆之外的內(nèi)存稱為非堆內(nèi)存(Non-heap memory)”。簡(jiǎn)單來說堆就是Java代碼可及的內(nèi)存,是留給開發(fā)人員使用的;非堆就是JVM留給自己用的,所以方法區(qū)、JVM內(nèi)部處理或優(yōu)化所需的內(nèi)存(如JIT編譯后的代碼緩存)、每個(gè)類結(jié)構(gòu)(如運(yùn)行時(shí)常數(shù)池、字段和方法數(shù)據(jù))以及方法和構(gòu)造方法的代碼都在非堆內(nèi)存中,它和堆不同,運(yùn)行期內(nèi)GC不會(huì)釋放其空間。
(1). 堆內(nèi)存分配 初始化堆的大小是JVM在啟動(dòng)時(shí)向系統(tǒng)申請(qǐng)的內(nèi)存的大小。一般而言,這個(gè)參數(shù)不重要。但是有的應(yīng)用程序在大負(fù)載的情況下會(huì)急劇地占用更多的內(nèi)存,此時(shí)這個(gè)參數(shù)就是顯得非常重要,如果JVM啟動(dòng)時(shí)設(shè)置使用的內(nèi)存比較小而在這種情況下有許多對(duì)象進(jìn)行初始化,JVM就必須重復(fù)地增加內(nèi)存來滿足使用。由于這種原因,我們一般把-Xms和-Xmx設(shè)為一樣大,而堆的最大值受限于系統(tǒng)使用的物理內(nèi)存。一般使用數(shù)據(jù)量較大的應(yīng)用程序會(huì)使用持久對(duì)象,內(nèi)存使用有可能迅速地增長(zhǎng)。當(dāng)應(yīng)用程序需要的內(nèi)存超出堆的最大值時(shí)JVM就會(huì)提示內(nèi)存溢出,并且導(dǎo)致應(yīng)用服務(wù)崩潰。所以,如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了物理內(nèi)存或者操作系統(tǒng)的最大限制都會(huì)引起服務(wù)器啟動(dòng)不起來。
(2). 非堆內(nèi)存分配
(3). JVM內(nèi)存限制(最大值) 二、三種內(nèi)存溢出異常介紹 1. OutOfMemoryError: Java heap space 堆溢出 內(nèi)存溢出主要存在問題就是出現(xiàn)在這個(gè)情況中。當(dāng)在JVM中如果98%的時(shí)間是用于GC且可用的 Heap size 不足2%的時(shí)候?qū)伋龃水惓P畔ⅰ? 2. OutOfMemoryError: PermGen space 非堆溢出(永久保存區(qū)域溢出) 這種錯(cuò)誤常見在web服務(wù)器對(duì)JSP進(jìn)行pre compile的時(shí)候。如果你的WEB APP下都用了大量的第三方j(luò)ar, 其大小超過了jvm默認(rèn)的大小(4M)那么就會(huì)產(chǎn)生此錯(cuò)誤信息了。如果web app用了大量的第三方j(luò)ar或者應(yīng)用有太多的class文件而恰好MaxPermSize設(shè)置較小,超出了也會(huì)導(dǎo)致這塊內(nèi)存的占用過多造成溢出,或者tomcat熱部署時(shí)侯不會(huì)清理前面加載的環(huán)境,只會(huì)將context更改為新部署的,非堆存的內(nèi)容就會(huì)越來越多。 3. OutOfMemoryError: unable to create new native thread. 無法創(chuàng)建新的線程 這種現(xiàn)象比較少見,也比較奇怪,主要是和jvm與系統(tǒng)內(nèi)存的比例有關(guān)。這種怪事是因?yàn)镴VM已經(jīng)被系統(tǒng)分配了大量的內(nèi)存(比如1.5G),并且它至少要占用可用內(nèi)存的一半。
三、Java JVM內(nèi)存配置 1. JVM內(nèi)存分配設(shè)置的參數(shù)有四個(gè) -Xmx Java Heap最大值,默認(rèn)值為物理內(nèi)存的1/4; -Xms Java Heap初始值,Server端JVM最好將-Xms和-Xmx設(shè)為相同值,開發(fā)測(cè)試機(jī)JVM可以保留默認(rèn)值; -Xmn Java Heap Young區(qū)大小,不熟悉最好保留默認(rèn)值; -Xss 每個(gè)線程的Stack大小,不熟悉最好保留默認(rèn)值; -XX:PermSize:設(shè)定內(nèi)存的永久保存區(qū)域;-XX:MaxPermSize:設(shè)定最大內(nèi)存的永久保存區(qū)域; -XX:PermSize:設(shè)定內(nèi)存的永久保存區(qū)域; -XX:NewSize:設(shè)置JVM堆的‘新生代’的默認(rèn)大?。?
-XX:MaxNewSize:設(shè)置JVM堆的‘新生代’的最大大小; 2. 如何設(shè)置JVM的內(nèi)存分配 (1)當(dāng)在命令提示符下啟動(dòng)并使用JVM時(shí)(只對(duì)當(dāng)前運(yùn)行的類Test生效):
(2)當(dāng)在集成開發(fā)環(huán)境下(如eclipse)啟動(dòng)并使用JVM時(shí):
(3)當(dāng)在服務(wù)器環(huán)境下(如Tomcat)啟動(dòng)并使用JVM時(shí)(對(duì)當(dāng)前服務(wù)器環(huán)境下所以Java程序生效):
3. 查看JVM內(nèi)存信息
關(guān)于maxMemory(),freeMemory()和totalMemory():maxMemory()為JVM的最大可用內(nèi)存,可通過-Xmx設(shè)置,默認(rèn)值為物理內(nèi)存的1/4,設(shè)置不能高于計(jì)算機(jī)物理內(nèi)存; totalMemory()為當(dāng)前JVM占用的內(nèi)存總數(shù),其值相當(dāng)于當(dāng)前JVM已使用的內(nèi)存及freeMemory()的總和,會(huì)隨著JVM使用內(nèi)存的增加而增加; freeMemory()為當(dāng)前JVM空閑內(nèi)存,因?yàn)镴VM只有在需要內(nèi)存時(shí)才占用物理內(nèi)存使用,所以freeMemory()的值一般情況下都很小,而JVM實(shí)際可用內(nèi)存并不等于freeMemory(),而應(yīng)該等于maxMemory()-totalMemory()+freeMemory()。 4. 實(shí)例,以下給出1G內(nèi)存環(huán)境下java jvm 的參數(shù)設(shè)置參考
大型的web工程,用tomcat默認(rèn)分配的內(nèi)存空間無法啟動(dòng),如果不是在myeclipse中啟動(dòng)tomcat可以對(duì)tomcat這樣設(shè)置:
如果要在myeclipse中啟動(dòng),上述的修改就不起作用了,可如下設(shè)置:
對(duì)于單獨(dú)的.class,可以用下面的方法對(duì)Test運(yùn)行時(shí)的jvm內(nèi)存進(jìn)行設(shè)置。 java -Xms64m -Xmx256m Test -Xms是設(shè)置內(nèi)存初始化的大小 -Xmx是設(shè)置最大能夠使用內(nèi)存的大小。 四、JVM內(nèi)存配置與GC 需要考慮的是Java提供的垃圾回收機(jī)制。JVM的堆大小決定了JVM花費(fèi)在收集垃圾上的時(shí)間和頻度。收集垃圾可以接受的速度與應(yīng)用有關(guān),應(yīng)該通過分析實(shí)際的垃圾收集的時(shí)間和頻率來調(diào)整。如果堆的大小很大,那么完全垃圾收集就會(huì)很慢,但是頻度會(huì)降低。如果你把堆的大小和內(nèi)存的需要一致,完全收集就很快,但是會(huì)更加頻繁。調(diào)整堆大小的的目的是最小化垃圾收集的時(shí)間,以在特定的時(shí)間內(nèi)最大化處理客戶的請(qǐng)求。在基準(zhǔn)測(cè)試的時(shí)候,為保證最好的性能,要把堆的大小設(shè)大,保證垃圾收集不在整個(gè)基準(zhǔn)測(cè)試的過程中出現(xiàn)。如果系統(tǒng)花費(fèi)很多的時(shí)間收集垃圾,請(qǐng)減小堆大小。一次完全的垃圾收集應(yīng)該不超過 3-5 秒。如果垃圾收集成為瓶頸,那么需要指定堆的大小,檢查垃圾收集的詳細(xì)輸出,研究垃圾收集參數(shù)對(duì)性能的影響。一般說來,你應(yīng)該使用物理內(nèi)存的 80% 作為堆大小。當(dāng)增加處理器時(shí),記得增加內(nèi)存,因?yàn)榉峙淇梢圆⑿羞M(jìn)行,而垃圾收集不是并行的。 Java Heap分為3個(gè)區(qū): 1.Young 2.Old 3.Permanent。Young保存剛實(shí)例化的對(duì)象。當(dāng)該區(qū)被填滿時(shí),GC會(huì)將對(duì)象移到Old區(qū)。Permanent區(qū)則負(fù)責(zé)保存反射對(duì)象,本文不討論該區(qū)。
JVM有2個(gè)GC線程:
為什么一些程序頻繁發(fā)生GC?有如下原因: 如果你發(fā)現(xiàn)每次GC后,Heap的剩余空間會(huì)是總空間的50%,這表示你的Heap處于健康狀態(tài)許多Server端的Java程序每次GC后最好能有65%的剩余空間。
經(jīng)驗(yàn)之談:
注意:
tomcat 的Connector配置如下 <</span>Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="800" acceptCount="1000"/>
其中最后兩個(gè)參數(shù)意義如下:
maxThreads:tomcat起動(dòng)的最大線程數(shù),即同時(shí)處理的任務(wù)個(gè)數(shù),默認(rèn)值為200 acceptCount:當(dāng)tomcat起動(dòng)的線程數(shù)達(dá)到最大時(shí),接受排隊(duì)的請(qǐng)求個(gè)數(shù),默認(rèn)值為100
這兩個(gè)值如何起作用,請(qǐng)看下面三種情況 情況1:接受一個(gè)請(qǐng)求,此時(shí)tomcat起動(dòng)的線程數(shù)沒有到達(dá)maxThreads,tomcat會(huì)起動(dòng)一個(gè)線程來處理此請(qǐng)求。 情況2:接受一個(gè)請(qǐng)求,此時(shí)tomcat起動(dòng)的線程數(shù)已經(jīng)到達(dá)maxThreads,tomcat會(huì)把此請(qǐng)求放入等待隊(duì)列,等待空閑線程。 情況3:接受一個(gè)請(qǐng)求,此時(shí)tomcat起動(dòng)的線程數(shù)已經(jīng)到達(dá)maxThreads,等待隊(duì)列中的請(qǐng)求個(gè)數(shù)也達(dá)到了acceptCount,此時(shí)tomcat會(huì)直接拒絕此次請(qǐng)求,返回connection refused maxThreads如何配置 一般的服務(wù)器操作都包括量方面:1計(jì)算(主要消耗cpu),2等待(io、數(shù)據(jù)庫(kù)等) 第一種極端情況,如果我們的操作是純粹的計(jì)算,那么系統(tǒng)響應(yīng)時(shí)間的主要限制就是cpu的運(yùn)算能力,此時(shí)maxThreads應(yīng)該盡量設(shè)的小,降低同一時(shí)間內(nèi)爭(zhēng)搶cpu的線程個(gè)數(shù),可以提高計(jì)算效率,提高系統(tǒng)的整體處理能力。 第二種極端情況,如果我們的操作純粹是IO或者數(shù)據(jù)庫(kù),那么響應(yīng)時(shí)間的主要限制就變?yōu)榈却獠抠Y源,此時(shí)maxThreads應(yīng)該盡量設(shè)的大,這樣 才能提高同時(shí)處理請(qǐng)求的個(gè)數(shù),從而提高系統(tǒng)整體的處理能力。此情況下因?yàn)閠omcat同時(shí)處理的請(qǐng)求量會(huì)比較大,所以需要關(guān)注一下tomcat的虛擬機(jī)內(nèi) 存設(shè)置和linux的open file限制。 我在測(cè)試時(shí)遇到一個(gè)問題,maxThreads我設(shè)置的比較大比如3000,當(dāng)服務(wù)的線程數(shù)大到一定程度時(shí),一般是2000出頭,單次請(qǐng)求的響應(yīng)時(shí)間就會(huì)急劇的增加, 百思不得其解這是為什么,四處尋求答案無果,最后我總結(jié)的原因可能是cpu在線程切換時(shí)消耗的時(shí)間隨著線程數(shù)量的增加越來越大, cpu把大多數(shù)時(shí)間都用來在這2000多個(gè)線程直接切換上了,當(dāng)然cpu就沒有時(shí)間來處理我們的程序了。 以前一直簡(jiǎn)單的認(rèn)為多線程=高效率。。其實(shí)多線程本身并不能提高cpu效率,線程過多反而會(huì)降低cpu效率。 當(dāng)cpu核心數(shù)<線程數(shù)時(shí),cpu就需要在多個(gè)線程直接來回切換,以保證每個(gè)線程都會(huì)獲得cpu時(shí)間,即通常我們說的并發(fā)執(zhí)行。 所以maxThreads的配置絕對(duì)不是越大越好。 現(xiàn)實(shí)應(yīng)用中,我們的操作都會(huì)包含以上兩種類型(計(jì)算、等待),所以maxThreads的配置并沒有一個(gè)最優(yōu)值,一定要根據(jù)具體情況來配置。 最好的做法是:在不斷測(cè)試的基礎(chǔ)上,不斷調(diào)整、優(yōu)化,才能得到最合理的配置。 acceptCount的配置,我一般是設(shè)置的跟maxThreads一樣大,這個(gè)值應(yīng)該是主要根據(jù)應(yīng)用的訪問峰值與平均值來權(quán)衡配置的。 如果設(shè)的較小,可以保證接受的請(qǐng)求較快相應(yīng),但是超出的請(qǐng)求可能就直接被拒絕 如果設(shè)的較大,可能就會(huì)出現(xiàn)大量的請(qǐng)求超時(shí)的情況,因?yàn)槲覀兿到y(tǒng)的處理能力是一定的。
|
|