JVM调优笔记:认识JVM内存模型(jdk1.8)
文章目錄
- 1、什么是JVM
- 2、jdk、jre、jvm關(guān)系
- 3、JVM執(zhí)行過(guò)程
- 4、JVM執(zhí)行程序的過(guò)程
- 5、JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 虛擬機(jī)棧(線程私有)
- 本地方法棧(線程私有)
- 程序計(jì)數(shù)器(線程私有)
- 堆(線程共享)
- 方法區(qū)(線程共享)
- 6、內(nèi)存分配參數(shù)
- 大小分配
- 比例分配
- 7、垃圾回收
- 算法與思想
- 分類
- 新生代串行收集器 Serial
- 老年代串行收集器 Serial Old
- 新生代并行收集器 ParNew
- 新生代并行回收收集器 Parallel Scavenge
- 老年代并行回收收集器 Parallel Old
- CMS收集器
- G1收集器
- 8、常見(jiàn)調(diào)優(yōu)方法
- 9、其他實(shí)用JVM參數(shù)
1、什么是JVM
(1)JVM即Java Virtual Machine,java虛擬機(jī)。它是一個(gè)虛構(gòu)出來(lái)的機(jī)器,是通過(guò)實(shí)際計(jì)算機(jī)上的仿真模擬各種功能實(shí)現(xiàn)的。
(2)JVM包含了一套字節(jié)碼指令集,一組寄存器、一個(gè)占、一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域。
(3)JVM支持java程序跨平臺(tái)。因?yàn)樗帘瘟伺c具體操作系統(tǒng)平臺(tái)相關(guān)信息,使我們的Java程序只需要生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上運(yùn)行。
JVM在執(zhí)行字節(jié)碼時(shí),實(shí)際上最終還是把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令來(lái)執(zhí)行。
2、jdk、jre、jvm關(guān)系
(1)JRE(Java Runtime Environment),也就是java平臺(tái)。所有的java程序都要在JRE環(huán)境下才能運(yùn)行。
(2)JDK(Java Development Kit),是開(kāi)發(fā)者用來(lái)編譯、調(diào)試程序用的開(kāi)發(fā)包。JDK也是JAVA程序需要在JRE上運(yùn)行。
(3)JVM(Java Virtual Machine),是JRE的一部分。它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。
JVM有自己完善的硬件架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。
Java語(yǔ)言最重要的特點(diǎn)就是跨平臺(tái)運(yùn)行。使用JVM就是為了支持與操作系統(tǒng)無(wú)關(guān),實(shí)現(xiàn)跨平臺(tái)。
3、JVM執(zhí)行過(guò)程
(1)jvm是java的核心和基礎(chǔ),在java編譯器和os平臺(tái)之間的虛擬處理器,可在上面執(zhí)行字節(jié)碼程序。
(2)java編譯器只要面向jvm,生成jvm能理解的字節(jié)碼文件。java源文件經(jīng)編譯成字節(jié)碼程序,通過(guò)jvm將每條指令翻譯成不同的機(jī)器碼,通過(guò)特定平臺(tái)運(yùn)行。
4、JVM執(zhí)行程序的過(guò)程
第一步:程序員寫(xiě)出各種java文件 通過(guò)編譯器編譯成class字節(jié)碼文件
第二步:然后我們通過(guò)tomcat 或者java -jar的形式在linux或windows上運(yùn)行
第三步:前提安裝了jdk 我們的jar或war程序就整體是一個(gè)jvm,在運(yùn)行時(shí)會(huì)首先去讓最頂層的父層 啟動(dòng)類加載器(BootStrap ClassLoader)去java的lib包下加載核心類庫(kù)。然后通過(guò) Extension ClassLoader 擴(kuò)展類加載器 在lib/ext下加載擴(kuò)展類 ,再然后通過(guò)Application ClassLoader應(yīng)用程序類加載器 加載我們自己的java程序中的class字節(jié)碼文件 。。。。。其中加載過(guò)程中會(huì)有雙親委派機(jī)制,就是當(dāng)前應(yīng)用程序類加載器向上層父級(jí)擴(kuò)展類中找尋是否該類應(yīng)由其執(zhí)行,擴(kuò)展類加載器在向上。到達(dá)啟動(dòng)類加載器去核心類庫(kù)中找尋是否是自己的類。如果是則有自己進(jìn)行類加載進(jìn)jvm內(nèi)存方法區(qū)中。如果沒(méi)有則再向下進(jìn)行委派。直到找到是屬于各級(jí)加載器自己的類。如果每一層都沒(méi)有。那么由發(fā)起方自己去執(zhí)行該類(類加載進(jìn)方法區(qū))
第四部:如果我們是通過(guò)tomcat加載的話。tomcat會(huì)將應(yīng)用程序解析,驗(yàn)證,通過(guò)自定義類加載加載如tomcat自己的jvm方法區(qū)。
第五步:放入方法區(qū)的class類會(huì)被字節(jié)碼引擎執(zhí)行字節(jié)碼指令進(jìn)行各種操作(讀寫(xiě)),過(guò)程中會(huì)有程序計(jì)數(shù)器來(lái)記錄指令執(zhí)行的位置。
第六步:執(zhí)行過(guò)程從各個(gè)方法也就是線程執(zhí)行起始,該對(duì)象在建立的時(shí)候會(huì)被放入堆內(nèi)存中。
第七步:初始化的對(duì)象 都會(huì)有自己的成員變量,賦完默認(rèn)值,會(huì)將成員變量進(jìn)行壓棧,進(jìn)入java虛擬機(jī)棧中成為棧幀。并且每一個(gè)成員變量都在棧內(nèi)存中指向堆內(nèi)存的對(duì)象。堆棧相互關(guān)聯(lián)。
第八步:垃圾回收時(shí)刻監(jiān)測(cè)堆內(nèi)存中是否有未被棧內(nèi)存中成員變量指向的對(duì)象。如果有,則進(jìn)行垃圾回收。
第九步:而在方法去的類和棧中的棧幀什么時(shí)候被回收呢?首先該類所有的實(shí)例化對(duì)象都在堆內(nèi)存中被回收。其次加載該類的類加載器已經(jīng)被回收,最后對(duì)該類的class文件沒(méi)有任何堆內(nèi)存的線程或線程中變量引用。這三種情況全部滿足,棧和方法區(qū)的線程和類都全部被回收了。
5、JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
從圖來(lái)看,我們可以把Java內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)。雖然這種分法比較粗糙,實(shí)際上要復(fù)雜的多,不過(guò)初學(xué)者來(lái)說(shuō)這是我們最關(guān)注的的兩塊區(qū)域。
總內(nèi)存=堆內(nèi)存(Xmx)+方法區(qū)內(nèi)存(MaxPermSize)+棧內(nèi)存(Xss)*線程數(shù)+直接內(nèi)存(MaxDirectMemorySize,堆外)+虛擬機(jī)內(nèi)存
虛擬機(jī)棧(線程私有)
(1)Java棧中存放的是一個(gè)個(gè)的棧幀,每個(gè)棧幀對(duì)應(yīng)一個(gè)被調(diào)用的方法,在棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)
(2)指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池(運(yùn)行時(shí)常量池的概念在方法區(qū)部分會(huì)談到)的引用(Reference to runtime constant pool)
(3)方法返回地址(Return Address)和一些額外的附加信息。當(dāng)線程執(zhí)行一個(gè)方法時(shí),就會(huì)隨之創(chuàng)建一個(gè)對(duì)應(yīng)的棧幀,并將建立的棧幀壓棧。當(dāng)方法執(zhí)行完畢之后,便會(huì)將棧幀出棧。
這塊區(qū)域存在兩種異常情況:
(1)如果線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度,拋出StackOverflowError異常;
(2)如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,且擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常。
在這里也說(shuō)一個(gè)題外話,由于每個(gè)方法從進(jìn)入到返回對(duì)應(yīng)著棧幀的壓入和彈出,這個(gè)過(guò)程需要耗費(fèi)一定的時(shí)間和資源,所以也意味著代碼中調(diào)用的方法越多,執(zhí)行效率也會(huì)越低。所以拆分方法的也不是越多越好的。
本地方法棧(線程私有)
本地方法棧和Java虛擬機(jī)棧的功能很相似,Java虛擬機(jī)棧用于管理Java函數(shù)的調(diào)用,而本地方法棧用于管理Native方法的調(diào)用。本地方法并不是用Java實(shí)現(xiàn)的,而是使用C實(shí)現(xiàn)的。在HotSpot虛擬機(jī)中,不區(qū)分本地方法棧和虛擬機(jī)棧,因此和虛擬機(jī)棧一樣,它也會(huì)拋出StackOverFlowError和OutOfMemoryError
程序計(jì)數(shù)器(線程私有)
(1)由于在JVM中,多線程是通過(guò)線程輪流切換來(lái)獲得CPU執(zhí)行時(shí)間的.
(2)在任一具體時(shí)刻,一個(gè)CPU的內(nèi)核只會(huì)執(zhí)行一條線程中的指令
(3)為了能夠使得每個(gè)線程都在線程切換后能夠恢復(fù)在切 換之前的程序執(zhí)行位置,每個(gè)線程都需要有自己獨(dú)立的程序計(jì)數(shù)器,并且不能互相被干擾
程序計(jì)數(shù)器是每個(gè)線程所私有的,由于程序計(jì)數(shù)器中存儲(chǔ)的數(shù)據(jù)所占空間的大小不會(huì)隨程序的執(zhí)行而發(fā)生改變,因此,對(duì)于程序計(jì)數(shù)器是不會(huì)發(fā)生內(nèi)存溢出現(xiàn)象(OutOfMemory)的。
堆(線程共享)
(1)Java堆可以說(shuō)是Java運(yùn)行時(shí)內(nèi)存中最重要的部分,是被所有線程共享的一塊區(qū)域幾乎所有的對(duì)象和數(shù)組都是在堆空間中分配空間的,但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)導(dǎo)致一些微妙的變化,所有對(duì)象堆上分配也漸漸變得不那么”絕對(duì)”了。
(2)Java堆分為新生代和老年代兩個(gè)部分,新生代又可進(jìn)一步分為eden、Survivor space0(from space)和survivor space1(to space)。eden意為伊甸園,即對(duì)象的出生地,s0和s1為survivor空間,可意為幸存者,如果幸存者的對(duì)象到了指定年齡仍未被回收,則有機(jī)會(huì)進(jìn)入老年代(tenured)。
默認(rèn)值:
老年代:2/3的堆空間
年輕代:1/3的堆空間
eden區(qū):8/10 的年輕代
survivor0: 1/10 的年輕代
survivor1:1/10的年輕代
方法區(qū)(線程共享)
(1)被所有方法線程共享的一塊內(nèi)存區(qū)域。
(2)用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息,常量,靜態(tài)變量等。
(3)這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要針對(duì)常量池的回收和堆類型的卸載。
在JDK1.8之前HotSpot的實(shí)現(xiàn)中是用永久代實(shí)現(xiàn)的,雖然叫永久代但同樣也是可以被GC回收的,只是對(duì)于GC的表現(xiàn)也和堆空間略有不同,通常對(duì)永久代GC的回收從兩個(gè)方面分析,一是GC對(duì)永久代常量池的回收,二是永久代對(duì)類元數(shù)據(jù)的回收(需要虛擬機(jī)確認(rèn)該類的所有實(shí)例已被回收并不會(huì)再被使用,并且加載該類的ClassLoader已經(jīng)被回收,GC就有可能回收該類型);而在JDK1.8之后永久代被拋棄,使用元空間。
6、內(nèi)存分配參數(shù)
大小分配
最大堆內(nèi)存:-Xmx,指新生代和老年代的大小之和的最大值
最小堆內(nèi)存:-Xms,Java應(yīng)用程序運(yùn)行時(shí),首先會(huì)被分配的內(nèi)存大小,無(wú)法滿足時(shí)會(huì)向操作系統(tǒng)申請(qǐng)更多的內(nèi)存。JVM會(huì)試圖將系統(tǒng)內(nèi)存盡可能限制在-Xms中,因此當(dāng)內(nèi)存實(shí)際使用量觸及-Xms指定大小時(shí),會(huì)觸發(fā)Full GC,因此把-Xms設(shè)置為-Xmx時(shí),可以在系統(tǒng)運(yùn)行初期減少GC次數(shù)和耗時(shí)
新生代:-Xmn,設(shè)置新生代的大小會(huì)影響老年代的大小,這個(gè)參數(shù)對(duì)系統(tǒng)性能與GC有很大的影響。一般新生代設(shè)置為整個(gè)堆內(nèi)存空間的1/4到1/3左右。在HotSpot中,可以用-XX:NewSize和-XX:MaxNewSize分別設(shè)置新生代初始值和最大值,但是一般用-Xmn統(tǒng)一設(shè)置就足夠了
永久代:-XX:PermSize和-XX:MaxPermSize,在JDK1.8之前,可以分別設(shè)置永久代的初始大小和最大值
元空間:-XX:MetaspaceSize和-XX:MaxMetaspaceSize,在JDK1.8之后使用元空間,元空間的大小僅受本地內(nèi)存限制,但可以分別設(shè)置元空間的初始大小和最大值(默認(rèn)沒(méi)有限制)
每個(gè)線程堆棧:-Xss,在線程中進(jìn)行局部變量分配,函數(shù)調(diào)用時(shí)都需要在棧中開(kāi)辟空間。如果棧的空間分配太小,那線程運(yùn)行中可能沒(méi)有足夠空間分配局部變量或達(dá)不到足夠的深度導(dǎo)致異常退出;如果棧空間過(guò)大,那么開(kāi)設(shè)線程所需的內(nèi)存成本上升,系統(tǒng)所能支持的線程總數(shù)也會(huì)下降
Direct內(nèi)存:-XX:MaxDirectMemorySize,javaNIO中通過(guò)Direct內(nèi)存來(lái)提高性能,這個(gè)區(qū)域的大小默認(rèn)是64M,在適當(dāng)?shù)膱?chǎng)景可以設(shè)置大一些
比例分配
新生代:-XX:SurvivorRatio,用于設(shè)置新生代中,eden空間和s0空間的比例關(guān)系,其中s0與s1空間大小是相同的,只能也是一樣的,并在MinorGC之后會(huì)互換角色,因此值為eden/s0 = eden/s1
新生代與老年代:-XX:NewRatio,用來(lái)設(shè)置老年代/新生代之間的比例
survivior使用率:-XX:TargetSurvivorRatio,設(shè)置survivior區(qū)的可使用率,當(dāng)使用率達(dá)到這個(gè)數(shù)值時(shí),會(huì)將對(duì)象送入老年代
新生代存活次數(shù):-XX:MaxTenuringThreshold,在新生代中對(duì)象存活次數(shù)(經(jīng)過(guò)Minor GC的次數(shù))后仍然存活,就會(huì)晉升到老年代
直接進(jìn)入老年代:-XX:PretenureSizeThreshold,設(shè)置大對(duì)象直接進(jìn)入老年代的閾值,當(dāng)對(duì)象的大小超過(guò)這個(gè)值時(shí)直接在老年代分配
堆空閑比例:-XX:MinHeapFreeRatio與-XX:MaxHeapFreeRatio,設(shè)置堆空間的對(duì)最小/最大空閑比例。當(dāng)堆空間的空閑內(nèi)存大于/小于對(duì)應(yīng)值時(shí),JVM便會(huì)擴(kuò)展/壓縮堆空間大小
元空間空閑比例:-XX:MinMetaspaceFreeRatio與-XX:MaxMetaspaceFreeRatio,設(shè)置最小/最大的Metaspace剩余空間容量的百分比,Metaspace GC之后,用來(lái)控制擴(kuò)展/壓縮元空間大小
可以使用-XX:PrintGCDetails來(lái)打印出堆的實(shí)際大小
7、垃圾回收
算法與思想
引用計(jì)數(shù)法:最經(jīng)典也是最古老的一種垃圾收集方法。引用計(jì)數(shù)器,只需要為每個(gè)對(duì)象配備一個(gè)整形的計(jì)數(shù)器即可,但是引用計(jì)數(shù)器有個(gè)嚴(yán)重的問(wèn)題,即無(wú)法處理循環(huán)引用問(wèn)題。因此在Java的垃圾回收器中沒(méi)有使用這種算法
標(biāo)記-清除算法(Mark-Sweep):是現(xiàn)代垃圾回收算法的思想基礎(chǔ)。將垃圾回收分為兩個(gè)階段,標(biāo)記階段和清除階段。一種可行的實(shí)現(xiàn)是,在標(biāo)記階段通過(guò)根節(jié)點(diǎn)標(biāo)記所有從根節(jié)點(diǎn)開(kāi)始可達(dá)的對(duì)象,然后在清除節(jié)點(diǎn)清除所有未被標(biāo)記的垃圾對(duì)象。標(biāo)記-清除算法可能產(chǎn)生的最大問(wèn)題就是空間碎片,因?yàn)榛厥蘸蟮目臻g是不連續(xù)的
復(fù)制算法(Copying):與標(biāo)記-清除算法相比,復(fù)制算法是一種相對(duì)高效的回收方法。核心思想是將原有的內(nèi)存空間分為兩塊,每次只使用一塊,在垃圾回收時(shí),將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未使用的內(nèi)存中,之后清除正在使用的內(nèi)存塊的所有對(duì)象,交換兩個(gè)內(nèi)存的角色,完成垃圾回收。如果系統(tǒng)中垃圾對(duì)象多,復(fù)制算法需要復(fù)制的對(duì)象數(shù)量不會(huì)太多,又由于對(duì)象被復(fù)制到新的內(nèi)存空間,所以確保沒(méi)有碎片。但是復(fù)制算法缺點(diǎn)是將系統(tǒng)內(nèi)存折半,因此單純的復(fù)制算法難以讓人接受
在Java的新生代串行垃圾回收期中,使用了復(fù)制算法的思想,將新生代分為eden空間、from空間和to空間3個(gè)部分。其中from和to空間可以視為用于復(fù)制的兩塊大小相同、地位相等且可角色互換的空間塊。復(fù)制算法比較適合新生代,因?yàn)樵谛律?#xff0c;垃圾對(duì)象通常會(huì)多于存活對(duì)象,復(fù)制算法的效果會(huì)比較好
標(biāo)記-壓縮算法(Mark-Compact):復(fù)制算法的高效建立在存活對(duì)象少、垃圾對(duì)象多的場(chǎng)景下,因此適合年輕代。但是對(duì)于老年代,更常見(jiàn)的是大部分對(duì)象都是存活對(duì)象,因此基于這種特性,需要使用新的算法。標(biāo)記-壓縮算法在標(biāo)記-清除的基礎(chǔ)上做了一些優(yōu)化。與標(biāo)記-清除一樣從根節(jié)點(diǎn)開(kāi)始對(duì)所有可達(dá)對(duì)象做一次標(biāo)記,但是之后清理未標(biāo)記對(duì)象時(shí),將存活對(duì)象壓縮到內(nèi)存的一端,然后清理邊界外的所有空間。這種方法既避免碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間,因此性價(jià)比較高
增量算法(Incremental Collecting):對(duì)大部分垃圾回收算法來(lái)說(shuō),在回收過(guò)程應(yīng)用程序都處于stop the world狀態(tài),所有線程都會(huì)掛起,暫停一切正常工作等待垃圾回收完成。如果回收時(shí)間很長(zhǎng)就會(huì)嚴(yán)重影響用戶體驗(yàn)和系統(tǒng)穩(wěn)定性。增量算法的基本思想是,如果一次性將所有垃圾進(jìn)行處理,需要造成系統(tǒng)長(zhǎng)時(shí)間停頓,那么就可以讓垃圾回收線程和應(yīng)用程序線程交替執(zhí)行。每次垃圾收集線程只收集一小塊的內(nèi)存空間,然后切換到應(yīng)用程序線程,如此反復(fù)直到垃圾收集完成。這種方式下,能間斷性地執(zhí)行應(yīng)用程序代碼,所以能減少系統(tǒng)停頓時(shí)間,但是由于線程切換和上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收總成本上升,造成系統(tǒng)吞吐量下降
分代(Generational Collecting):所有的算法都無(wú)法完全替代其他算法,都具有獨(dú)特的優(yōu)勢(shì)和特點(diǎn),因此根據(jù)垃圾回收對(duì)象的特性,使用合適的算法才是明智之舉。分代就是基于這種思想,將內(nèi)存區(qū)間根據(jù)對(duì)象的特點(diǎn)分成幾塊,根據(jù)每塊內(nèi)存的特點(diǎn)選擇不同的回收算法,提高回收效率。幾乎所有的垃圾回收器都區(qū)分年輕代和老年代
分區(qū)算法(Region):分代算法是按照對(duì)象的生命周期長(zhǎng)短劃分成兩個(gè)部分,分區(qū)算法是將整個(gè)堆空間劃分為連續(xù)的不同小區(qū)間,每個(gè)小區(qū)間都獨(dú)立使用,獨(dú)立回收。這種算法的好處是可以控制一次回收多少個(gè)小區(qū)間,一般來(lái)說(shuō)堆空間越大,相同條件下一次GC所需的時(shí)間就越長(zhǎng),那么每次合理回收若干個(gè)小區(qū)塊,從而可以減少一次GC所產(chǎn)生的停頓
分類
按線程數(shù)分,可以分為串行垃圾回收器和并行垃圾回收器;按工作模式分,可以分為并發(fā)式垃圾回收器和獨(dú)占式垃圾回收器;按碎片處理方式,可以分為壓縮式垃圾回收器和非壓縮式垃圾回收器;按工作的內(nèi)存區(qū)間,可以分為新生代垃圾回收器和年老代垃圾回收器
評(píng)價(jià)指標(biāo):
1、吞吐量:指在應(yīng)用程序的生命周期內(nèi),應(yīng)用程序所花費(fèi)的時(shí)間和系統(tǒng)總運(yùn)行時(shí)間(應(yīng)用耗時(shí)+GC耗時(shí))的比值
2、垃圾回收器負(fù)載:與吞吐量相反,是垃圾回收器耗時(shí)與系統(tǒng)運(yùn)行總時(shí)間的比值
3、停頓時(shí)間:指垃圾回收器正在運(yùn)行時(shí),應(yīng)用程序的暫停時(shí)間
4、垃圾回收頻率:指垃圾回收器多長(zhǎng)時(shí)間會(huì)運(yùn)行一次
5、反應(yīng)時(shí)間:指當(dāng)一個(gè)對(duì)象成為垃圾后,多長(zhǎng)時(shí)間它所占用的內(nèi)存會(huì)被釋放
6、堆分配:不同垃圾回收器對(duì)堆內(nèi)存的分配方式可能不同,一個(gè)良好的垃圾回收期需要有一個(gè)合理的堆內(nèi)存區(qū)間劃分
新生代串行收集器 Serial
是最古老的一種,也是JDK最基本的垃圾收集器之一。主要有兩個(gè)特點(diǎn):第一,它僅僅使用單線程進(jìn)行垃圾回收;第二,它是獨(dú)占式的垃圾回收。因此垃圾收集器運(yùn)行時(shí),應(yīng)用所有線程都停止工作進(jìn)行等待。雖然如此,但是串行收集器是一個(gè)成熟、經(jīng)過(guò)長(zhǎng)時(shí)間考驗(yàn)的極為高效的收集器。新生代串行收集器使用復(fù)制算法,實(shí)現(xiàn)相對(duì)簡(jiǎn)單,邏輯處理高效,而且沒(méi)有線程切換的開(kāi)銷,在諸如單CPU或較小應(yīng)用內(nèi)存等硬件的平臺(tái),它的性能可以超過(guò)并行回收器和并發(fā)回收器
-XX:+UseSerialGC,指定使用新生代串行收集器和年老代串行收集器。當(dāng)JVM在Client模式下時(shí),它是默認(rèn)的垃圾收集器
老年代串行收集器 Serial Old
采用的是標(biāo)記-壓縮算法,和新生代串行收集器一樣,是串行的、獨(dú)占式的垃圾回收器。由于老年代垃圾回收器通常比新生代垃圾回收器使用更長(zhǎng)的時(shí)間,因此在堆空間較大的應(yīng)用中,一旦老年串行收集器啟動(dòng),應(yīng)用程序?qū)?huì)因此停頓幾秒甚至更長(zhǎng)。雖然如此,作為老牌的垃圾回收器,老年代串行收集器可以和多種新生代回收器配合使用,同時(shí)它也作為CMS回收器的備用回收器
-XX:+UseSerialGC,新生代、老年代都使用串行回收器
-XX:+UseParNewGC,新生代使用并行收集器,老年代使用串行收集器
-XX:+UseParallelGC,新生代使用并行回收收集器,老年代使用串行收集器
新生代并行收集器 ParNew
并行收集器是工作在新生代的垃圾收集器,它只是簡(jiǎn)單地將串行回收器多線程化,它的回收策略、算法以及參數(shù)和串行回收器一樣,并行回收器也是獨(dú)占式的回收器。但由于多線程進(jìn)行垃圾回收,因此在并發(fā)能力較強(qiáng)的CPU上,暫停時(shí)間短于串行回收器
-XX:ParallelGCThreads,指定工作時(shí)的線程數(shù)量,一般與CPU數(shù)量相當(dāng)
-XX:+UseParNewGC,新生代使用并行收集器,老年代使用串行收集器
-XX:+UseConcMarkSweepGC,新生代使用并行收集器,老年代使用CMS
新生代并行回收收集器 Parallel Scavenge
新生代并行回收器也是使用復(fù)制算法的收集器,表面上和并行收集器一樣,都是多線程、獨(dú)占式的收集器。但是它有一個(gè)重要的特點(diǎn),就是它非常關(guān)注系統(tǒng)的吞吐量。除此之外,并行回收收集器與并行收集器另一個(gè)不同之處在于,它還支持一種自適應(yīng)的GC調(diào)節(jié)策略,這種模式下,新生代的大小、eden和survivor的比例、晉升老年代的對(duì)象年齡等參數(shù)都會(huì)被自動(dòng)調(diào)整,已達(dá)到堆大小、吞吐量和停頓時(shí)間的平衡點(diǎn),在手動(dòng)調(diào)優(yōu)比較難的場(chǎng)合可以使用這種自適應(yīng)的方式
-XX:+UseParallelGC,新生代使用并行回收收集器,老年代使用串行收集器
-XX:+UseParallelOldGC,新生代和老年代都使用并行回收收集器
-XX:MaxGCPauseMillis,設(shè)置最大垃圾收集停頓時(shí)間,值為大于0的整數(shù)。收集器在工作時(shí),會(huì)調(diào)整Java堆大小或其他參數(shù),盡可能把停頓時(shí)間控制在范圍內(nèi)。如果把值設(shè)置過(guò)小,可能JVM會(huì)使用很小的堆(小堆回收更快),這將導(dǎo)致垃圾回收變得頻繁,反而增加總時(shí)間,降低吞吐量
-XX:GCTimeRatio,設(shè)置吞吐量大小,值是0~100之間的整數(shù)
-XX:+UseAdaptiveSizePolicy,打開(kāi)自適應(yīng)GC策略
老年代并行回收收集器 Parallel Old
老年代并行回收收集器也是一種多線程并發(fā)的收集器,和新生代并行回收收集器一樣,也是一種關(guān)注吞吐量的收集器。老年代并行回收收集器使用標(biāo)記-壓縮算法
-XX:+UseParallelOldGC,新生代和老年代都使用并行回收收集器
CMS收集器
與并行回收收集器不同,CMS收集器主要關(guān)注系統(tǒng)停頓時(shí)間,是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。是Concurrent Mark Sweep的縮寫(xiě),意為并發(fā)標(biāo)記清除,因此它使用的是標(biāo)記-清除算法,同時(shí)也是一個(gè)使用多線程并行回收的垃圾收集器。只會(huì)回收老年代和永久帶(1.8開(kāi)始為元數(shù)據(jù)區(qū),需要設(shè)置CMSClassUnloadingEnabled),不會(huì)收集年輕代。CMS收集器的工作過(guò)程略顯復(fù)雜,主要步驟有:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除和并發(fā)重置(回收完成后重新初始化CMS數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù))。其中初始標(biāo)記和重新標(biāo)記是獨(dú)占系統(tǒng)資源的,而并發(fā)標(biāo)記、并發(fā)清除和并發(fā)重置是可以和用戶線程一起并發(fā)執(zhí)行的。因此不算是獨(dú)占式的,可以在應(yīng)用程序運(yùn)行過(guò)程中進(jìn)行垃圾回收
-XX:+UseConcMarkSweepGC,使用CMS收集器
-XX:ParallelGCThreads,設(shè)置CMS的線程數(shù)量
-XX:CMSInitiatingOccupancyFraction,設(shè)置老年代使用率回收閾值,因?yàn)镃MS回收時(shí),應(yīng)用持續(xù)工作,因此會(huì)有新的垃圾產(chǎn)生,而這些垃圾在CMS回收過(guò)程中無(wú)法清除,因此CMS回收過(guò)程中還需要保證有足夠的內(nèi)存可用,這樣就不等待堆內(nèi)存飽和再進(jìn)行回收,而是當(dāng)堆內(nèi)存使用率達(dá)到某一閾值后就進(jìn)行回收
-XX:UseCMSCompactAtFullCollection,開(kāi)關(guān)使CMS在垃圾收集完成后,進(jìn)行一次內(nèi)存碎片整理,內(nèi)存碎片整理不是并發(fā)進(jìn)行的
-XX:CMSFullGCsBeforeCompaction,指定進(jìn)行多少次CMS回收后進(jìn)行一次內(nèi)存壓縮
G1收集器
是目前最新的垃圾回收器,目標(biāo)是作為一款服務(wù)端的垃圾收集器,因此在吞吐量和停頓控制上,預(yù)期要優(yōu)于CMS收集器。與CMS相比,G收集器是基于標(biāo)記-壓縮算法的,因此不會(huì)產(chǎn)生空間碎片,G1收集器還可以進(jìn)行非常精確的停頓控制。G1能充分利用CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè)CPU(CPU或者CPU核心)來(lái)縮短stop-The-World停頓時(shí)間。部分其他收集器原本需要停頓Java線程執(zhí)行的GC動(dòng)作,G1收集器仍然可以通過(guò)并發(fā)的方式讓java程序繼續(xù)執(zhí)行。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆,但是還是保留了分代的概念。降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),但G1除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi)。G1的運(yùn)行步驟有1、初始標(biāo)記;2、并發(fā)標(biāo)記;3、最終標(biāo)記;4、篩選回收
-XX:+UseG1GC,啟用G1回收器
-XX:+UnlockExperimentalVMOptions,允許使用實(shí)驗(yàn)性參數(shù)
-XX:GCPauseIntervalMillis,設(shè)置停頓間隔時(shí)間
-XX:MaxGCPauseMillis,設(shè)置最大垃圾收集停頓時(shí)間
8、常見(jiàn)調(diào)優(yōu)方法
將新對(duì)象預(yù)留在新生代:由于Full GC的成本要遠(yuǎn)遠(yuǎn)高于Minor GC,因此盡可能將對(duì)象分配在新生代是一項(xiàng)明智的做法。雖然大部分情況下,JVM會(huì)嘗試在eden區(qū)分配對(duì)象,但是由于空間緊張等問(wèn)題,很可能不得不將部分年輕代對(duì)象提前先老年代壓縮。因此在JVM參數(shù)調(diào)優(yōu)時(shí),可以為應(yīng)用分配一個(gè)合理的新生代空間,最大限度避免新對(duì)象直接進(jìn)入老年代的情況
-Xmn,-XX:NewRatio,-XX:SurvivorRatio
大對(duì)象進(jìn)入老年代:大部分情況下將對(duì)象分配到新生代是合理的,但是對(duì)于大對(duì)象,很可能擾亂新生代GC,并破壞新生代原有的對(duì)象結(jié)構(gòu)。因?yàn)閲L試在新生代分配大對(duì)象,可能導(dǎo)致空間不足,JVM不得不將新生代中的年輕對(duì)象移動(dòng)到年老代。因?yàn)榇髮?duì)象占用空間大,所以可能需要移動(dòng)大量小的年輕對(duì)象進(jìn)入老年代,這對(duì)GC來(lái)說(shuō)是相當(dāng)不利的。但是如果大對(duì)象是個(gè)短命的對(duì)象,這種情況出現(xiàn)比較頻繁,那對(duì)GC也是一種災(zāi)難,擾亂了分代內(nèi)存回收的思想,因此應(yīng)該盡可能避免使用短命的大對(duì)象
-XX:PretenureSizeThreshold
設(shè)置對(duì)象進(jìn)入老年代的年齡:對(duì)象在eden區(qū)經(jīng)過(guò)一次GC后還存活,就移到survivior區(qū)并年齡加1,直到達(dá)到閾值進(jìn)入老年代。默認(rèn)值是15,但不意味著必須要達(dá)到這個(gè)年齡才能進(jìn)入老年代。實(shí)際上,對(duì)象實(shí)際進(jìn)入老年代的年齡是虛擬機(jī)在運(yùn)行時(shí)根據(jù)內(nèi)存使用情況動(dòng)態(tài)計(jì)算的,參數(shù)只是可以指定閾值年齡的最大值,即實(shí)際晉升年齡取閾值與動(dòng)態(tài)計(jì)算年齡中的最小值
-XX:MaxTenuringThreshold
穩(wěn)定與震蕩堆大小:穩(wěn)定的堆大小對(duì)垃圾回收是有利的,獲得穩(wěn)定堆大小的方式就是將-Xms和-Xmx設(shè)為大小一致。這樣運(yùn)行時(shí)堆大小恒定,穩(wěn)定的堆空間可以減少GC次數(shù)。但是不穩(wěn)定的堆也并不是毫無(wú)用處,穩(wěn)定的堆雖然減少GC次數(shù),但是也可能增加每次GC的時(shí)間。當(dāng)系統(tǒng)不需要大內(nèi)存時(shí),讓堆大小在一個(gè)區(qū)間中震蕩,壓縮堆空間,使GC應(yīng)對(duì)一個(gè)較小的堆,可以加快單次GC速度
-XX:MinHeapFreeRatio,-XX:MaxHeapFreeRatio
吞吐量?jī)?yōu)先案例:-Xms與-Xmx一致,使用-XX:+UseParallelGC新生代使用并行回收收集器并設(shè)置線程數(shù),-XX:UseParallelOldGC老年代也使用并行回收收集器
使用大頁(yè)案例:-XX:LargePageSizeInBytes設(shè)置大頁(yè)的大小,使用大的內(nèi)存分頁(yè)可以增強(qiáng)CPU的內(nèi)存尋址能力,從而提高系統(tǒng)性能
降低停頓案例:首先考慮使用關(guān)注系統(tǒng)停頓時(shí)間的CMS回收器,其次考慮減少Full GC次數(shù),應(yīng)盡可能將對(duì)象預(yù)留在新生代
9、其他實(shí)用JVM參數(shù)
JIT編譯參數(shù),JVM的JIT編譯器,可以在運(yùn)行時(shí)將字節(jié)碼編譯為本地代碼,從而提高函數(shù)的執(zhí)行效率,JIT編譯會(huì)花費(fèi)一定時(shí)間,但未來(lái)運(yùn)行中這些時(shí)間會(huì)被賺回來(lái)
-XX:CompileThreshold,JIT編譯閾值,當(dāng)函數(shù)調(diào)用次數(shù)超過(guò)該值,JIT就將字節(jié)碼編譯為本地機(jī)器碼。在Client模式默認(rèn)1500,Server模式下默認(rèn)10000 -XX:+CITime,打印JIT編譯的耗時(shí) -XX:+PrintCompilation,打印JIT編譯的信息堆快照
-XX:+HeapDumpOnOutOfMemoryError,將當(dāng)前的堆信息保存到文件中,對(duì)于排查問(wèn)題是很有幫助的 -XX:HeapDumpPath,指定堆快照保存位置取得GC信息
-verbose:gc或-XX:+PrintGC,獲取簡(jiǎn)要的GC信息 -XX:+PrintGCDetails,獲取詳細(xì)GC信息類和對(duì)象跟蹤
-XX:+TraceClassLoading,跟蹤類加載信息 -XX:+TraceClassUnloading,跟蹤類卸載信息 -verbose:class,同時(shí)打開(kāi)類加載和類卸載信息控制GC
-XX:+DisableExplicitGC,禁止在程序中使用System.gc()觸發(fā)Full GC -Xnoclassgc,不需要回收類 -Xincgc,開(kāi)啟后系統(tǒng)會(huì)進(jìn)行增量式的GC,增量式GC使用特定算法讓GC線程與應(yīng)用線程交叉執(zhí)行,從而減小停頓時(shí)間Solaris下線程控制
-XX:+UseBoundThreads,綁定所有用戶線程到內(nèi)核線程,減少線程進(jìn)饑餓狀態(tài)的次數(shù) -XX:+UseLWPSynchronization,使用內(nèi)核線程代替線程同步 -XX:+UseVMInterruptibleIO,允許運(yùn)行時(shí)中斷線程使用大頁(yè)
-XX:+UseLargePages,啟用大頁(yè) -XX:LargePageSizeInBytes,設(shè)置大頁(yè)的大小總結(jié)
以上是生活随笔為你收集整理的JVM调优笔记:认识JVM内存模型(jdk1.8)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 普罗米修斯笔记:初识Prometheus
- 下一篇: BigDecimal运算的工具类