⑴ 船上的垃圾分類和處理
船上的垃圾分類和處理,國家環保局也有相應的規定和要求,按照要求對垃圾進行分類,也是對於環境保護做出的貢獻。
⑵ C#中關於垃圾收集器的問題
該對象已經被析構。
ObjectReliveTest.Program.relived = this; //復活
執行了對象復甦。但是,該對象已經被執行了終止化,使用他可能會導致不可預期的結果。(參考《.net框架程序設計》,p489)
如果被析構,那麼,為什麼CLR允許無意義的對象調用方法,而不是拋出異常?
既然是「不可預期的結果」,所以必須通過大量實驗來進行觀察。你可以多測試幾次,你會發現某幾次clr會拋出nullreference異常。
----------------------------------------------------------
~Reliver()
{
Console.WriteLine("正在析構復活類");
ObjectReliveTest.Program.relived = this; //復活
}
當對象的析構函數(finalize方法)被執行時,該對象的一個引用被放入了一個根中,從而使其又成為應用程序得一個可達對象。對象重新復甦以後,垃圾收集器不會再將其認為是可收集的對象,應用程序也可以自由的使用該對象。但是,該對象已經被執行了終止化,使用他可能會導致不可預期的結果。
換句話說,在對象執行了finalize方法以後,它的成員仍然可能被訪問。
因此應該這樣回答你這個問題:對象確實已經被析構,但是clr執行的這個析構,不是將對象從內存中抹掉,而是對它進行了特殊的標記,以便於在某一代齡的內存空間不足時流出空間,在此之前,應用程序仍然能夠訪問這個對象。 從這里也可以看出.net語言和c++的一些區別。
⑶ 5、垃圾回收機制
JVM的垃圾回收機制主要涉及三個方面的問題:
1.JVM有哪些垃圾回收演算法?各自有什麼優勢?
2.CMS垃圾回收器是如何工作的?有哪些階段?
3.服務卡頓的元兇到底是什麼?
Java不用程序來管理內存的回收,但這些內存是如何回收的?
其實,JVM有專門的線程在做這件事情。當內容空間達到一定條件時,會自動觸發,這個過程就叫GC,負責GC的組件被稱為垃圾回收器。JVM規范沒有規定垃圾回收器怎麼實現,它只需要保證不要把正在使用的對象回收掉就可以。在現在的伺服器環境中,經常被使用的垃圾回收器有CMS和G1,但JVM還有其它幾個常見的垃圾回收器。
GC的過程是先找到活躍的對象,然後把其他不活躍的對象判定為垃圾,然後刪除,所以GC只與活躍的對象有關,和堆的大小無關。
接下來學習下分代垃圾回收的內存劃分和GC過程,再有就是常見的垃圾回收器。
這篇比較重要,因為幾乎所有的垃圾回收器都是在這些基本思想上演化出來的。
GC的第一步就是找出活躍的對象,根據GC Roots遍歷所有的可達對象,這個過程就叫作標記。
如上圖所示,圓圈代表對象,綠色的代表GC Roots,紅色的代表可以追溯到的對象,標記後,有多個灰色的圓圈,代表都是可被回收的對象。
清除階段就是把未被標記的對象回收掉。
這種方式有一個明顯的問題,會產生碎片空間。
比如申請了1k、2k、3k、4k、5k的內存
由於某些原因,2k和4k的內存不再使用,交給垃圾回收器回收。
解決碎片問題,就需要進行內存整理。
有一個思路就是提送一個對等的內存空間,將存活的對象復制過去,然後清除員內存空間。
在程序設計時,一般遇到擴縮容或者碎片整理問題時,復制演算法都是非常有效的。比如:HashMap的擴容使用的是同樣的思路,Redis的rehash也是如此。
整個過程如下圖
這種方式看似完美,解決了碎片問題,但是弊端也非常明顯,它浪費了一半的內存空間來做這個事情,如果原本資源就有限,這就是一種無法容忍的浪費。
不用分配一個對等的空間也是可以完成內存的整理工作。
可以把內存想像成一個非常大的數組,根據隨機的index刪除了一些數據,那麼對數組的清理不需要另外一個數組來進行支持的,使用程序就可以。
主要思路是移動所有的存活對象,且按照內存地址順序依次排列,然後將末端內存地址以後的內存全部收回。
對象的引用關系一般是非常復雜的,從效率上來說,一般整理演算法是要低於復制演算法的。
JVM的垃圾回收器,都是對以上幾種樸素演算法的結合使用,簡單看一下它們的特點:
效率一般,缺點是回造成內存碎片的問題。
復制演算法是所有演算法裡面效率最高的,缺點是造成一定的空間浪費。
效率比前兩者要差,但沒有空間浪費,也消除了內存碎片問題。
所以沒有最優的演算法,只有最合適的演算法。
JVM是計算節點,而不是存儲節點。最理想的情況就是對象使用完成之後,它的生命周期立馬就結束了,而那些被頻繁訪問的資源,我們希望它能夠常駐在內存里。
對象大致可以分為兩類:
1.大部分對象的生命周期都很短
2.其他對象則很可能會存活很長時間
現在的垃圾回收器都會在物理上或者邏輯上,把這兩類對象進行分區。我們把死的快的對象所佔的區域叫年輕代(Young Generation)。把其他活的長的對象所佔的區域叫作老年代(Old Generation),老年代在有時候會叫作Tenured Generation。
年輕代使用的垃圾回收演算法是復制演算法,因為年輕代發生GC後,會有非常少的對象存活,復制這部分對象是非常高效的
年輕代的內部分區
如圖所示,年輕代分為:一個伊甸園空間(Eden),兩個倖存者空間(Survivor)。
當年輕代中的Eden區分配滿的時候,就會觸發年輕代的GC(Minor GC),具體過程如下
1.在Eden區執行了第一次GC之後,存活的對象會被移動到其中一個Suvivor分區(from);
2.Eden區再次GC,這是會採用復制演算法,將Eden和from區一起清理,存活的對象會被復制到to區;接下來只需要清空from區就可以了
在整個過程中總會有一個Survivor分區是空置的。Eden、from、to的默認比例是8:1:1,所以只會造成10%的空間浪費。
這個比例是由參數-XX:SurvivorRatio進行配置的(默認為8)。
補充下不常提到的TLAB。TLAB全稱是Thread Local Allocation Buffer,JVM默認給每個線程開辟一個buffer區域,用來加速對象分配。這個buffer就放在Eden區中。
這個道理和Java語言中的ThreadLocal類似,避免了對公共區的操作,以及一些鎖競爭。
老年代一般使用"標記-清除"、"標記-整理"演算法。因為老年代的對象存活率一般是比較高的,空間又比較大,拷貝起來並不劃算,不如採取就地收集的方式。
對象進入老年代的途徑分類
如果對象夠老,會通過"提升"進入老年代。關於對象老不老,是通過它的年齡來判斷的。每發生一次Minor GC,存活下來的對象年齡都會加1,直到達到一定的閥值,就會提升到老年代,
這些對象如果變的不可達,直到老年代發生GC的時候才會被清理掉。
這個閥值可以通過參數 -XX:+MaxTenuringThreshold進行配置,最大值是15,因為它是用4bit存儲的(所以把這個值調的很大的文章,是沒有什麼根據的)。
每次存活的對象,都會放入其中一個倖存區,這個區域默認比例是10%,但無法保證每次存活的對象都小於10%,當Survivor空間不夠,就需要依賴其它內存(老年代)進行分配擔保。這個時候,對象也會直接在老年代上分配。
超出某個大小的對象直接在老年代分配,通過參數設置-XX:PretenureSizeThreshold進行配置的,默認為0,默認全部在Eden區進行分配。
有的垃圾回收演算法,並不要求age必須達到15才能晉升到老年代,它會使用一些動態的計算方法。比如,如果倖存區中相同年齡對象大小的和,大於倖存區的一半,大於或者等於age的對象將會直接進入老年代。
這些動態判定一半不受外部控制
對象的引用關系時一個巨大的網狀,有的對象在Eden區,有的可能在老年代,那麼這種跨代的引用是如何處理的呢?由於Minor GC是單獨發生的,如果一個老年代的對象引用了它,如何確保能夠讓年輕代的對象存活呢?
對於是、否的判斷,我們通常都會用到Bitmap(點陣圖)和布隆過濾器來加快搜索的速度,需要另外再學習下(如果不知道這兩個概念的話)
JVM也是用了類似的方法。其實,老年代是被分成眾多的卡頁(Card Page)的(一般數量是2的次冪)
卡表(Card Table)就是用於標記卡頁狀態的一個集合,每個卡表對應一個卡頁。
如果年輕代有對象分配,而且老年代有對象指向這個新對象,那麼這個老年代對象所對應內存的卡頁就會被標識為dirty,卡表只需要非常小的存儲空間就可以保留這些狀態,垃圾回收時,就可以先讀這個卡表,進行快速的判斷。
接下來學習HotSpot的幾個垃圾回收器,每種回收器都有各自的特點。在平常的GC優化時,一定要清楚現在用的是那種垃圾回收器。
下圖包含了年輕代和老年代的劃分,方便接下來的學習參考
處理GC的只有一條線程,並且在垃圾回收的過程中暫停一切用戶線程。
這是最簡單的垃圾回收器,雖然簡單,但十分高效,通常用在客戶端應用上。因為客戶端應用不會頻繁創建很多對象,用戶也不會感覺出明顯的卡頓。相反,它使用的資源更少,也更輕量級。
ParNew是Serial的多線程版本,由多條GC線程並行地進行垃圾清理。清理過程依然要停止用戶線程。追求低停頓時間,與Serial唯一區別就是使用了多線程進行垃圾回收,在多CPU環境下性能比Serial會有一定程度的提升;但線程切換需要額外的開銷,因此在單CPU環境中表現不如Serial。
另一個多線程版本的垃圾回收器。但與ParNew是有區別的
1.Parallel Scavenge:追求CPU吞吐量,能夠在較短時間內完成指定任務,適合沒有交互的後台計算,弱交互強計算。
2.ParNew:追求降低用戶停頓時間,適合互動式應用,強交互弱計算。
與年輕代的Serial垃圾回收器對應,都是單線程版本,同樣適合客戶端使用。
年輕代Serial,使用復制演算法。
老年代的Old Serial,使用標記-整理演算法。
Parallel Old回收器是Parallel Scavenge 的老年代版本,追求CPU吞吐量。
CMS(Concurrent Mark Sweep)回收器是以獲取最短GC停頓時間為目標的收集器,它在垃圾回收時使得用戶線程和GC線程能夠並發執行,因此在垃圾回收過程中用戶也不會感到明顯的卡頓。
長期看來,CMS垃圾回收器,是要被G1等垃圾回收器替換掉的,在Java8之後,使用它將會拋出一個警告!
除了上面幾個垃圾回收器,我們還有G1、ZGC等更加高級的垃圾回收器,它們都有專門的配置參數來使其生效。
通過-XX:PrintCommandLineFlags參數,可以查看當前Java版本默認使用的垃圾回收器。在Java13中,默認的回收器就是G1。
以下是一些配置參數:
1.-XX:+UseSerialGC 年輕代和年老代回收器
2.-XX:+UseParNewGC 年輕代使用ParNew,老年代使用Serial Old。
3.-XX:+UseParallelOldGC 年輕代和老年代哦都市用並行回收器。
4.-XX:+UseConcMarkSweepGC 表示年輕代使用ParNew,老年代使用CMS。
5.-XX:+UseG1GC 使用G1垃圾回收器
6.-XX:+UseZGC 使用ZGC垃圾回收器
這些垃圾回收器的關系還是比較復雜的,請看下圖
目前Java8還是主流使用版本,從Java8升級到高版本的Java體系是有一定成本的,所以CMS垃圾回收器還會持續一段時間
拋個問題,如果在垃圾回收的時候,又有新的對象進入怎麼辦?
為了保住程序不亂套,最好的辦法就是暫停用戶的一切線程,也就是在這段時間,是不能new對象的,只能等待,表象是在JVM上就是短暫的卡頓,什麼都幹不了,這個現象叫作Stop The World。
標記階段,大多數是要STW的。如果不暫停用戶進程,在標記對象的時候,有可能有其它用戶線程會產生一些新的對象和引用,造成混亂。
現在的垃圾回收器,都會盡量去減少這個過程。但即使最先進的ZGC回收器,也會有短暫的STW過程。我們要做的就是在現有基礎設施上,盡量減少GC停頓。
舉例說明下
某個高並發服務的峰值流量是10萬次/秒,後面有10台負載均衡的機器,那麼每台機器平均下來需要1w/s。假如某台機器在這段時間內發生了STW,持續了一秒,那麼至少需要10ms就可以返回的1萬個請求,需要至少等待1秒。
在用戶那裡的表現就是系統發生了卡頓。如果我們的GC非常的頻繁。這種卡頓就會特別的明顯,嚴重影響用戶體驗。
雖然說Java為我們提供了非常棒的自動內存管理機制,但也不能濫用,因為它是有STW硬傷的。
介紹了堆的具體分區,年輕代和老年代。介紹了多個常用的垃圾回收器,不同的垃圾回收器有不同的特點。各種垃圾回收器都是為了解決頭疼的STW問題,讓GC時間更短,停頓更短,吞吐量更大。
接觸了很多名詞,總結如下
1.Mark
2.Sweep
3.Copy
4.Compact
1.Young generation
2.Survivor
3.Eden
4.Old Generation |Tenured Generation
5.GC
--1.Minor GC
--2.Major GC
1.weak generational hypothesis
2.分配擔保
3.提升
4.卡片標記
5.STW
⑷ 生活中垃圾的怎麼處理
生活垃圾指人們日常家庭生活所產生的廢棄物,品種多、結構雜。主要來源於家庭生活的日常起居、飲食的食品殘渣、各類器件、物品的包裝袋、包裝紙、大批的廢紙及紡織品、廢金屬等因此,根據垃圾的來源及基本種類可將其分為三類:
1、食品垃圾:這是食用各種食品所產生的殘余廢物的總稱。其主要特徵是生物分解速度快,腐蝕性強,並產生令人厭惡的各種刺鼻氣味。食品垃圾的處理要求不高,主要需解決處理速度的問題。
2、普通垃圾:這是人們日常生活廢物的總稱。包括廢棄的木製品、紙製品、塑料、橡膠、紡織品、皮革製品及丟棄的碎玻璃及金屬製品和塵土等。普通垃圾是城市中垃圾回收的主要對象。
3、危險垃圾:對人類和動植物的生命具有瞬間的、短期或長期危害的垃圾稱為危險垃圾。包括干電池、日光燈管、體溫計等各種化學、生物的危險品、易燃、易爆品,含放射性物質的廢物。這類垃圾一般不能混入普通垃圾中,應單獨清運和處理。
長久以來,垃圾的處理所采承的方法主要有焚燒、填埋、堆肥這三種。詳細內容如下:
內容
衛生填埋
焚燒
堆肥
操作安全性
較好,注意防火
好
好
技術可靠性
可靠
可靠
可靠,國內有相當經驗
佔地
大
小
中等
選址
較困難,要考慮地形、地質條件,防止地表水、地下水污染,一般遠離市區,運輸距離較遠。
易,可靠近市區建設,運輸距離較近
較易,僅需避開居民密集區,氣味影響半徑小於200m,運輸距離適中。
適用條件
無機物>60%
含水量<30%
密度>0.5t/d
垃圾低位熱值>3300kJ/kg時不需添加輔助燃料。
從無害化角度,垃圾中可生物降解有機物≥10%,從肥效出發應>40%。
最終處置
無
僅殘渣需作填埋處理,為初始量的10%。
非堆肥物需作填埋處理,為初始量的20~25%。
產品市場
可回收沼氣發電。
能產生熱能或電能。
建立穩定的堆肥市場較困難。
建設投資
較低
較高
適中
資源回收
無現場分選回收實例,但有潛在可能。
前處理工序可回收部分原料,但取決於垃圾中可利用物的比例。
同左
內容
衛生填埋
焚燒
堆肥
地表水污染
有可能,但可採取措施減少可能性。
在處理廠區無,在爐灰填埋時,其對地表水污染的可能性比填埋小。
在非堆肥物填埋時與衛生填埋相仿。
地下水污染
有可能,雖可採取防滲措施,但仍然可能發生滲漏。
灰渣中沒有有機質等污染物,僅需填埋時採取固化等措施可防止污染。
重金屬等可能隨堆肥製品污染地下水
大氣污染
有,但可用覆蓋壓實等措施控制
可以控制,但二惡英(Doxlin)等微量劇毒物需採取措施控制。
有輕微氣味,污染指標可能性不大。
土壤污染
限於填埋場區。
無
需控制堆肥製品中重金屬含量。
電池在我們日常生活中的應用越來越廣泛。電池有汞電池(鈕扣式電池)、鹼電池(普通電池)、錳電池(陣用電池)之分,一般都不同程度地含有對人體有危害的汞和錳,而廢電池中95%的物質又均可以回收,尤其是重金屬回收價值很高。因此,將廢電池亂扔或焚燒,不僅造成嚴重的環境問題,更是一種資源的浪費。普通干電池是圓筒形的,外筒由鋅製成,這一鋅筒即為電池的負極;筒中央炭棒為正極;筒內為二氧化錳、氯化鉸和氯化鋅。下面介紹兩種廢干電池內物質回收利用的方法:
1、提取氯化銨
將電池裡的黑色物質放在水裡攪拌溶解並過濾,將部分濾液放在蒸發皿中蒸發,得白色固體,再加熱,利用「升華」收集較純的氯化銨。
2、製取鋅粒
將鋅筒上的鋅片剪成碎片,放在坩堝中強熱(鋅熔點419攝氏度,熔化後小心地將鋅液倒入冷水裡,得到鋅粒。該方法可用於實驗室里製取鋅粒)。
過去人們一直認為垃圾是廢品,是沒用的東西,這是片面的觀點。垃圾其實是一種資源,一種符合可持續發展的經濟型再生資源,而現有的處理方式中,掩埋無疑不能回收垃圾資源;焚燒還處在一個資源回收較低的水平;只有綜合利用,數管其下,才能實現垃圾資源的最大回收。垃圾要做到綜合利用,第一步就要進行分類收集。分類收集是指在垃圾的產地,根據對垃圾的處理或回收利用的不同要求,將垃圾分為不同類別進行收集。分類收集一般採取設置不同形狀、不同顏色容器或規定特定的垃圾收集器等辦法進行。這也必將成為未來垃圾收集的主要方法。根據一些地區的經驗,垃圾的分類收集可以以一個居民住宅區為單位。在小區內設置三種顏色不同的垃圾箱。一種顏色的垃圾箱裝食品垃圾,一種顏色的垃圾箱裝普通垃圾,另一種顏色的垃圾箱裝危險垃圾。由於食品垃圾易腐爛的特點,垃圾的清運應較頻繁,速度較快,以半天或一天為單位比較適合;普通垃圾和危險垃圾可以根據各自的產生量來制定清運頻率,其中危險垃圾產生量較少,建議2~4天清理一次。垃圾分類運至處理場所一般可由專門的部門來進行(也可由條件較完備的小區進行)。這些機構將食品垃圾、普通垃圾及危險垃圾檢查後分別送至不同的處理場所進行最後的處理。其中食品垃圾可以經過發酵等方法處理生成燃氣與肥料。普通垃圾中,紙張和木材可再生紙品;玻璃、金屬可以熔化後製成新產品;塑料、橡膠也可以熔化後投入再生產。除此之外,1噸廢玻璃經機械化工藝處理後可生產出2萬只啤酒瓶或相當於1個籃球場面積的窗玻璃;廢塑料經回收後,可以變成原油,再從中提煉出柴油、汽油;約占城市生活垃圾總量65%~70%的廚余、果皮垃圾則可動用生化技術和專門的機器「吃進」,利用微生物發酵原理,24小時內就可將垃圾就地變成顆粒型或粉狀的肥料或飼料,供公共綠地使用或供市民家庭養花。危險垃圾則通過特殊處理而去除其毒害性。這就是發展了綜合利用處理垃圾的方法。
通過這次的學習與研究,我們不僅了解了生活垃圾的處理及利用,還可以准確處理個人製造的垃圾,盡量使垃圾變廢為寶。我們應該堅信,總有一天,我們將會走出垃圾的「圍城」。
附件1:
校園垃圾處理應實行分類回收制度,其具體操作方法如下:
①每一個班在班內建立一個小型垃圾點,將垃圾按紙類、塑料、金屬(如易拉罐等)、電池、其他共五類分類收集。由於紙類、塑料數量較多,可以一天清理一次,另外考慮到電池對環境的危害,建議同學們把廢舊電池統一回收。
②學校設立幾個大型垃圾箱,統一回收各班分類垃圾箱中的垃圾,並在校園內多設置一些小型分類垃圾箱。學校負責與社會回收單位聯系,及時送出回收來的垃圾。
③學校應當設立一個評分制度,對垃圾回收工作開展得好的班級進行鼓勵。回收的收入也可返回各班使用。
附件2:
學生對校園白色垃圾處理建議:
(1)學校應教育同學們增強環保意識,多宣傳白色污染的危害。
(2)同學們不要隨意扔垃圾,對隨地扔廢棄物的人講講環保的重要性。
(3)學校統一將垃圾分類、回收,集中處理。
(4)增設垃圾箱,放在白色污染嚴重的地方(如小賣部門口)。
(5)設計一個環保標志,掛在醒目的地方。
(6)盡量減少用塑料袋包裝物品,並杜絕使用一次性發泡飯盒(現在校食堂使用的一次性飯盒就是國家禁止使用的飯盒,但仍在使用)。
(7)呼籲全社會增強環保意識。
(8)學校不要焚燒垃圾。
⑸ 垃圾收集器-CMS、三色標記、記憶集
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它非常符合在注重用戶體驗的應用上使用,它是HotSpot虛擬機第一款真正意義上的並發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作。
從名字中的Mark Sweep這兩個詞可以看出,CMS收集器是一種 「標記-清除」演算法實現的,它的運作過程相比於前面幾種垃圾收集器來說更加復雜一些。整個過程分為四個步驟:
初始標記:
暫停所有的其他線程(STW),並記錄下gc roots直接能引用的對象,速度很快
並發標記:
並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程, 這個過程耗時較長但是不需要停頓用戶線程, 可以與垃圾收集線程一起並發運行。因為用戶程序繼續運行,可能會有導致已經標記過的對象狀態發生改變。
重新標記:
重新標記階段就是為了修正並發標記期間因為用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比並發標記階段時間短。主要用到三色標記里的增量更新演算法(見下面詳解)做重新標記。
並發清理:
開啟用戶線程,同時GC線程開始對未標記的區域做清掃。這個階段如果有新增對象會被標記為黑色不做任何處理(見下面三色標記演算法詳解)。
並發重置:
重置本次GC過程中的標記數據。
從它的名字就可以看出它是一款優秀的垃圾收集器,主要優點:並發收集、低停頓。但是它有下面幾個明顯的缺點:
1.對CPU資源敏感(會和服務搶資源);
2.無法處理浮動垃圾( 在並發標記和並發清理階段又產生垃圾 ,這種浮動垃圾只能等到下一次gc再清理了);
3.它使用的回收演算法-「標記-清除」演算法會導致收集結束時會有大量空間碎片產生,當然通過參數-XX:+UseCMSCompactAtFullCollection可以讓jvm在執行完標記清除後再做整理執行過程中的不確定性,會存在上一次垃圾回收還沒執行完,然後垃圾回收又被觸發的情況,特別是 在並發標記和並發清理階段會出現,一邊回收,系統一邊運行,也許沒回收完就再次觸發full gc,也就是"concurrent mode failure",此時會進入stop the world,用serial old垃圾收集器來回收
CMS的相關核心參數
1.-XX:+UseConcMarkSweepGC:啟用cms
2.-XX:ConcGCThreads:並發的GC線程數
3.-XX:+UseCMSCompactAtFullCollection:FullGC之後做壓縮整理(減少碎片)
4.-XX:CMSFullGCsBeforeCompaction:多少次FullGC之後壓縮一次,默認是0,代表每次FullGC後都會壓縮一 次
5.-XX:: 當老年代使用達到該比例時會觸發FullGC(默認是92,這是百分比)
6.-XX:+UseCMSInitiatingOccupancyOnly:只使用設定的回收閾值(-XX:設 定的值),如果不指定,JVM僅在第一次使用設定值,後續則會自動調整
7.-XX:+CMSScavengeBeforeRemark:在CMS GC前啟動一次minor gc,目的在於減少老年代對年輕代的引用,降低CMS GC的標記階段時的開銷,一般CMS的GC耗時 80%都在標記階段
8.-XX:+CMSParallellnitialMarkEnabled:表示在初始標記的時候多線程執行,縮短STW
9.-XX:+CMSParallelRemarkEnabled:在重新標記的時候多線程執行,縮短STW;
在並發標記的過程中,因為標記期間應用線程還在繼續跑,對象間的引用可能發生變化,多標和漏標的情況就有可能發生。這里引入「三色標記」來給大家解釋下,把Gcroots可達性分析遍歷對象過程中遇到的對象, 按照「是否訪問過」這個條件標記成以下三種顏色:
黑色:
表示對象已經被垃圾收集器訪問過, 且這個對象的所有引用都已經掃描過。 黑色的對象代表已經掃描過, 它是安全存活的, 如果有其他對象引用指向了黑色對象, 無須重新掃描一遍。 黑色對象不可能直接(不經過灰色對象) 指向某個白色對象。
灰色:
表示對象已經被垃圾收集器訪問過, 但這個對象上至少存在一個引用還沒有被掃描過。
白色:
表示對象尚未被垃圾收集器訪問過。 顯然在可達性分析剛剛開始的階段, 所有的對象都是白色的, 若在分析結束的階段, 仍然是白色的對象, 即代表不可達。
標記過程:
初始時,所有對象都在 【白色集合】中;
將GC Roots 直接引用到的對象 挪到 【灰色集合】中;
從灰色集合中獲取對象:
3.1. 將本對象 引用到的 其他對象 全部挪到 【灰色集合】中;
3.2. 將本對象 挪到 【黑色集合】裡面。
重復步驟3,直至【灰色集合】為空時結束。
結束後,仍在【白色集合】的對象即為GC Roots 不可達,可以進行回收
多標-浮動垃圾
在並發標記過程中,如果由於方法運行結束導致部分局部變數(gcroot)被銷毀,這個gcroot引用的對象之前又被掃描過 (被標記為非垃圾對象),那麼本輪GC不會回收這部分內存。這部分本應該回收但是沒有回收到的內存,被稱之為「浮動 垃圾」。浮動垃圾並不會影響垃圾回收的正確性,只是需要等到下一輪垃圾回收中才被清除。
另外,針對並發標記(還有並發清理)開始後產生的新對象,通常的做法是直接全部當成黑色,本輪不會進行清除。這部分 對象期間可能也會變為垃圾,這也算是浮動垃圾的一部分。
漏標-讀寫屏障
漏標只有 同時滿足 以下兩個條件時才會發生:
條件一:灰色對象 斷開了 白色對象的引用;即灰色對象 原來成員變數的引用 發生了變化。
條件二:黑色對象 重新引用了 該白色對象;即黑色對象 成員變數增加了 新的引用。
漏標會導致被引用的對象被當成垃圾誤刪除,這是嚴重bug,必須解決,有兩種解決方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。
增量更新 就是當黑色對象 插入新的指向 白色對象的引用關系時, 就將這個新插入的引用記錄下來, 等並發掃描結束之後, 再將這些記錄過的引用關系中的黑色對象為根, 重新掃描一次。 這可以簡化理解為, 黑色對象一旦新插入了指向白色對象的引用之後, 它就變回灰色對象了。
原始快照 就是當灰色對象要 刪除指向 白色對象的引用關系時, 就將這個要刪除的引用記錄下來, 在並發掃描結束之後, 再將這些記錄過的引用關系中的灰色對象為根, 重新掃描一次,這樣就能掃描到白色的對象,將白色對象直接標記為黑色(目的就是讓這種對象在本輪gc清理中能存活下來,待下一輪gc的時候重新掃描,這個對象也有可能是浮動垃圾)
以上 無論是對引用關系記錄的插入還是刪除, 虛擬機的記錄操作都是通過寫屏障實現的。
寫屏障實現原始快照(SATB): 當對象B的成員變數的引用發生變化時,比如引用消失(a.b.d = null),我們可以利用寫屏障,將B原來成員變數的引用對象D記錄下來:
寫屏障實現增量更新: 當對象A的成員變數的引用發生變化時,比如新增引用(a.d = d),我們可以利用寫屏障,將A新的成員變數引用對象D 記錄下來:
記憶集
當我們進行young gc時,我們的 gc roots除了常見的棧引用、靜態變數、常量、鎖對象、class對象 這些常見的之外,如果 老年代有對象引用了我們的新生代對象 ,那麼老年代的對象也應該加入gc roots的范圍中,但是如果每次進行young gc我們都需要掃描一次老年代的話,那我們進行垃圾回收的代價實在是太大了,因此我們引入了一種叫做記憶集的抽象數據結構來記錄這種引用關系。
什麼是記憶集?
記憶集是一種用於記錄從非收集區域指向收集區域的指針集合的數據結構。
如果我們不考慮效率和成本問題,我們可以用一個數組存儲所有有指針指向新生代的老年代對象。但是如果這樣的話我們維護成本就很好,打個比方,假如所有的老年代對象都有指針指向了新生代,那麼我們需要維護整個老年代大小的記憶集,毫無疑問這種方法是不可取的。因此我們引入了卡表的數據結構
什麼是卡表?
記憶集是我們針對於跨代引用問題提出的思想,而卡表則是針對於該種思想的具體實現。(可以理解為記憶集是結構,卡表是實現類)
在hotspot虛擬機中,卡表是一個位元組數組,數組的每一項對應著內存中的某一塊連續地址的區域,如果該區域中有引用指向了待回收區域的對象,卡表數組對應的元素將被置為1,沒有則置為0;
G1的記憶集
上述的 卡表機制基本上適用於CMS垃圾回收器 ,因為CMS垃圾回收器只需要在young gc時維護老年代對新生代的引用即可,但是G1垃圾回收器不一樣,因為G1垃圾回收器是基於分區模型的,所以每一個Region需要知道有哪些region的引用指向了它,並且這些region是不是本次垃圾回收區域的一部分。因此G1垃圾回收器不能簡單的只維護一個卡表(卡表只能簡單的知道某塊內存區域有沒有引用收集區域的對象,但是不能知道到底是誰引用了自己),所以在 G1垃圾回收器的記憶集的實現實際上是基於哈希表的 ,key代表的是其他region的起始地址,value是一集合,裡面存放了對應區域的卡表的索引,因此G1的region能夠通過記憶集知道,當前是哪個region有引用指向了它,並且能知道是哪塊區域存在指針指向。
但是大家應該能注意到, 每個region都維護一個記憶集,內存佔用量肯定很大,這也就是為什麼G1垃圾回收器比傳統的其他垃圾回收器要有更高的內存佔用 。據統計G1至少要耗費大約10%-20%的Java堆空間來維護收集器的工作。
參考:
https://blog.csdn.net/xc1989xc/article/details/107466313
https://blog.csdn.net/shangshanzixu/article/details/113918994
⑹ G1 GC垃圾收集流程
從 GC 演算法的角度,G1 選擇的是復合演算法,可以簡化理解為:
YoungGC 觸發時機
在分配一般對象(非巨型對象)時,當所有 eden region 使用達到最大閥值並且無法申請足夠內存時,會觸發一次YoungGC。每次young gc會回收所有Eden以及Survivor區,並且將存活對象復制到Old區以及另一部分的Survivor區。因為YoungGC會進行根掃描,所以會 stop the world 。
YoungGC的回收過程如下 :
1.根掃描root scan,跟CMS類似, Stop the world ,掃描GC Roots對象。
2.處理Dirty card,更新RSet.
3.掃描RSet,掃描RSet中所有old區對掃描到的young區或者survivor去的引用。
4.拷貝掃描出的存活的對象到survivor2/old區
5.處理引用隊列,軟引用,弱引用,虛引用
值得高興的情況是, cleanup 可以釋放老年代的整個regions. 但是並不是每次都是這樣, 當並發標記成功結束後,G1會預定一個混合的收集來收集年輕代regions的垃圾,也會在收集集合中加入一部分老年代的regions.
一個混合的Evacuation Pause並不總是並發標記階段結束後立即開始. 有一系列的規則和啟發式演算法來決定這個. 比如, 可以釋放掉老年代的一大部分空間, 那麼就沒必要做這個了.
因此,就是在並發標記結束和混合Evacuation Pause之間加入很多fully-young的Evacuation Pause.
具體放入收集集合的老年代區的region,以及它們被加入的順序都基於一系列的規則選擇出來的. 這些規則包括:應用設定的軟的實時性能指標, 存活統計以及並發標記階段垃圾回收的效率, 還有一系列可配的JVM 選項. 混合式收集大體上與我們前面看到fully-young相同, 但這次我們講到新的對象 remembered sets .
remembered sets 用來支持在不同heap regions上的獨立收集. 比如當收集region A,B,C, 我們只需要知道從region D和E中是否有引用到它們來決定它們的存活性.因為遍歷整個堆會消耗很久的時間並且打破了我們增量收集的意義, 所以在G1中也採用了與在其他演算法中採用Card Table來獨立收集年輕代區域類似的優化演算法, 叫做remember sets.
如下圖所示, 每個region都有一個RSet保存從其他region到這個region中對象的引用. 這些對象會被當做額外的GC roots. 注意在並發標記階段, 老年代被認為是垃圾的對象會被忽略, 即便有外部對象還在引用它們, 因為它們的對象也會被當做垃圾.
接下來發生的與其他收集器類似:多個並行的GC線程會找出哪些是存活的哪些是垃圾. 最後, 所有存活對象會被移動到survivor區(如有必要創建新的).所有的空region會被釋放後被用來存放對象. [[圖片上傳失敗.
為了在應用程序運行期間維護RSets, 任何時候對域的更新都會觸發一個Post-Write屏障. 如果關聯的引用是跨region的, 比如從一個region到另一個region,一個對應的記錄也會在目標region的RSet中添加. 將記錄(cards)加入到RSet是非同步的並應用了很多優化.簡單來說它用Write屏障來將臟記錄放到本地buffer中, 一個特殊的GC線程會選擇這些記錄,然後傳播信息給其他region的RSet.
Young GC發生的時機大家都知道,那什麼時候發生Mixed GC呢?其實是由一些參數控制著的,另外也控制著哪些老年代Region會被選入CSet。
gc_handbook_zh
第一階段initial mark是共用了Young GC的暫停,這是因為他們可以復用root scan操作,所以可以說global concurrent marking是伴隨Young GC而發生的。
第四階段Cleanup只是回收了沒有 任何存活對象的Region ,所以它並不需要STW
圖是來自 oracle 上對 gc 周期的描述,實心圓都表示一次 GC 停頓
除了以上的參數,G1 GC相關的其他主要的參數有:
參數 含義
-XX:G1HeapRegionSize=n 設置Region大小,並非最終值
-XX:MaxGCPauseMillis 設置G1收集過程目標時間,默認值200ms,不是硬性條件
-XX:G1NewSizePercent 新生代最小值,默認值5%
-XX:G1MaxNewSizePercent 新生代最大值,默認值60%
-XX:ParallelGCThreads STW期間,並行GC線程數
-XX:ConcGCThreads=n 並發標記階段,並行執行的線程數
-XX: 設置觸發標記周期的 Java 堆佔用率閾值。默認值是45%。這里的java堆佔比指的是non_young_capacity_bytes,包括old+humongous
-XX:G1ReservePercent,通過-XX:G1ReservePercent指定G1為分配擔保預留的空間比例,默認10%。也就是老年代會預留10%的空間來給新生代的對象晉升,如果經常發生新生代晉升失敗而導致Full GC,那麼可以適當調高此閾值。但是調高此值同時也意味著降低了老年代的實際可用空間
-XX:G1HeapWastePercent
通過-XX:G1HeapWastePercent指定觸發Mixed GC的堆垃圾佔比,默認值5%,也就是在全局標記結束後能夠統計出所有Cset內可被回收的垃圾占整對的比例值,如果超過5%,那麼就會觸發之後的多輪Mixed GC,如果不超過,那麼會在之後的某次Young GC中重新執行全局並發標記。可以嘗試適當的調高此閾值,能夠適當的降低Mixed GC的頻率
其他參數
因此AlwaysPreTouch,JVM就會先訪問所有分配給它的內存,讓操作系統把內存真正的分配給JVM.後續JVM就可以順暢的訪問內存了
關於AlwaysPreTouch找了一些資料,這個參數屬於比較偏門的優化項
JAVA進程啟動的時候,雖然我們可以為 JVM 指定合適的內存大小,但是這些內存操作系統並沒有真正的分配給JVM,而是等JVM訪問這些內存的時候,才真正分配,這樣會造成以下問題:
配置-XX:+AlwaysPreTouch參數可以優化這個問題,不過這個參數也有副作用,它會影響啟動時間,那影響到底有多大呢?請接著往下看。
配置這個參數後這么耗時其中一個原因是,這個特性在JDK8版本以前都不是並行處理的,到了JDK9才是並行。可以戳鏈接Parallelize Memory Pretouch: https://bugs.openjdk.java.net/browse/JDK-815795
配置-XX:+AlwaysPreTouch參數後,JVM進程啟動時間慢了幾個數量級的根本原因呢?
在沒有配置-XX:+AlwaysPreTouch參數即默認情況下,JVM參數-Xms申明的堆只是在虛擬內存中分配,而不是在物理內存中分配:它被以一種內部數據結構的形式記錄,從而避免被其他進程使用這些內存。這些內存頁直到被訪問時,才會在物理內存中分配。當JVM需要內存的時候,操作系統將根據需要分配內存頁。
配置-XX:+AlwaysPreTouch參數後,JVM將-Xms指定的堆內存中每個位元組都寫入』0』,這樣的話,除了在虛擬內存中以內部數據結構保留之外,還會在物理內存中分配。並且由於touch這個行為是單線程的,因此它將會讓JVM進程啟動變慢。所以,要麼選擇減少接下來對每個緩存頁的第一次訪問時間,要麼選擇減少JVM進程啟動時間,這是一種trade-off。
在某些情況下,G1觸發了Full GC,這時G1會退化使用Serial收集器來完成垃圾的清理工作,它僅僅使用單線程來完成GC工作,GC暫停時間將達到秒級別的。整個應用處於假死狀態,不能處理任何請求,我們的程序當然不希望看到這些。那麼發生Full GC的情況有哪些呢?
並發模式失敗
G1啟動標記周期,但在Mix GC之前,老年代就被填滿,這時候G1會放棄標記周期。這種情形下,需要增加堆大小,或者調整周期(例如增加線程數-XX:ConcGCThreads等)。
晉升失敗或者疏散失敗
G1在進行GC的時候沒有足夠的內存供存活對象或晉升對象使用,由此觸發了Full GC。可以在日誌中看到(to-space exhausted)或者(to-space overflow)。解決這種問題的方式是:
巨型對象分配失敗
當巨型對象找不到合適的空間進行分配時,就會啟動Full GC,來釋放空間。這種情況下,應該避免分配大量的巨型對象,增加內存或者增大-XX:G1HeapRegionSize,使巨型對象不再是巨型對象。
參考
分享文檔
Java Hotspot G1 GC的一些關鍵技術- 美團
Plumbr Handbook Java Garbage Collection.pdf 翻譯
G1GC 概念與性能調優- oppo
hbase G1
https://www.jianshu.com/p/5d4e319582f7
⑺ 幫我找一些關於垃圾處理的資料
垃圾的處理方法
分類收集 是指在垃圾的產地,根據對垃圾的處理或回收利用的不同要求,將垃圾分為不同類別進行收集。分類收集一般採取設置不同容器、不同顏色的或規定特定的垃圾收集器等辦法進行。分類收集處理效果好,經濟效益顯著,但需要居民的協作。
德國就是採用分類收集的方法來處理生活垃圾的。每個家庭一般有三個桶:一個桶裝報紙和各種廢紙;一個桶裝要送去焚燒處理的垃圾;還有一個桶用來裝要收集的各類資源型垃圾,這些垃圾會被送去資源垃圾回收廠進行分類和回收,但不包括玻璃瓶。各種顏色的玻璃瓶洗凈、晾乾後,放在大門進口處的一個大箱子里,然後再送到社區的玻璃專用回收桶中去。
破碎、壓縮 破碎是重要的預處理工序,特別是大型消費廢棄物數量的增加,破碎更顯得重要。破碎的目的是減少垃圾體積,增加均勻度,便於下一步處理。壓縮是減少垃圾體積的最直接的方法,壓縮過程對水質和空氣不造成污染,投資和操作費用低,壓縮後更便於運輸、填地,甚至可直接用作建築材料。城市垃圾經壓縮後一般可縮至原體積的1/5,法國還試驗採用更高壓力,並加入粘合劑,將垃圾壓縮到原體積的1/20。
管道運輸 這是理想的運輸垃圾廢物的方法,清潔衛生,還可節約勞動力和車輛,避免了地面交通擁擠的問題,但投資比較高。瑞典的斯德哥爾摩市有10處採用真空管道運輸垃圾,垃圾集中站的下面鋪設真空管道,可自動地將垃圾運走。西歐正在試驗將垃圾製成漿狀,通過管道長距離輸送更重的廢物。
現在採用最多的幾種垃圾處理方法是:
衛生填埋 這是廣泛採用的處理城市垃圾的方法,其基本操作是鋪上一層城市垃圾並壓實後,再鋪上一層土,然後逐次鋪城市垃圾和土,如此形成夾層結構。同時,可有計劃地將廢礦坑、粘土坑等經過衛生填地,改造成公園、綠地、牧場、農田或作建築用地。
垃圾焚化 焚化是將城市垃圾在高溫下燃燒,使可燃廢物轉變為二氧化碳和水,焚化後殘灰僅為廢物原體積的5%以下,從而大大減少了固體廢物量,還可以消滅各種病原體,把一些有毒、有害物質轉化為無害物質並可回收熱能。由於大城市附近缺乏填埋場所,所以多採用焚化法處理垃圾、廢物。
綜合利用 城市垃圾的回收利用日益受到重視,美國修訂了原來的《固體廢物處理法》,公布了《資源回收法》,通過法律鼓勵和支持回收利用技術的研究。日本、西歐等發達國家也在大力開展這方面的工作,這種方法也叫資源化法,是當前和今後要大力發展的方法。美國科學家認為,扔棄於垃圾堆的各種廢紙、廢木纖維和木材廢料,是一種新型「森林」。過去它們長期被人類忽視,造成了木質資源的巨大浪費,據估計,美國每年有5600萬噸廢木材和廢紙被拋入垃圾堆,這相當於美國1990年國有林採伐量的3倍。近年來,科學家經研究發現,這種「森林」的開發利用前景十分可觀,方法多種多樣。各種廢紙可以回收制漿造紙,也可和其他原料搭配生產各種紙板。回收的廢紙和其他纖維原料製成濕纖維漿,然後模壓成型,製成一種「空隙板」,可廣泛應用於包裝、傢具、輕型房屋建築等方面。而對各種木材廢料的再回收利用就更豐富多彩了,如廢木包裝箱可重新回收,用於生產刨花板;對於拆毀建築所得的板材,可以重新分級再次利用等。
⑻ 網紅海上酒店已停用並被拖離海域,原因是什麼
據相關報道,漳州當地群眾林某在未取得海域使用權的情況下,於東山前樓鎮下林尾村南側海域私自搭一間“網紅”海上酒店。因其涉嫌非法佔用海域,漳州海警局東山工作站執法人員已對其立案調查,並責令其停止違法行為,酒店已被拖離海域關閉停業,店內沒有員工,此前預定所有房費均已退還給客戶。而對於網友關心的環保及排污等相關問題,該酒店發出招募首席環保官及環保設備技術顧問的文章,尋找一位“首席環保官”,為“海席”在建設、運營和對周邊海域的環境保護上給予專業的評估、指導和監督。希望技術顧問協助開展:(1)針對海上居民生活的小型污水處理方案:從設備、技術埠提供解決方案;(2)針對周邊海域的塑料垃圾自動收集方案:水面垃圾收集器、無人船裝置等;(3)針對周邊漁民的垃圾分類收集處理方案:引入國內外先進海上垃圾處理設備。
⑼ 三色標記法與垃圾回收器(CMS、G1)
JVM中的CMS、G1垃圾回收器所使用垃圾回收演算法即為三色標記法。
三色標記法將對象的顏色分為了黑、灰、白,三種顏色。
存在問題:
浮動垃圾:並發標記的過程中,若一個已經被標記成黑色或者灰色的對象,突然變成了垃圾,此時,此對象不是白色的不會被清除,重新標記也不能從GC Root中去找到,所以成為了浮動垃圾,這種情況對系統的影響不大,留給下一次GC進行處理即可。
對象漏標問題(需要的對象被回收):並發標記的過程中,一個業務線程將一個未被掃描過的白色對象斷開引用成為垃圾(刪除引用),同時黑色對象引用了該對象(增加引用)(這兩部可以不分先後順序);因為黑色對象的含義為其屬性都已經被標記過了,重新標記也不會從黑色對象中去找,導致該對象被程序所需要,卻又要被GC回收,此問題會導致系統出現問題,而CMS與G1,兩種回收器在使用三色標記法時,都採取了一些措施來應對這些問題,CMS對增加引用環節進行處理(Increment Update),G1則對刪除引用環節進行處理(SATB)。
在JVM虛擬機中有兩種常見垃圾回收器使用了該演算法:
CMS(Concurrent Mark Sweep)
CMS,是非常有名的JVM垃圾回收器,它起到了承上啟下的作用,開啟了並發回收的篇章。
但是CMS由於許多小問題,現在基本已經被淘汰。
增量更新(Increment Update)
在應對漏標問題時,CMS使用了Increment Update方法來做:
在一個未被標記的對象(白色對象)被重新引用後,==引用它的對象==,若為黑色則要變成灰色,在下次二次標記時讓GC線程繼續標記它的屬性對象。
但是就算時這樣,其仍然是存在漏標的問題:
在一個灰色對象正在被一個GC線程回收時,當它已經被標記過的屬性指向了一個白色對象(垃圾)
而這個對象的屬性對象本身還未全部標記結束,則為灰色不變
而這個GC線程在標記完最後一個屬性後,認為已經將所有的屬性標記結束了,將這個灰色對象標記為黑色,被重新引用的白色對象,無法被標記
補充,CMS除了這個缺陷外,仍然存在兩個個較為致命的缺陷:
解決方案:使用Mark-Sweep-Compact演算法,減少垃圾碎片
當JVM認為內存不夠了,再使用CMS進行並發清理內存可能會發生OOM的問題,而不得不進行Serial Old GC,Serial Old是單線程垃圾回收,效率低
解決方案:降低觸發CMS GC的閾值,讓浮動垃圾不那麼容易占滿老年代
G1(Garbage First)
從G1垃圾回收器開始,G1的物理內存不再分代,而是由一塊一塊的Region組成;邏輯分代仍然存在。
前置知識 — Card Table(多種垃圾回收器均具備)
由於在進行YoungGC時,我們在進行對一個對象是否被引用的過程,需要掃描整個Old區,所以JVM設計了CardTable,將Old區分為一個一個Card,一個Card有多個對象;如果一個Card中的對象有引用指向Young區,則將其標記為Dirty Card,下次需要進行YoungGC時,只需要去掃描Dirty Card即可。
Card Table 在底層數據結構以 Bit Map實現。
CSet(Collection Set)
SATB(Snapshot At The Beginning)
在應對漏標問題時,CMS使用了SATB方法來做:
因為SATB在重新標記環節只需要去重新掃描那些被推到堆棧中的引用,並配合Rset來判斷當前對象是否被引用來進行回收;
並且在最後G1並不會選擇回收所有垃圾對象,而是根據Region的垃圾多少來判斷與預估回收價值(指回收的垃圾與回收的STW時間的一個預估值),將一個或者多個Region放到CSet中,最後將這些Region中的存活對象壓縮並復制到新的Region中,清空原來的Region。
問題:G1會不會進行Full GC?
會,當內存滿了的時候就會進行Full GC;且JDK10之前的Full GC,為單線程的,所以使用G1需要避免Full GC的產生。
解決方案:
加大內存;
提高CPU性能,加快GC回收速度,而對象增加速度趕不上回收速度,則Full GC可以避免;
降低進行Mixed GC觸發的閾值,讓Mixed GC提早發生(默認45%)
G1的第一篇paper(附錄1)發表於2004年,在2012年才在jdk1.7u4中可用。oracle官方計劃在jdk9中將G1變成默認的垃圾收集器,以替代CMS。為何oracle要極力推薦G1呢,G1有哪些優點?
首先,G1的設計原則就是簡單可行的性能調優
開發人員僅僅需要聲明以下參數即可:
其中-XX:+UseG1GC為開啟G1垃圾收集器,-Xmx32g 設計堆內存的最大內存為32G,-XX:MaxGCPauseMillis=200設置GC的最大暫停時間為200ms。如果我們需要調優,在內存大小一定的情況下,我們只需要修改最大暫停時間即可。
其次,G1將新生代,老年代的物理空間劃分取消了。
這樣我們再也不用單獨的空間對每個代進行設置了,不用擔心每個代內存是否足夠。
取而代之的是,G1演算法將堆劃分為若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區域,G1收集器通過將對象從一個區域復制到另外一個區域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內存碎片問題的存在了。
在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認為這是一個巨型對象。這些巨型對象,默認直接會被分配在年老代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。
PS:在java 8中,持久代也移動到了普通的堆內存空間中,改為元空間。
對象分配策略
說起大對象的分配,我們不得不談談對象的分配策略。它分為3個階段:
對TLAB空間中無法分配的對象,JVM會嘗試在Eden空間中進行分配。如果Eden空間無法容納該對象,就只能在老年代中進行分配空間。
最後,G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是Stop The World(STW)的。下面我們將分別介紹一下這2種模式。
Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。在這種情況下,Eden空間的數據移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數據會直接晉升到年老代空間。Survivor區的數據移動到新的Survivor區中,也有部分數據晉升到老年代空間中。最終Eden空間的數據為空,GC停止工作,應用線程繼續執行。
這時,我們需要考慮一個問題,如果僅僅GC 新生代對象,我們如何找到所有的根對象呢? 老年代的所有對象都是根么?那這樣掃描下來會耗費大量的時間。於是,G1引進了RSet的概念。它的全稱是Remembered Set,作用是跟蹤指向某個heap區內的對象引用。
在CMS中,也有RSet的概念,在老年代中有一塊區域用來記錄指向新生代的引用。這是一種point-out,在進行Young GC時,掃描根時,僅僅需要掃描這一塊區域,而不需要掃描整個老年代。
但在G1中,並沒有使用point-out,這是由於一個分區太小,分區數量太多,如果是用point-out的話,會造成大量的掃描浪費,有些根本不需要GC的分區引用也掃描了。於是G1中使用point-in來解決。point-in的意思是哪些分區引用了當前分區中的對象。這樣,僅僅將這些對象當做根來掃描就避免了無效的掃描。由於新生代有多個,那麼我們需要在新生代之間記錄引用嗎?這是不必要的,原因在於每次GC時,所有新生代都會被掃描,所以只需要記錄老年代到新生代之間的引用即可。
需要注意的是,如果引用的對象很多,賦值器需要對每個引用做處理,賦值器開銷會很大,為了解決賦值器開銷這個問題,在G1 中又引入了另外一個概念,卡表(Card Table)。一個Card Table將一個分區在邏輯上劃分為固定大小的連續區域,每個區域稱之為卡。卡通常較小,介於128到512位元組之間。Card Table通常為位元組數組,由Card的索引(即數組下標)來標識每個分區的空間地址。默認情況下,每個卡都未被引用。當一個地址空間被引用時,這個地址空間對應的數組索引的值被標記為」0″,即標記為臟被引用,此外RSet也將這個數組下標記錄下來。一般情況下,這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index。
Young GC 階段:
Mix GC不僅進行正常的新生代垃圾收集,同時也回收部分後台掃描線程標記的老年代分區。
它的GC步驟分2步:
全局並發標記(global concurrent marking)
拷貝存活對象(evacuation)
在進行Mix GC之前,會先進行global concurrent marking(全局並發標記)。 global concurrent marking的執行過程是怎樣的呢?
在G1 GC中,它主要是為Mixed GC提供標記服務的,並不是一次GC過程的一個必須環節。global concurrent marking的執行過程分為五個步驟:
初始標記(initial mark,STW)
在此階段,G1 GC 對根進行標記。該階段與常規的 (STW) 年輕代垃圾回收密切相關。
根區域掃描(root region scan)
G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的對象。該階段與應用程序(非 STW)同時運行,並且只有完成該階段後,才能開始下一次 STW 年輕代垃圾回收。
並發標記(Concurrent Marking)
G1 GC 在整個堆中查找可訪問的(存活的)對象。該階段與應用程序同時運行,可以被 STW 年輕代垃圾回收中斷
最終標記(Remark,STW)
該階段是 STW 回收,幫助完成標記周期。G1 GC 清空 SATB 緩沖區,跟蹤未被訪問的存活對象,並執行引用處理。
清除垃圾(Cleanup,STW)
在這個最後階段,G1 GC 執行統計和 RSet 凈化的 STW 操作。在統計期間,G1 GC 會識別完全空閑的區域和可供進行混合垃圾回收的區域。清理階段在將空白區域重置並返回到空閑列表時為部分並發。
提到並發標記,我們不得不了解並發標記的三色標記演算法。它是描述追蹤式回收器的一種有用的方法,利用它可以推演回收器的正確性。 首先,我們將對象分成三種類型的。
根對象被置為黑色,子對象被置為灰色。
繼續由灰色遍歷,將已掃描了子對象的對象置為黑色。
遍歷了所有可達的對象後,所有可達的對象都變成了黑色。不可達的對象即為白色,需要被清理。
這看起來很美好,但是如果在標記過程中,應用程序也在運行,那麼對象的指針就有可能改變。這樣的話,我們就會遇到一個問題:對象丟失問題
我們看下面一種情況,當垃圾收集器掃描到下面情況時:
這時候應用程序執行了以下操作:
這樣,對象的狀態圖變成如下情形:
這時候垃圾收集器再標記掃描的時候就會下圖成這樣:
很顯然,此時C是白色,被認為是垃圾需要清理掉,顯然這是不合理的。那麼我們如何保證應用程序在運行的時候,GC標記的對象不丟失呢?有如下2中可行的方式:
在插入的時候記錄對象
在刪除的時候記錄對象
剛好這對應CMS和G1的2種不同實現方式:
在CMS採用的是增量更新(Incremental update),只要在寫屏障(write barrier)里發現要有一個白對象的引用被賦值到一個黑對象 的欄位里,那就把這個白對象變成灰色的。即插入的時候記錄下來。
在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,刪除的時候記錄所有的對象,它有3個步驟:
這樣,G1到現在可以知道哪些老的分區可回收垃圾最多。 當全局並發標記完成後,在某個時刻,就開始了Mix GC。這些垃圾回收被稱作「混合式」是因為他們不僅僅進行正常的新生代垃圾收集,同時也回收部分後台掃描線程標記的分區。混合式垃圾收集如下圖:
混合式GC也是採用的復制的清理策略,當GC完成後,會重新釋放空間。
至此,混合式GC告一段落了。下一小節我們講進入調優實踐。
MaxGCPauseMillis調優
前面介紹過使用GC的最基本的參數:
前面2個參數都好理解,後面這個MaxGCPauseMillis參數該怎麼配置呢?這個參數從字面的意思上看,就是允許的GC最大的暫停時間。G1盡量確保每次GC暫停的時間都在設置的MaxGCPauseMillis范圍內。 那G1是如何做到最大暫停時間的呢?這涉及到另一個概念,CSet(collection set)。它的意思是在一次垃圾收集器中被收集的區域集合。
Young GC:選定所有新生代里的region。通過控制新生代的region個數來控制young GC的開銷。
Mixed GC:選定所有新生代里的region,外加根據global concurrent marking統計得出收集收益高的若干老年代region。在用戶指定的開銷目標范圍內盡可能選擇收益高的老年代region。
在理解了這些後,我們再設置最大暫停時間就好辦了。 首先,我們能容忍的最大暫停時間是有一個限度的,我們需要在這個限度范圍內設置。但是應該設置的值是多少呢?我們需要在吞吐量跟MaxGCPauseMillis之間做一個平衡。如果MaxGCPauseMillis設置的過小,那麼GC就會頻繁,吞吐量就會下降。如果MaxGCPauseMillis設置的過大,應用程序暫停時間就會變長。G1的默認暫停時間是200毫秒,我們可以從這里入手,調整合適的時間。
其他調優參數
避免使用以下參數:
避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設置年輕代大小。固定年輕代的大小會覆蓋暫停時間目標。
觸發Full GC
在某些情況下,G1觸發了Full GC,這時G1會退化使用Serial收集器來完成垃圾的清理工作,它僅僅使用單線程來完成GC工作,GC暫停時間將達到秒級別的。整個應用處於假死狀態,不能處理任何請求,我們的程序當然不希望看到這些。那麼發生Full GC的情況有哪些呢?
並發模式失敗
G1啟動標記周期,但在Mix GC之前,老年代就被填滿,這時候G1會放棄標記周期。這種情形下,需要增加堆大小,或者調整周期(例如增加線程數-XX:ConcGCThreads等)。
晉升失敗或者疏散失敗
G1在進行GC的時候沒有足夠的內存供存活對象或晉升對象使用,由此觸發了Full GC。可以在日誌中看到(to-space exhausted)或者(to-space overflow)。解決這種問題的方式是:
巨型對象分配失敗
當巨型對象找不到合適的空間進行分配時,就會啟動Full GC,來釋放空間。這種情況下,應該避免分配大量的巨型對象,增加內存或者增大-XX:G1HeapRegionSize,使巨型對象不再是巨型對象。
由於篇幅有限,G1還有很多調優實踐,在此就不一一列出了,大家在平常的實踐中可以慢慢探索。最後,期待java 9能正式發布,默認使用G1為垃圾收集器的java性能會不會又提高呢?
G1處理和傳統的垃圾收集策略是不同的,關鍵的因素是它將所有的內存進行了子區域的劃分。
總結
G1是一款非常優秀的垃圾收集器,不僅適合堆內存大的應用,同時也簡化了調優的工作。通過主要的參數初始和最大堆空間、以及最大容忍的GC暫停目標,就能得到不錯的性能;同時,我們也看到G1對內存空間的浪費較高,但通過**首先收集盡可能多的垃圾(Garbage First)的設計原則,可以及時發現過期對象,從而讓內存佔用處於合理的水平。
參考鏈接:
https://juejin.cn/post/6859931488352370702
https://blog.csdn.net/qq_39276448/article/details/104470796