您好,登錄后才能下訂單哦!
這篇文章主要介紹了java基礎(chǔ)之JVM中GC算法怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
JVM內(nèi)存組成結(jié)構(gòu):
(1)堆
所有通過new創(chuàng)建的對象都是在堆中分配內(nèi)存,其大小可以通過-Xmx和-Xms來控制,堆被劃分為新生代和舊生代,新生代又被進(jìn)一步劃分為Eden和Survivor區(qū)。Survivor被劃分為from space 和 to space組成,結(jié)構(gòu)圖如下:
(2)棧
每個線程 執(zhí)行每個方法的時候都會在棧中申請一個棧幀,每個棧幀包含局部變量區(qū)和操作數(shù)棧。用于存放此次方法調(diào)用過程中的臨時變量,參數(shù)和中間結(jié)果
(3)本地方法棧
用于支持native方法的執(zhí)行。存儲了每個native方法調(diào)用的狀態(tài)
(4)方法區(qū)
存放了要加載的類信息,靜態(tài)變量,final類型的常量,屬性和方法信息。JVM用持久代(permanet generation)來存放方法區(qū),可通過-XX:PermSize和 -XX:MaxPermSize來指定最小值和最大值。
(5)程序計數(shù)器
每個線程私有,當(dāng)前線程執(zhí)行的字節(jié)碼的行數(shù)。
JAVA堆內(nèi)存分配機(jī)制
java內(nèi)存分配和回收概括地說:就是分代分配,分代回收。對象將根據(jù)存貨的時間被分為:young generation, old generation,permanent generation。
yong generation:對象被創(chuàng)建時,內(nèi)存的分配首先發(fā)生在年輕代(大對象可以直接創(chuàng)建在old generation),大部分的對象在創(chuàng)建后很快不再使用,因此很快變得不可達(dá),被young generation 的GC機(jī)制清理掉(IBM的研究表示,98%的對象都是很快消亡的),這個GC機(jī)制被稱為Minor GC或者 Young GC;Minor GC并不代表內(nèi)存不足。
young generation分為 3個區(qū)域, eden區(qū),兩個 survivor區(qū)(from survivor, to survivor),內(nèi)存分配過程如下所示:
1.絕大多數(shù)對象剛創(chuàng)建被分配在 eden區(qū),其中的大多數(shù)對象很快就會消亡,eden區(qū)域是連續(xù)的內(nèi)存空間,在其上分配內(nèi)存極快。
2.最初一次,當(dāng)eden區(qū)滿的時候,執(zhí)行 minor GC,將消亡的對象清理掉,并將eden,survivor 1剩余的對象復(fù)制到到一個存活區(qū) Survivor 0(此時Survivor 1是空白的,兩個Survivor總有一個是空白的)
3.下次eden滿了,在執(zhí)行一次 minor GC,將消亡的對象清理掉,存活的對象復(fù)制到survivor1中,清空eden區(qū)。將survivor 0 中消亡的對象清理掉,將其中可以晉級的對象晉級到old區(qū),將存活的對象也復(fù)制到survivor 1中,清空survivor 0
4.當(dāng)被兩個存活期 來回復(fù)制了幾次之后,(用-XX:maxTenuringThreshold 控制,大于該值進(jìn)入old generation,但是這只是個最大值,并不代表一定是這個值,因為:為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。)仍然存活的對象,將被復(fù)制到old generation。
old generation:對象如果在young generation 存活了足夠長的時間而沒有被清理掉,則會被復(fù)制到old generation,old generation 的空間一般比young generation大得多,發(fā)生的GC次數(shù)也比年輕代少,當(dāng)年老代內(nèi)存不足時,將執(zhí)行 Major GC,也叫 Full GC;
可以使用-XX:+UseAdaptiveSizePolicy開關(guān)來控制是否采用動態(tài)控制策略,如果動態(tài)控制,則動態(tài)調(diào)整Java堆中各個區(qū)域的大小以及進(jìn)入老年代的年齡。
如果對象比較大,young generation空間不足,則大對象會直接分配到old generation(大對象可能提前觸發(fā)GC,應(yīng)盡少使用大對象,更少用短命的大對象)。用-XX:PretenureSizeThreshold來控制直接升入老年代的對象大小,大于這個值的對象會直接分配在老年代上。
可能存在年老代對象引用新生代對象的情況,如果需要執(zhí)行Young GC,則可能需要查詢整個老年代以確定是否可以清理回收,這顯然是低效的。解決的方法是,年老代中維護(hù)一個512 byte的塊——”card table“,所有老年代對象引用新生代對象的記錄都記錄在這里。Young GC時,只要查這里即可,不用再去查全部老年代,因此性能大大提高。
JAVA 不同代GC 機(jī)制
young generation:發(fā)生的GC是minor GC,使用停止-復(fù)制算法進(jìn)行清理,將新生代內(nèi)存分為:較大的eden,和兩個相等survivor,每次進(jìn)行清理時,把eden 和一個survivor中存活的對象 復(fù)制到另一個survivor中,如果存活的對象超過了survivor內(nèi)存,則需要通過空間分配擔(dān)保機(jī)制將一部分對象復(fù)制到old generation,然后清理掉eden,和剛才的survivor??梢酝ㄟ^ -XX:SurvivorRation參數(shù)來調(diào)整eden和survivor區(qū)的內(nèi)存容量比值。默認(rèn)是8,eden:survivor:survivor = 8:1:1。
old generation:發(fā)生major GC。存儲的對象比young generation多得多,且存在很多大對象,對old generation進(jìn)行內(nèi)存清理,如果使用 停止-復(fù)制算法,相當(dāng)?shù)托?,一般使?標(biāo)記-整理算法,標(biāo)記出仍然存活的對象(存在引用),將所有存活的對象向一端移動,以保證內(nèi)存的連續(xù)。
在發(fā)生Minor GC時,虛擬機(jī)會檢查每次晉升進(jìn)入老年代的大小是否大于老年代的剩余空間大小,如果大于,則直接觸發(fā)一次Full GC,否則,就查看是否設(shè)置了-XX:+HandlePromotionFailure(允許擔(dān)保失?。?,如果允許,則只會進(jìn)行MinorGC,此時可以容忍內(nèi)存分配失?。蝗绻辉试S,則仍然進(jìn)行Full GC(這代表著如果設(shè)置-XX:+Handle PromotionFailure,則觸發(fā)MinorGC就會同時觸發(fā)Full GC,哪怕老年代還有很多內(nèi)存,所以,最好不要這樣做)。
old generation GC之 標(biāo)記-清除法:標(biāo)記出所有需要回收的對象(可達(dá)性分析),標(biāo)記完成后統(tǒng)一清理掉所有被標(biāo)記的對象。
該算法有兩個問題:
(1)下頻率問題:標(biāo)記和清除過程效率都不高
(2)空間問題:標(biāo)記清除后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,空間碎片太多會導(dǎo)致在運(yùn)行過程需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。
old generation GC之 標(biāo)記整理算法:標(biāo)記過程和標(biāo)記清除算法一樣,但是后續(xù)步驟不再對可回收對象直接清理,而是讓所有存活對象都向一端移動,然后清理掉邊界以外的內(nèi)存。
permanent generation:永久代的垃圾收集分為兩類:廢棄的常量和不再被使用的類。
廢棄常量:比如說我們在常量池中用intern()添個字符串常量“a”,但是現(xiàn)在的系統(tǒng)沒有任何一個String對象叫“a”,所以這個常量就是廢棄了。
不再被使用的類:
①該類的所有的實例都已經(jīng)被回收,Java堆中不存在該類的任何實例。
②加載類的ClassLoader已經(jīng)被回收
③沒有該類的java.lang.Class對象被引用,即不能通過反射訪問該類信息。
滿足了上述三個條件只是滿足了類回收的基本條件,是否回收不用的類需要看設(shè)置的-Xnoclassgc參數(shù)進(jìn)行控制,還可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看類的加載和卸載信息。
在大量使用反射、動態(tài)代理、CGLib等bytecode框架的場景,以及動態(tài)生成JSP和OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機(jī)具備類卸載的功能,以保證永久代不會溢出。
算法分析:
空間分配擔(dān)保:
在執(zhí)行Minor GC前, VM會首先檢查老年代是否有足夠的空間存放新生代尚存活對象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況時, 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無法容納的對象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對象. 但存活對象的大小在實際完成GC前是無法明確知道的, 因此Minor GC前, VM會先首先檢查老年代連續(xù)空間是否大于新生代對象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間).然而取歷次晉升的對象的平均大小也是有一定風(fēng)險的, 如果某次Minor GC存活后的對象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無法存放這些對象了), 此時就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).
GC回收對象確立:
引用計數(shù):如果有引用這個對象的,對象計數(shù)器+1,引用失效,計數(shù)器-1,絕大多數(shù)情況下,這個算法高效簡單,但是不能解決對象之間的循環(huán)引用的關(guān)系,所以沒有被主流語言采用。
可達(dá)性算法:通過一系列的稱為 GC Roots 的對象作為起點, 然后向下搜索; 搜索所走過的路徑稱為引用鏈/Reference Chain, 當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連時, 即該對象不可達(dá), 也就說明此對象是不可用的, 如下圖: Object5、6、7 雖然互有關(guān)聯(lián), 但它們到GC Roots是不可達(dá)的, 因此也會被判定為可回收的對象:
在Java, 可作為GC Roots的對象包括:
方法區(qū): 類靜態(tài)屬性引用的對象; 方法區(qū): 常量引用的對象; 虛擬機(jī)棧(本地變量表)中引用的對象. 本地方法棧JNI(Native方法)中引用的對象。
可達(dá)性算法如果對象到GC Roots不可達(dá)也不是說這個對象會立即被回收,是需要經(jīng)過一個兩次標(biāo)記過程的,第一次:是在可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。第二次是判斷是否需要執(zhí)行finalize(),這個方法也是對象唯一能夠進(jìn)行自我救贖的機(jī)會了,但是不推薦使用,因為這個方法運(yùn)行代價高,不確定性大,不能保證不同對象的執(zhí)行順序。如果不需要執(zhí)行該方法,直接就進(jìn)行回收,如果需要執(zhí)行該方法,那么該對象會被放在一個F-Queue里。雖然會被執(zhí)行,但是不一定保證能夠執(zhí)行成功,因為有可能會在這個方法執(zhí)行過程中出現(xiàn)死循環(huán)等意外情況,所以虛擬機(jī)并不一定會等待這個方法執(zhí)行結(jié)束才進(jìn)行回收。如果在第二次標(biāo)記的時候,該對象沒有成功的自我拯救,那么就真的被回收了。
常用JVM配置參數(shù)
-XX:CMSInitiatingPermOccupancyFraction:當(dāng)永久區(qū)占用率達(dá)到這一百分比時,啟動CMS回收
-XX:CMSInitiatingOccupancyFraction:設(shè)置CMS收集器在老年代空間被使用多少后觸發(fā)
-XX:+CMSClassUnloadingEnabled:允許對類元數(shù)據(jù)進(jìn)行回收
-XX:CMSFullGCsBeforeCompaction:設(shè)定進(jìn)行多少次CMS垃圾回收后,進(jìn)行一次內(nèi)存壓縮
-XX:NewRatio:新生代和老年代的比
-XX:ParallelCMSThreads:設(shè)定CMS的線程數(shù)量
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)
-XX:SurvivorRatio:設(shè)置eden區(qū)大小和survivior區(qū)大小的比例
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:+UseCMSCompactAtFullCollection:設(shè)置CMS收集器在完成垃圾收集后是否要進(jìn)行一次內(nèi)存碎片的整理
-XX:+UseCMSInitiatingOccupancyOnly:表示只在到達(dá)閥值的時候,才進(jìn)行CMS回收
(+不能省)
-Xms:設(shè)置堆的最小空間大小。
-Xmx:設(shè)置堆的最大空間大小。
-XX:NewSize設(shè)置新生代最小空間大小。
-XX:MaxNewSize設(shè)置新生代最大空間大小。
-XX:PermSize設(shè)置永久代最小空間大小。
-XX:MaxPermSize設(shè)置永久代最大空間大小。
-Xss:設(shè)置每個線程的堆棧大小
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“java基礎(chǔ)之JVM中GC算法怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。