我們都知道JVM內(nèi)存由幾個(gè)部分組成: Java棧、程序計(jì)數(shù)器(ProgramCounter)寄存器、本地方法棧、堆、方法區(qū)、運(yùn)行常量池。 JVM垃圾回收僅僅針對(duì)公共內(nèi)存區(qū)域即:堆和方法區(qū)進(jìn)行。 本文主要討論兩點(diǎn),一是垃圾回收策略,二是調(diào)優(yōu)的方法。 一、垃圾回收機(jī)制1.1 分代管理將堆和方法區(qū)按照對(duì)象出現(xiàn)的不同時(shí)間進(jìn)行分代: u 堆中會(huì)頻繁地創(chuàng)建對(duì)象,基于一種分代的思想,按照對(duì)象存活時(shí)間將堆劃分為新生代和舊生代兩部分,我們不能一次垃圾回收新生代存活的對(duì)象就放入舊生代,而是要經(jīng)過幾次GC后還存活的對(duì)象,我們才放入舊生代,所以我們又把新生代再次劃分為Eden區(qū)和兩個(gè)Survivor區(qū),讓對(duì)象創(chuàng)建在Eden區(qū),然后在兩個(gè)Survivor之間反復(fù)復(fù)制,最后仍然存活的對(duì)象才復(fù)制到舊生代中。 u 方法區(qū)存放的是常量、加載的字節(jié)碼文件信息等,信息相對(duì)穩(wěn)定。因?yàn)椴粫?huì)頻繁地創(chuàng)建對(duì)象,所以不需要分代,直接GC即可。 由此我們JVM垃圾回收要掃描的范圍是: 注:圖片來自網(wǎng)絡(luò) 新生代: 1.所有新對(duì)象創(chuàng)建發(fā)生在Eden區(qū),Eden區(qū)滿后觸發(fā)新生代上的minor GC,將Eden區(qū)和非空閑Survivor區(qū)存活對(duì)象復(fù)制到另一個(gè)空閑的Survivor區(qū)中。 2.永遠(yuǎn)保證一個(gè)Survivor是空的,新生代minor GC就是在兩個(gè)Survivor區(qū)之間相互復(fù)制存活對(duì)象,直到Survivor區(qū)滿為止。 舊生代: 1.Eden區(qū)滿后觸發(fā)minor GC將存活對(duì)象復(fù)制到Survivor區(qū),Survivor區(qū)滿后觸發(fā)minor GC將存活對(duì)象復(fù)制到舊生代。 2.經(jīng)過新生代的兩個(gè)Survivor之間多次復(fù)制,仍然存活下來的對(duì)象就是年齡相對(duì)比較老的,就可以放入到舊生代了,隨著時(shí)間推移,如果舊生代也滿了,將觸發(fā)Full GC,針對(duì)整個(gè)堆(有新生代、舊生代和持久代)進(jìn)行垃圾回收。 持久代: 持久代如果滿,將觸發(fā)Full GC 1.2 垃圾回收要執(zhí)行g(shù)c關(guān)鍵在于兩點(diǎn),一是檢測(cè)出垃圾對(duì)象,二是釋放垃圾對(duì)象所占用的空間。 1.2.1 檢測(cè)垃圾對(duì)象檢測(cè)出垃圾對(duì)象一般有兩種算法: 1、 引用計(jì)數(shù)法 2、 可達(dá)性分析 引用計(jì)數(shù)法因?yàn)闊o法檢測(cè)對(duì)象之間相互循環(huán)引用的問題,基本沒有被采用?,F(xiàn)在主流的語(yǔ)言的垃圾收集中檢測(cè)垃圾對(duì)象主要還是“可達(dá)性分析”方法,下面也主要介紹JVM可達(dá)性分析方法檢測(cè)垃圾對(duì)象。 “可達(dá)性分析”算法描述? 通過一系列的名為“GC Root”的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Root沒有任何引用鏈相連時(shí),則該對(duì)象不可達(dá),該對(duì)象是不可使用的,垃圾收集器將回收其所占的內(nèi)存。所以JVM判斷對(duì)象需要存活的原則是:能夠被一個(gè)根對(duì)象到達(dá)的對(duì)象。 什么是能夠到達(dá)呢? 就是對(duì)象A中引用了對(duì)象B,那么就稱A到B可達(dá)。 GCRoot對(duì)象集合? a.Java虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對(duì)象。 b.方法區(qū)中的類靜態(tài)屬性引用的對(duì)象。 c.方法區(qū)中的常量引用的對(duì)象。 d.本地方法棧中JNI本地方法的引用對(duì)象。 1.2.2 釋放空間1、垃圾回收算法前面已經(jīng)介紹了如何檢測(cè)出垃圾對(duì)象,在檢測(cè)出垃圾對(duì)象之后,需要按照特定的垃圾回收算法進(jìn)行內(nèi)存回收,常見的垃圾回收算法有: u 復(fù)制(Copying) u 標(biāo)記-清除(Mark-Sweep) u 標(biāo)記-整理(Mark-Compact) u 分代(Generational Collection),借助前面三種算法實(shí)現(xiàn) 這里就不一一詳述,感興趣可以自行百度。 2、垃圾收集器實(shí)現(xiàn)上面算法都是理論性的東西,Java虛擬機(jī)規(guī)范沒有規(guī)定垃圾收集器具體如何實(shí)現(xiàn),因此不同廠商、不同版本虛擬機(jī)提供的垃圾收集器可能有所差異。下面列舉HotSpot(Sun JDK和Open JDK自帶)虛擬機(jī)提供的六種垃圾收集器實(shí)現(xiàn):
并行(Parallel):多條垃圾收集線程并行工作,而用戶線程仍處于等待狀態(tài)。 并發(fā)(Concurrent):垃圾收集線程與用戶線程一段時(shí)間內(nèi)同時(shí)工作(不是并行,而是交替執(zhí)行)。 總結(jié): 1、兩個(gè)串行收集器、三個(gè)并行收集器、一個(gè)并發(fā)收集器。 2、ParNew收集器是Serial的多線程版本。 3、Serial Old收集器是Serial收集器的舊生代版本。 4、Parallel Scavenge收集器以吞吐量為目標(biāo),適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。 5、Parallel Old收集器是Parallel Scavenge的舊生代版本。 6、Parallel Scavenge收集器和Parallel Old收集器是名副其實(shí)的“吞吐量?jī)?yōu)先”組合。 7、除CMS外,其他收集器工作時(shí)都需要暫停其他所有線程,CMS是第一款真正意義上的并發(fā)(Concurrent)收集器,第一次實(shí)現(xiàn)了讓垃圾收 集器線程與 用戶線程同時(shí)工作,是一款以最短停頓時(shí)間為目標(biāo)的收集器,適合交互性較多的場(chǎng)景,這也是與Parallel Scavenge/Parallel Old吞吐量?jī)?yōu)先組合的區(qū)別。 8、新生代因?yàn)榛厥樟粝碌膶?duì)象少,所以采用標(biāo)記-復(fù)制法。 9、舊生代因?yàn)榛厥樟粝碌膶?duì)象多,所以采用標(biāo)記-清除/標(biāo)記-整理算法。 3、選擇所需垃圾收集器虛擬機(jī)提供了參數(shù),以便用戶根據(jù)自己的需求設(shè)置所需的垃圾收集器:
二、性能調(diào)優(yōu)2.1 性能調(diào)優(yōu)的目的減少minor gc的頻率、以及full gc的次數(shù)。 2.2 性能調(diào)優(yōu)的手段1.使用JDK提供的內(nèi)存查看工具,如JConsole和Java VisualVM 2.控制堆內(nèi)存各個(gè)部分所占的比例 3.采用合適的垃圾收集器 手段1:內(nèi)存查看工具和GC日志分析n -verbose.gc:顯示GC的操作內(nèi)容。打開它,可以顯示最忙和最空閑收集行為發(fā)生的時(shí)間、收集前后的內(nèi)存大小、收集需要的時(shí)間等。 n -xx:+printGCdetails:詳細(xì)了解GC中的變化。 n -XX:+PrintGCTimeStamps:了解這些垃圾收集發(fā)生的時(shí)間,自JVM啟動(dòng)以后以秒計(jì)量。 n -xx:+PrintHeapAtGC:了解堆的更詳細(xì)的信息。 手段2:針對(duì)新生代和舊生代的比例如果新生代太小,會(huì)導(dǎo)致頻繁GC,而且大對(duì)象對(duì)直接進(jìn)入舊生代引發(fā)full gc 如果新生代太大,會(huì)誘發(fā)舊生代full gc,而且新生代的gc耗時(shí)會(huì)延長(zhǎng) 建議新生代占整個(gè)堆1/3合適,相關(guān)JVM參數(shù)如下: n -Xms:初始堆大小 n -Xmx:最大堆大小 n - Xmn:新生代大小 n -XX:PermSize=n:持久代最大值 n -XX:MaxPermSize=n:持久代最大值 n -XX:NewRatio=n:設(shè)置新生代和舊生代的比值。如:為3,表示新生代與舊生代比值為1:3,新生代占整個(gè)新生代舊生代和的1/4。 手段3:針對(duì)Eden和Survivor的比例如果Eden太小,會(huì)導(dǎo)致頻繁GC。 如果Eden太大,會(huì)導(dǎo)致大對(duì)象直接進(jìn)入舊生代,降低對(duì)象在新生代存活時(shí)間。 n -XX:SurvivorRatio=n:新生代中Eden區(qū)與兩個(gè)Survivor區(qū)的比值。注意Survivor區(qū)有兩個(gè)。如:3,表示Eden:Survivor=3:2,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/5 n -XX:PretenureSizeThreshold:直接進(jìn)入舊生代中的對(duì)象大小,設(shè)置此值后,大于這個(gè)參數(shù)的對(duì)象將直接在舊生代中進(jìn)行內(nèi)存分配。 n -XX:MaxTenuringThreshold:對(duì)象轉(zhuǎn)移到舊生代中的年齡,每個(gè)對(duì)象經(jīng)歷過一次新生代GC(Minor GC)后,年齡就加1,到超過設(shè)置的值后,對(duì)象轉(zhuǎn)移到舊生代。 手段4:采用正確的垃圾收集器通過JVM參數(shù)設(shè)置所使用的垃圾收集器參考前面的介紹,這里關(guān)注其他一些設(shè)置。 并行收集器設(shè)置 n -XX:ParallelGCThreads=n:設(shè)置并行收集器收集時(shí)并行收集線程數(shù)。 n -XX:MaxGCPauseMillis=n:設(shè)置并行收集最大暫停時(shí)間,僅對(duì)ParallelScavenge生效。 n -XX:GCTimeRatio=n:設(shè)置垃圾回收時(shí)間占程序運(yùn)行時(shí)間的百分比,僅對(duì)Parallel Scavenge生效。 并發(fā)收集器設(shè)置 n -XX:CMSInitiatingOccupancyFraction:默認(rèn)設(shè)置下,CMS收集器在舊生代使用了68%的空間后就會(huì)被激活。此參數(shù)就是 設(shè)置舊生代空間被使用多少后觸發(fā)垃圾收集。注意要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要,就會(huì)出現(xiàn)concurrent mode failure,這時(shí)候就會(huì)啟用Serial Old收集器作為備用進(jìn)行舊生代的垃圾收集。 n -XX:+UseCMSCompactAtFullCollection:空間碎片過多是標(biāo)記-清除算法的弊端,此參數(shù)設(shè)置在FULL GC后再進(jìn)行一個(gè)碎片整理過程 n -XX:CMSFullGCsBeforeCompaction:設(shè)置在若干次垃圾收集之后再啟動(dòng)一次內(nèi)存碎片整理 三、實(shí)際項(xiàng)目配置
1、修改 tomcat\bin\Catalina.bat 文件
在166行左右 在 %DEBUG_OPTS% 后面添加-Xms256m -Xmx512m linux環(huán)境下: 打開在Tomcat的安裝目錄的bin文件的catalina.sh文件,進(jìn)入編輯狀態(tài).
保存后,重新以命令行的方式運(yùn)行 tomcat ,即可,然后通過最后面介紹的如何觀察tomcat現(xiàn)有內(nèi)存情況的方法進(jìn)行查看是否已經(jīng)變更成功。 |
|