知识蒸馏 循环蒸馏_Java垃圾收集蒸馏
知識(shí)蒸餾 循環(huán)蒸餾
串行,并行,并發(fā),CMS,G1,Young Gen,New Gen,Old Gen,Perm Gen,Eden,Tenured,Survivor Spaces,Safepoints和數(shù)百個(gè)JVM啟動(dòng)標(biāo)志。 在嘗試從Java應(yīng)用程序獲取所需的吞吐量和延遲的同時(shí)調(diào)整垃圾收集器時(shí),這是否使您感到困惑? 如果確實(shí)如此,那就不用擔(dān)心,您并不孤單。 描述垃圾收集的文檔就像飛機(jī)的手冊(cè)頁。 每個(gè)旋鈕和轉(zhuǎn)盤都有詳細(xì)的說明,但找不到任何有關(guān)如何飛行的指南。 本文將嘗試解釋為特定工作負(fù)載選擇和調(diào)整垃圾收集算法時(shí)的權(quán)衡取舍。
重點(diǎn)將放在Oracle Hotspot JVM和OpenJDK收集器上,因?yàn)樗鼈兪亲畛S玫氖占鳌?最后,將討論其他商用JVM以說明替代方案。
權(quán)衡
明智的人們不斷告訴我們: “您一無所獲” 。 當(dāng)我們得到一些東西時(shí),我們通常不得不放棄一些回報(bào)。 當(dāng)涉及到垃圾收集時(shí),我們使用3個(gè)主要變量來為收集器設(shè)置目標(biāo):
注意: Hotspot通常無法實(shí)現(xiàn)這些目標(biāo),并且會(huì)在沒有任何警告的情況下默默地繼續(xù)運(yùn)行,因?yàn)樗呀?jīng)大大偏離了目標(biāo)。
延遲是事件之間的分布。 可以增加平均等待時(shí)間以減少最壞情況的等待時(shí)間,或者降低等待時(shí)間,這是可以接受的。 我們不應(yīng)將“實(shí)時(shí)”一詞解釋為意味著最低的延遲。 實(shí)時(shí)是指無論吞吐量如何都具有確定性的延遲。
對(duì)于某些應(yīng)用程序工作負(fù)載,吞吐量是最重要的目標(biāo)。 一個(gè)例子是長(zhǎng)期運(yùn)行的批處理作業(yè)。 只要可以更快地完成整個(gè)作業(yè),那么在進(jìn)行垃圾收集時(shí)是否偶爾將批處理作業(yè)暫停幾秒鐘并不重要。
對(duì)于幾乎所有其他工作負(fù)載,從面向人類的交互式應(yīng)用程序到金融交易系統(tǒng),如果系統(tǒng)在某些情況下無法響應(yīng)的時(shí)間超過幾秒鐘甚至幾毫秒,則可能會(huì)帶來災(zāi)難。 在金融交易中,通常值得犧牲一些吞吐量以換取一致的延遲。 我們可能還會(huì)有一些應(yīng)用程序,這些應(yīng)用程序受到可用物理內(nèi)存量的限制,并且必須保持占用空間,在這種情況下,我們必須放棄延遲和吞吐量方面的性能。
權(quán)衡通常表現(xiàn)如下:
- 通過為垃圾回收算法提供更多的內(nèi)存,可以在很大程度上減少作為攤銷成本的垃圾回收成本。
- 通過包含活動(dòng)集并保持堆大小較小,可以減少由于垃圾收集而導(dǎo)致的觀察到的最壞情況的延遲引發(fā)的暫停。
- 通過管理堆和生成大小以及控制應(yīng)用程序的對(duì)象分配速率,可以減少出現(xiàn)暫停的頻率。
- 通過與應(yīng)用程序同時(shí)運(yùn)行GC,可以減少較大的暫停頻率,有時(shí)會(huì)犧牲吞吐量。
對(duì)象壽命
垃圾收集算法通常經(jīng)過優(yōu)化,以期大多數(shù)對(duì)象的生存期很短,而很少有對(duì)象生存期很長(zhǎng)。 在大多數(shù)應(yīng)用程序中,生存期很長(zhǎng)的對(duì)象往往構(gòu)成隨時(shí)間分配的對(duì)象的很小一部分。 在垃圾收集理論中,這種觀察到的行為通常被稱為“ 嬰兒死亡率 ”或“ 弱代假設(shè) ”。 例如,循環(huán)迭代器通常壽命很短,而靜態(tài)字符串實(shí)際上是永生的。
實(shí)驗(yàn)表明,世代垃圾收集器通常可以比非世代收集器支持更大數(shù)量級(jí)的吞吐量,因此幾乎在服務(wù)器JVM中廣泛使用。 通過分離對(duì)象的世代,我們知道新分配對(duì)象的區(qū)域?qū)τ诨顒?dòng)對(duì)象可能非常稀疏。 因此,收集器在此新區(qū)域中清除少量活動(dòng)對(duì)象并將其復(fù)制到較舊對(duì)象的另一個(gè)區(qū)域中可以非常有效。 熱點(diǎn)垃圾收集器根據(jù)生存的GC周期數(shù)記錄對(duì)象的壽命。
注意:如果您的應(yīng)用程序持續(xù)生成許多可以生存很長(zhǎng)時(shí)間的對(duì)象,則可以預(yù)期您的應(yīng)用程序?qū)⒒ㄙM(fèi)大量時(shí)間進(jìn)行垃圾回收,并希望花費(fèi)大量時(shí)間來調(diào)整Hotspot垃圾收集器。 這是由于世代“過濾器”效率降低時(shí)發(fā)生的GC效率降低,以及導(dǎo)致更頻繁地收集更長(zhǎng)壽命的世代的成本。 老一輩人稀疏,因此老一輩人收集算法的效率往往要低得多。 分代垃圾收集器通常以兩個(gè)不同的收集周期運(yùn)行:收集短期對(duì)象的次要垃圾收集,以及收集較舊區(qū)域的次要垃圾收集。
世界停止事件
在垃圾回收期間,應(yīng)用程序遭受的暫停是由于所謂的世界停止事件造成的。 為了使垃圾收集器運(yùn)行,出于實(shí)際工程上的原因,有必要定期停止正在運(yùn)行的應(yīng)用程序,以便可以管理內(nèi)存。 根據(jù)算法的不同,不同的收集器將在特定的執(zhí)行點(diǎn)停下世界,并持續(xù)不同的時(shí)間。 要使應(yīng)用程序完全停止,必須暫停所有正在運(yùn)行的線程。 垃圾收集器通過發(fā)信號(hào)通知線程在到達(dá)“ 安全點(diǎn) ”時(shí)停止運(yùn)行來做到這一點(diǎn),這是程序執(zhí)行期間所有GC根已知且所有堆對(duì)象內(nèi)容一致的點(diǎn)。 根據(jù)線程在做什么,可能需要一些時(shí)間才能達(dá)到安全點(diǎn)。 安全點(diǎn)檢查通常在方法返回和回送邊沿上執(zhí)行,但可以在某些地方進(jìn)行優(yōu)化,從而使其在動(dòng)態(tài)上更加罕見。 例如,如果線程正在復(fù)制大型數(shù)組,克隆大型對(duì)象或執(zhí)行具有有限界限的單調(diào)計(jì)數(shù)循環(huán),則到達(dá)安全點(diǎn)可能要花費(fèi)幾毫秒的時(shí)間。 安全時(shí)間(TTS)是低延遲應(yīng)用程序中的重要考慮因素。 通過啟用?XX:+ PrintGCApplicationStoppedTime標(biāo)志以及其他GC標(biāo)志,可以浮出水面。
注意:對(duì)于具有大量正在運(yùn)行的線程的應(yīng)用程序,當(dāng)世界停止事件發(fā)生時(shí),隨著線程在釋放后恢復(fù),系統(tǒng)將承受重大的調(diào)度壓力。 因此,較少依賴于世界停止事件的算法可能會(huì)更有效。
熱點(diǎn)堆組織
要了解不同收集器的工作方式,最好探索如何組織Java堆以支持分代收集器。
伊甸園是最初分配大多數(shù)對(duì)象的區(qū)域。 幸存者空間是一個(gè)臨時(shí)存儲(chǔ)區(qū),用于存儲(chǔ)在伊甸園空間中幸存的對(duì)象。 討論次要收藏時(shí),將描述幸存者空間的使用情況。 伊甸園和幸存者空間統(tǒng)稱為“年輕”或“新生代”。
壽命足夠長(zhǎng)的對(duì)象最終將提升為使用期限 。
燙發(fā)生成是運(yùn)行時(shí)將其“知道”為有效的對(duì)象(例如類和靜態(tài)字符串)存儲(chǔ)的地方。 不幸的是,在許多應(yīng)用程序中持續(xù)使用類加載的常見用法使?fàn)C發(fā)生成背后的動(dòng)機(jī)假設(shè)錯(cuò)誤,即類是不朽的。 在Java 7中,已將字符串從permgen轉(zhuǎn)移到Tenured ,而從Java 8中不再存在perm的生成,因此本文將不進(jìn)行討論。 大多數(shù)其他商業(yè)收藏家并不使用單獨(dú)的燙發(fā)空間,而是傾向于將所有長(zhǎng)期存在的物品視為永久使用。
注意:虛擬空間允許收集器調(diào)整區(qū)域的大小,以滿足吞吐量和延遲目標(biāo)。 收集器會(huì)保留每個(gè)收集階段的統(tǒng)計(jì)信息,并相應(yīng)地調(diào)整區(qū)域大小,以達(dá)到目標(biāo)。
對(duì)象分配
為了避免爭(zhēng)用,每個(gè)線程都分配有一個(gè)線程本地分配緩沖區(qū)(TLAB),從該線程中分配對(duì)象。 使用TLAB可以避免對(duì)象在單個(gè)內(nèi)存資源上的爭(zhēng)用,從而使對(duì)象分配隨線程數(shù)擴(kuò)展。 通過TLAB分配對(duì)象是非常便宜的操作; 它只是碰觸對(duì)象大小的指針,在大多數(shù)平臺(tái)上大約需要10條指令。 Java的堆內(nèi)存分配比從C運(yùn)行時(shí)使用malloc還要便宜。
注意:盡管單個(gè)對(duì)象分配非常便宜,但必須進(jìn)行次要收集的速率與對(duì)象分配的速率成正比。
當(dāng)TLAB耗盡時(shí),一個(gè)線程只需向Eden空間請(qǐng)求一個(gè)新線程。 當(dāng)伊甸園裝滿后,便開始小規(guī)模收集。
大對(duì)象(-XX:PretenureSizeThreshold = <n>)可能無法容納在年輕的一代中,因此必須在舊的一代中進(jìn)行分配,例如大型數(shù)組。 如果將閾值設(shè)置為低于TLAB大小,則不會(huì)在舊版本中創(chuàng)建適合TLAB的對(duì)象。 新的G1收集器以不同的方式處理大型物體,稍后將在其單獨(dú)的部分中進(jìn)行討論。
小型收藏
當(dāng)伊甸園變滿時(shí),將觸發(fā)次要回收。 這是通過將新一代的所有活動(dòng)對(duì)象適當(dāng)?shù)貜?fù)制到幸存者空間或保有權(quán)空間來完成的。 復(fù)制到使用權(quán)空間稱為升級(jí)或使用權(quán)。 對(duì)于足夠舊的對(duì)象(– XX:MaxTenuringThreshold = <n>),或幸存者空間溢出時(shí),將進(jìn)行升級(jí)。
活動(dòng)對(duì)象是應(yīng)用程序可訪問的對(duì)象。 任何其他物體均無法到達(dá),因此可以視為已死亡。 在次要集合中,首先通過遵循所謂的GC根目錄執(zhí)行活動(dòng)對(duì)象的復(fù)制,然后反復(fù)復(fù)制可到達(dá)生存空間的任何對(duì)象。 GC根通常包括來自應(yīng)用程序和JVM內(nèi)部靜態(tài)字段以及線程堆棧框架的引用,所有這些引用均有效指向應(yīng)用程序的可訪問對(duì)象圖。
在世代集合中,新一代可訪問對(duì)象圖的GC根目錄還包括從舊一代到新一代的所有引用。 還必須對(duì)這些引用進(jìn)行處理,以確保新一代中的所有可訪問對(duì)象在次要集合中都不會(huì)丟失。 通過使用“ 卡片表 ”來識(shí)別這些跨代參考。 熱點(diǎn)卡表是一個(gè)字節(jié)數(shù)組,其中每個(gè)字節(jié)用于跟蹤舊一代的相應(yīng)512字節(jié)區(qū)域中跨代引用的潛在存在。 在將引用存儲(chǔ)到堆時(shí),“存儲(chǔ)屏障”代碼將標(biāo)記卡,以指示從舊一代到新一代的潛在引用可能存在于關(guān)聯(lián)的512字節(jié)堆區(qū)域中。 在收集時(shí),卡片表用于掃描此類跨代引用,這些引用有效地代表了新一代的其他GC根。 因此,次要藏品的重大固定成本與上一代的大小成正比。
新一代Hotspot中有兩個(gè)幸存者空間,它們的“ 到太空 ”和“ 從太空 ”角色交替出現(xiàn)。 在次要收集開始時(shí),到太空幸存者空間始終為空,并充當(dāng)次要收集的目標(biāo)副本區(qū)域。 先前的次要收藏的目標(biāo)幸存者空間是起始空間的一部分,起始空間還包括伊甸園,在伊甸園中可以找到需要復(fù)制的活動(dòng)對(duì)象。
少量GC收集的成本通常由將對(duì)象復(fù)制到幸存者和保有權(quán)空間的成本決定。 不能幸免的對(duì)象可以有效地自由處理。 在次要收藏期間完成的工作與發(fā)現(xiàn)的活動(dòng)對(duì)象的數(shù)量成正比,而不與新一代的大小成正比。 每次將伊甸園面積擴(kuò)大一倍時(shí),花在次要收藏上的總時(shí)間幾乎可以減少一半。 因此可以將內(nèi)存用于吞吐量。 將Eden大小增加一倍會(huì)導(dǎo)致每個(gè)收集周期的收集時(shí)間增加,但是如果要提升的對(duì)象數(shù)和舊一代的大小都恒定,則這相對(duì)較小。
注意:在熱點(diǎn)中,次要收藏是世界停止事件。 隨著越來越多的活動(dòng)對(duì)象堆越來越大,這正Swift成為一個(gè)主要問題。 我們已經(jīng)開始看到需要同時(shí)收集年輕一代以達(dá)到暫停時(shí)間目標(biāo)的需求。
主要收藏
主要藏品收集了老一代,以便可以從年輕一代中推廣物品。 在大多數(shù)應(yīng)用程序中,絕大多數(shù)程序狀態(tài)最終出現(xiàn)在老一代。 對(duì)于前代來說,存在種類最多的GC算法。 有些會(huì)在填滿時(shí)壓縮整個(gè)空間,而另一些會(huì)與應(yīng)用程序同時(shí)收集以防止填滿。
老一代的收藏家將嘗試預(yù)測(cè)何時(shí)需要收藏,以避免年輕一代的晉升失敗。 收集器跟蹤舊一代的填充閾值,并在超過該閾值時(shí)開始收集。 如果該閾值不足以滿足促銷要求,那么將觸發(fā)“ FullGC ”。 FullGC涉及推廣年輕一代的所有活動(dòng)對(duì)象,然后收集和壓縮舊一代。 升級(jí)失敗是一項(xiàng)非常昂貴的操作,因?yàn)楸仨毥忾_此循環(huán)中的狀態(tài)和升級(jí)對(duì)象,以便發(fā)生FullGC事件。
注意:為避免升級(jí)失敗,您將需要調(diào)整舊版本允許容納升級(jí)的填充(?XX:PromotedPadding = <n>)。
注意:當(dāng)堆需要增長(zhǎng)時(shí),會(huì)觸發(fā)FullGC。 通過將–Xms和–Xmx設(shè)置為相同的值,可以避免這些調(diào)整堆大小的FullGC。
除了FullGC,舊版本的壓縮很可能是應(yīng)用程序?qū)⒂龅降淖畲蟮耐nD停頓狀態(tài)。 壓縮的時(shí)間往往會(huì)隨著使用權(quán)空間中活動(dòng)對(duì)象的數(shù)量線性增長(zhǎng)。
有時(shí)可以通過增加幸存者空間的大小和對(duì)象的年齡來降低占位空間的填充率,然后再提升其為占位空間。 但是,在促銷之前增加次要收藏中幸存者空間的大小和對(duì)象年齡(–XX:MaxTenuringThreshold = <n>)也會(huì)增加次要收藏物中的成本和暫停時(shí)間,這是由于未成年收藏者之間的生存空間之間的復(fù)制成本增加了集合。
串行收集器
串行收集器(-XX:+ UseSerialGC)是最簡(jiǎn)單的收集器,是單處理器系統(tǒng)的不錯(cuò)選擇。 它還具有所有收集器中最小的占地面積。 它對(duì)次要和主要集合都使用一個(gè)線程。 使用簡(jiǎn)單的凹凸指針?biāo)惴ㄔ诔志每臻g中分配對(duì)象。 當(dāng)使用權(quán)空間已滿時(shí),將觸發(fā)主要集合。
并聯(lián)收集器
并行收集器有兩種形式。 并行收集器 (?XX:+ UseParallelGC),它使用多個(gè)線程來執(zhí)行年輕代的次要收集,并使用單個(gè)線程來執(zhí)行舊代的主要收集。 自Java 7u4起默認(rèn)的Parallel Old收集器 (?XX:+ UseParallelOldGC)使用多個(gè)線程進(jìn)行次要收集,并使用多個(gè)線程進(jìn)行主要收集。 使用簡(jiǎn)單的凹凸指針?biāo)惴ㄔ诔志每臻g中分配對(duì)象。 當(dāng)使用權(quán)空間已滿時(shí),將觸發(fā)主要集合。
在多處理器系統(tǒng)上,并行舊收集器將提供所有收集器中最大的吞吐量。 直到發(fā)生收集為止,它對(duì)正在運(yùn)行的應(yīng)用程序沒有影響,然后將使用最有效的算法使用多個(gè)線程并行收集。 這使得Parallel Old Collector非常適合批處理應(yīng)用。
收集舊版本的成本受要保留的對(duì)象數(shù)量的影響要比與堆大小的影響更大。 因此,可以通過提供更多內(nèi)存并接受較大但較少的收集暫停來提高Parallel Old收集器的效率,以實(shí)現(xiàn)更大的吞吐量。
期望使用該收集器獲得最快的次要收集,因?yàn)樯?jí)到保有空間僅是指針和復(fù)制操作的簡(jiǎn)單顛簸。
對(duì)于服務(wù)器應(yīng)用程序,Parallel Old收集器應(yīng)該是第一個(gè)調(diào)用端口。 但是,如果主要的收集暫停時(shí)間超出了您的應(yīng)用程序所能承受的范圍,則您需要考慮使用并發(fā)收集器,該并發(fā)收集器在應(yīng)用程序運(yùn)行時(shí)同時(shí)收集歷時(shí)對(duì)象。
注意:在壓縮舊版本的同時(shí),現(xiàn)代硬件上每GB實(shí)時(shí)數(shù)據(jù)的暫停時(shí)間大約為1到5秒。
注意:通過為多插槽CPU服務(wù)器應(yīng)用程序的-XX:+ UseNUMA分配并行的收集器,有時(shí)可以為CPU套接字本地的線程分配Eden內(nèi)存,從而獲得性能優(yōu)勢(shì)。 遺憾的是,該功能對(duì)其他收集器不可用。
并發(fā)標(biāo)記掃描(CMS)收集器
CMS(-XX:+ UseConcMarkSweepGC)收集器在舊版本中運(yùn)行,以收集在大型收集期間不再可訪問的終身對(duì)象。 它與應(yīng)用程序同時(shí)運(yùn)行,目的是在老一代中保留足夠的可用空間,從而不會(huì)發(fā)生年輕一代的升級(jí)失敗。
升級(jí)失敗將觸發(fā)FullGC。 CMS遵循多個(gè)步驟:
當(dāng)租用對(duì)象變得不可訪問時(shí),CMS將回收該空間并將其放入空閑列表。 進(jìn)行促銷時(shí),必須在自由列表中搜索要促銷的對(duì)象的合適大小的Kong。 與Parallel Collector相比,這增加了推廣成本,從而增加了Minor收藏的成本。
注意 :CMS不是壓縮收集器,隨著時(shí)間的推移,它可能導(dǎo)致舊的碎片化。 對(duì)象升級(jí)可能會(huì)失敗,因?yàn)榇笮蛯?duì)象可能不適合舊版本中的可用Kong。 發(fā)生這種情況時(shí),將記錄“ 升級(jí)失敗 ”消息,并觸發(fā)FullGC壓縮活動(dòng)的使用權(quán)對(duì)象。 對(duì)于此類壓縮驅(qū)動(dòng)的FullGC,由于CMS僅使用單個(gè)線程進(jìn)行壓縮,因此期望的延遲比使用Parallel Old收集器的主要收集更糟糕。
CMS通常與應(yīng)用程序并發(fā),這具有許多含義。 首先,CPU時(shí)間由收集器占用,從而減少了可用于應(yīng)用程序的CPU。 CMS所需的時(shí)間與將對(duì)象提升到保有空間的數(shù)量成線性增長(zhǎng)。 其次,對(duì)于并發(fā)GC周期的某些階段,必須將所有應(yīng)用程序線程帶入一個(gè)安全點(diǎn),以標(biāo)記GC根并執(zhí)行并行重新標(biāo)記以檢查變異。
注意 :如果應(yīng)用程序發(fā)現(xiàn)使用權(quán)對(duì)象發(fā)生了重大變化,則重新標(biāo)記階段可能很重要,在極端情況下,重新標(biāo)記階段可能比使用Parallel Old Collector進(jìn)行完全壓縮要花費(fèi)更長(zhǎng)的時(shí)間。
CMS使FullGC成為不太頻繁的事件,但代價(jià)是吞吐量降低,更昂貴的次要收集和更大的占用空間。 與并行收集器相比,吞吐量的降低幅度可能在10%-40%之間,具體取決于提升率。 CMS還需要占用20%的空間,以容納其他數(shù)據(jù)結(jié)構(gòu)和“浮動(dòng)垃圾”,這些并發(fā)標(biāo)記在傳遞到下一個(gè)周期的并發(fā)標(biāo)記期間可能會(huì)丟失。
有時(shí)可以通過增加年輕一代空間和老一代空間的大小來降低高晉升率和由此造成的分裂。
注意 :如果CMS收集速度不足以跟上升級(jí)的速度,則CMS可能會(huì)遇到“ 并發(fā)模式故障 ”,這可以在日志中看到。 當(dāng)收集開始太晚時(shí)可能會(huì)導(dǎo)致這種情況,有時(shí)可以通過調(diào)整來解決。 但是,當(dāng)收集率無法跟上某些應(yīng)用程序的高推廣率或高對(duì)象突變率時(shí),也會(huì)發(fā)生這種情況。 如果應(yīng)用程序的提升率或變異率太高,則您的應(yīng)用程序可能需要進(jìn)行一些更改以減輕提升壓力。 向這樣的系統(tǒng)添加更多的內(nèi)存有時(shí)會(huì)使情況變得更糟,因?yàn)镃MS將需要更多的內(nèi)存來進(jìn)行掃描。
垃圾優(yōu)先(G1)收集器
G1(-XX:+ UseG1GC)是Java 6中引入的新收集器,現(xiàn)已從Java 7u4開始正式支持。 這是一種部分并發(fā)的收集算法,該算法還嘗試在較小的增量“停止世界”停頓中壓縮占位空間,以盡量減少由于碎片而困擾CMS的FullGC事件。 G1是一個(gè)世代收集器,通過將其劃分為大量(?2000個(gè))可變大小的固定大小區(qū)域(而不是出于相同目的的連續(xù)區(qū)域)來與其他收集器進(jìn)行不同的組織。
G1采用同時(shí)標(biāo)記區(qū)域的方法來跟蹤區(qū)域之間的引用,并將收集集中在具有最大可用空間的區(qū)域上。 然后,通過將活動(dòng)對(duì)象疏散到一個(gè)空的區(qū)域,以停下來的暫停增量收集這些區(qū)域,從而在此過程中進(jìn)行壓縮。 一個(gè)循環(huán)中要收集的區(qū)域稱為收集集 。
大于某個(gè)區(qū)域50%的對(duì)象被分配在多個(gè)區(qū)域中的大型區(qū)域中。 在G1下,大型對(duì)象的分配和收集可能會(huì)非常昂貴,并且迄今為止幾乎沒有或沒有進(jìn)行任何優(yōu)化工作。
任何壓縮收集器所面臨的挑戰(zhàn)不是對(duì)象的移動(dòng),而是對(duì)這些對(duì)象的引用的更新。 如果從許多區(qū)域引用了一個(gè)對(duì)象,則更新這些引用所花費(fèi)的時(shí)間可能比移動(dòng)該對(duì)象要長(zhǎng)得多。 G1通過“ 記住的集合 ”跟蹤區(qū)域中的哪些對(duì)象具有其他區(qū)域的引用。 記住集是標(biāo)記為突變的牌的集合。 如果“記住的集合”變大,則G1會(huì)顯著降低速度。 當(dāng)將對(duì)象從一個(gè)區(qū)域撤離到另一個(gè)區(qū)域時(shí),相關(guān)的世界停止事件的時(shí)間長(zhǎng)度往往與需要掃描并可能需要打補(bǔ)丁的參考區(qū)域的數(shù)量成正比。
維護(hù)“已記住的集合”會(huì)增加次要集合的成本,從而導(dǎo)致停頓的時(shí)間要長(zhǎng)于Parallel Old或CMS的次要集合。
G1是目標(biāo)驅(qū)動(dòng)程序,其時(shí)延為–XX:MaxGCPauseMillis = <n>,默認(rèn)值= 200ms。 該目標(biāo)將盡力而為地影響每個(gè)周期的工作量。 在幾十毫秒內(nèi)設(shè)置目標(biāo)通常是徒勞的,而在撰寫本文時(shí),針對(duì)數(shù)十毫秒的目標(biāo)還不是G1的重點(diǎn)。
對(duì)于較大的堆,G1是一個(gè)很好的通用收集器,當(dāng)應(yīng)用程序可以容忍0.5-1.0秒范圍內(nèi)的增量壓縮暫停時(shí),G1往往會(huì)變得碎片化。 G1傾向于減少CMS看到的最壞情況的停頓的頻率,這是因?yàn)樗槠拇鷥r(jià)是擴(kuò)展了次要收集的范圍和老一代的增量壓縮。 大多數(shù)暫停最終都局限于區(qū)域壓縮,而不是全部堆壓縮。
像CMS一樣,G1也可能無法跟上晉升率,并且會(huì)退回到世界末日的FullGC。 就像CMS具有“ 并發(fā)模式故障 ”一樣,G1可能會(huì)發(fā)生疏散故障,在日志中被視為“ 空間溢出 ”。 當(dāng)沒有空閑區(qū)域可將對(duì)象撤離時(shí),就會(huì)發(fā)生這種情況,這類似于升級(jí)失敗。 如果發(fā)生這種情況,請(qǐng)嘗試使用更大的堆和更多的標(biāo)記線程,但是在某些情況下,可能需要更改應(yīng)用程序以降低分配率。
對(duì)于G1來說,一個(gè)具有挑戰(zhàn)性的問題是處理受歡迎的物體和區(qū)域。 當(dāng)區(qū)域中的活動(dòng)對(duì)象沒有從其他區(qū)域大量引用時(shí),增量停止世界壓縮將非常有效。 如果某個(gè)對(duì)象或區(qū)域很受歡迎,則“記住的集合”將很大,G1將嘗試避免收集這些對(duì)象。 最終,它別無選擇,這會(huì)導(dǎo)致堆壓縮時(shí)非常頻繁的中長(zhǎng)度暫停。
替代并行收集器
CMS和G1通常被稱為并發(fā)收集器。 當(dāng)您查看所執(zhí)行的全部工作時(shí),很顯然,年輕一代,晉升甚至許多老一代工作根本不是同時(shí)發(fā)生的。 CMS在大多數(shù)情況下是并發(fā)的。 G1更像是一個(gè)停滯不前的增量收集器。 CMS和G1都有重大且定期發(fā)生的世界停止事件,以及最壞的情況,通常使它們不適用于嚴(yán)格的低延遲應(yīng)用程序,例如金融交易或React性用戶界面。
可以使用其他收集器,例如Oracle JRockit Real Time,IBM Websphere Real Time和Azul Zing。 JRockit和Websphere收集器在大多數(shù)情況下都比CMS和G1具有延遲優(yōu)勢(shì),但是經(jīng)常遇到吞吐量限制,并且仍然遭受重大的世界停止事件。 Zing是該作者所知的唯一Java收集器,它可以真正地并發(fā)進(jìn)行收集和壓縮,同時(shí)保持所有代的高吞吐率。 Zing確實(shí)有一些毫秒級(jí)的世界停止事件,但這些事件是與收集周期中的相移有關(guān)的,這些相移與活動(dòng)對(duì)象集的大小無關(guān)。
對(duì)于在包含的堆大小下的高分配率而言,JRockit RT可以實(shí)現(xiàn)數(shù)十毫秒的典型暫停時(shí)間,但有時(shí)還必須恢復(fù)到完全壓縮暫停。 Websphere RT可以通過受限制的分配速率和活動(dòng)集大小來實(shí)現(xiàn)單位毫秒的暫停時(shí)間。 通過在所有階段(包括次要收集期間)并發(fā)執(zhí)行,Zing可以以高分配率實(shí)現(xiàn)亞毫秒級(jí)的暫停。 無論堆大小如何,Zing都可以保持這種一致的行為,從而使用戶可以根據(jù)需要應(yīng)用大堆大小,以適應(yīng)應(yīng)用程序吞吐量或?qū)ο竽P蜖顟B(tài)需求,而不必?fù)?dān)心增加暫停時(shí)間。
對(duì)于所有針對(duì)延遲的并發(fā)收集器,您必須放棄一些吞吐量并增加占用空間。 根據(jù)并發(fā)收集器的效率,您可能會(huì)放棄一點(diǎn)吞吐量,但是始終會(huì)增加大量占用空間。 如果是真正的并發(fā),幾乎沒有停滯事件,則需要更多的CPU內(nèi)核來啟用并發(fā)操作并保持吞吐量。
注意:分配足夠的空間后,所有并發(fā)收集器往往會(huì)更有效地發(fā)揮作用。 作為經(jīng)驗(yàn)法則,您應(yīng)該將堆的預(yù)算至少為活動(dòng)集大小的2到3倍,以實(shí)現(xiàn)高效操作。 但是,用于維持并發(fā)操作的空間需求隨應(yīng)用程序吞吐量以及相關(guān)的分配和提升率而增長(zhǎng)。 因此,對(duì)于更高吞吐量的應(yīng)用程序,可以保證更高的堆大小與活動(dòng)集比率。 鑒于當(dāng)今系統(tǒng)可用的巨大內(nèi)存空間,在服務(wù)器端很少出現(xiàn)問題。
垃圾收集監(jiān)控和調(diào)整
要了解您的應(yīng)用程序和垃圾收集器的行為方式,請(qǐng)至少使用以下設(shè)置啟動(dòng)JVM:
-verbose:gc -Xloggc: -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime然后將日志加載到Chewiebug之類的工具中進(jìn)行分析。
要查看GC的動(dòng)態(tài)性質(zhì),請(qǐng)啟動(dòng)JVisualVM并安裝Visual GC插件。 這將使您能夠如下所示查看適用于您的應(yīng)用程序的GC。
為了了解您的應(yīng)用程序的GC需求,您需要可以重復(fù)執(zhí)行的代表性負(fù)載測(cè)試。 當(dāng)您掌握每個(gè)收集器的工作方式時(shí),然后以不同的配置運(yùn)行負(fù)載測(cè)試作為實(shí)驗(yàn),直到達(dá)到吞吐量和延遲目標(biāo)。 從最終用戶的角度衡量延遲很重要。 這可以通過在直方圖中捕獲每個(gè)測(cè)試請(qǐng)求的響應(yīng)時(shí)間來實(shí)現(xiàn),您可以在此處了解更多信息。 如果您的延遲峰值超出可接受范圍,請(qǐng)嘗試將其與GC日志關(guān)聯(lián),以確定是否是GC問題。 其他問題可能會(huì)導(dǎo)致延遲峰值。 另一個(gè)值得考慮的有用工具是jHiccup ,它可用于跟蹤JVM中以及整個(gè)系統(tǒng)中的暫停。 用jHiccup測(cè)量您的空閑系統(tǒng)幾個(gè)小時(shí),您通常會(huì)感到非常驚訝。
如果延遲高峰是由于GC引起的,則投資調(diào)整CMS或G1以查看您的延遲目標(biāo)是否可以實(shí)現(xiàn)。 有時(shí),這可能是由于高分配率和提升率以及低延遲要求而無法實(shí)現(xiàn)的。 GC調(diào)整可以成為一項(xiàng)高技能的練習(xí),通常需要更改應(yīng)用程序以減少對(duì)象分配率或?qū)ο髩勖?如果是這種情況,則可能需要在時(shí)間和花費(fèi)在GC調(diào)整和應(yīng)用程序更改上的資源之間進(jìn)行商業(yè)平衡,例如,可能需要購(gòu)買商業(yè)并發(fā)壓縮JVM中的一種,例如JRockit Real Time或Azul Zing。
翻譯自: https://www.javacodegeeks.com/2013/07/java-garbage-collection-distilled.html
知識(shí)蒸餾 循環(huán)蒸餾
總結(jié)
以上是生活随笔為你收集整理的知识蒸馏 循环蒸馏_Java垃圾收集蒸馏的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问界新M7单日大定突破2000台 这波华
- 下一篇: 英特尔酷睿 i9-14900KF 处理器