GC

八涨颜、垃圾回收

什么樣的對象是活的,或者說有用的對象依沮。

C涯贞、c++都是手動關(guān)聯(lián)內(nèi)存的,所有在對象不需要的時候需要回收對象危喉,所以說就沒垃圾對象宋渔。那么在java中的內(nèi)存都是由jvm來關(guān)聯(lián)的,那么就需要弄清楚這個問題辜限。

垃圾:沒有被任何引用指向的對象

在java早起來說皇拣,我們是使用引用計數(shù)法來查看對象是否是垃圾,但是這種解釋是由缺陷的薄嫡。下面展示一種模型氧急。

public class JcModle {
?
 private JcModle1 jcModle1;
?
 public JcModle1 getJcModle1() {
 return jcModle1;
 }
?
 public void setJcModle1(JcModle1 jcModle1) {
 this.jcModle1 = jcModle1;
 }
?
 public static void main(String[] args) {
 JcModle1 jc1 = new JcModle1();
?
 JcModle jc = new JcModle();
?
 jc.setJcModle1(jc1);
 jc1.setJcModle(jc);
?
 //切斷和操作數(shù)棧直接的聯(lián)系
 jc = null;
 jc1 = null;
?
?
?
 }
}
?
class JcModle1{
?
 private JcModle jcModle;
?
 public void setJcModle(JcModle jcModle) {
 this.jcModle = jcModle;
 }
}

通過上面的例子,我么知道將jc和jc1設(shè)置為null毫深,并不是真的把所對于的對象干掉重置吩坝,而是將jc和jc1的引用指向了null,那么這兩個真實的對象依然存在于內(nèi)存空間中哑蔫,只是操作數(shù)棧中并沒有其直接的引用了钉寝,但是有意思的事情發(fā)生了。這兩個對象的引用計數(shù)并不為0闸迷,他們直接相互引用嵌纲,但是從我只管的角度去想,我都把你兩設(shè)置為null了腥沽,我還要你兩干嘛逮走。因此隨著java的不斷發(fā)展,出現(xiàn)了一種更為巧妙的算法:可達性分析巡球。

我們從一個根的角度去分析一個對象是否有用言沐,那么我們來介紹那些事作為根的內(nèi)存:

  • 虛擬機棧,也就說線程中正在操作的對象

  • 方法區(qū)中的靜態(tài)屬性的引用對象

  • 方法區(qū)中常量引用對象酣栈,別如說:static final 中的對象险胰。

  • 本地方法棧中JNI引用的對象(這個不是我們的虛擬機棧或者說是操作數(shù)棧)

  • JVM的內(nèi)部引用(class對象矿筝,萬物皆對象起便,class也是一個對象,異常對象,系統(tǒng)類加載器榆综,BootStrapClassLoad妙痹,ApplicationClassLoad)

  • 被同步鎖持有的對象

  • JVM內(nèi)部的JMXBean、JVMTI中注冊的回調(diào)

  • JVM實現(xiàn)中的“臨時性的對象”鼻疮,跨帶引用的對象怯伊。

image-20200803205943320.png

這種方法就會天然的排除引用計數(shù)法所帶來的問題,那就不需要寫另外的操作去排除循環(huán)引用的算法判沟。

Class也是可以被回收的耿芹,但是條件比較苛刻

同時滿足下面條件,Class將被回收挪哄。

  • 該類的所有實例對象都被回收

  • 加載該類的ClassLoader已經(jīng)被回收(一般只能是自制的一種類的加載器)

  • 該類的對應(yīng)的符號引用沒有被任何地方引用吧秕,無法在任何地方通過反射構(gòu)建該類的方法(?迹炼?怎么做到)

  • 參數(shù)控制:啟動的時候開啟這個配置


    image-20200803211950702.png

    如果想查看gc的過程砸彬,可以配置參數(shù)-XX:+PrintGC

九、Finalize——>對象最后的晚餐

當(dāng)一個對象即將被宣告死亡的時候斯入,jvm會調(diào)用Object中的finalize()方法砂碉,那么這個對象可以趁此機會拜托被垃圾回收的命運

 public static FinalzeObject finalzeObject = null;
?
@Override
protected void finalize() throws Throwable {
 super.finalize();
 System.out.println("finalize execute");
 FinalzeObject.finalzeObject = this;
?
}
?
private static void isLive(){
 System.out.println("live");
}
?
public static void main(String[] args) {
?
 finalzeObject = new FinalzeObject();
?
 finalzeObject = null;
?
 System.gc();
?
 if(finalzeObject != null){
 isLive();
 }else{
 System.out.println("dead");
 }
?
}
image-20200803220403019.png

運行結(jié)果居然是dead,這就說明咱扣,在finalize還沒有來得及執(zhí)行的時候绽淘,我們的gc就已經(jīng)完成了,說明了finalize方法的優(yōu)先度很低闹伪,同事需要知道的是執(zhí)行finalize()方法的線程是守護線程,就是不一定會執(zhí)行壮池。因此偏瓤,不推薦使用finalize來給對象做最后的出來,多數(shù)使用finally來做椰憋。此外厅克,finalize()方法只能使用一次,第二次gc的時候?qū)⒉粫?zhí)行

十橙依、強引用证舟、軟引用、弱引用窗骑、虛引用

強引用

一般指的是根可達的對象女责,稱之為強引用。在任何gc中都不會被回收创译,即便發(fā)生了outofmemory

軟引用

在系統(tǒng)即將要outof Memory的時候會被回收抵知。(在一些緩存中會用到)

/**
 * -Xms=30m -Xmx=30m
 */
public class SoftRefe {
?
 private static class User{
?
 }
?
 public static void main(String[] args) {
 User u = new User();
?
 //構(gòu)建軟引用的對象
 SoftReference<User> su = new SoftReference<>(u);
 //切斷根可達的關(guān)聯(lián),確保su只是通過本身與虛擬機棧的關(guān)聯(lián),而非通過u構(gòu)建了強關(guān)聯(lián)
 u = null;
?
 System.gc();
?
 System.out.println("u is live?" +su.get());
?
 List<byte[]> list = new ArrayList<>();
?
 try{
 while (true){
 byte[] b = new byte[10*1024*1024];
 list.add(b);
 }
 }catch (Throwable e){
 System.out.println("u is live?" +su.get());
?
 }
?
?
 }
}
image-20200803223125338.png

我們可以看到第一次的回收刷喜,并不會對我們的對象造成任何的影響残制,但是在系統(tǒng)空間溢出的時候,就把它回收掉了掖疮。

可以做一些圖片緩存初茶,為了加速我們可以吧圖片放在內(nèi)存中,但是如果空間不夠了浊闪,就把這部分回收掉恼布,就可以避免內(nèi)存溢出了。

弱引用

只要發(fā)生垃圾回收规揪,就會被回收掉桥氏。

public static void main(String[] args) {
//        SoftRefe.User u = new SoftRefe.User();
 User user = new User();
 WeakReference<User> wu = new WeakReference<>(user);
 //構(gòu)建軟引用的對象
 //切斷根可達的關(guān)聯(lián),確保su只是通過本身與虛擬機棧的關(guān)聯(lián)猛铅,而非通過u構(gòu)建了強關(guān)聯(lián)
 user = null;
?
 System.gc();
?
 System.out.println("u is live?" +wu.get());
?
 List<byte[]> list = new ArrayList<>();
?
 try{
 while (true){
 byte[] b = new byte[10*1024*1024];
 list.add(b);
 }
 }catch (Throwable e){
 System.out.println("u is live?" +wu.get());
?
 }
?
?
 }

虛引用

隨時會被回收掉的對象字支。使用PhantomReference來構(gòu)建對象。一般用來測試虛擬機gc是否正常奸忽。

十一堕伪、對象的分配策略

分配在堆上的對象

新生代的GC

對象在堆空間上,機會都會采用的模型是分帶年齡劃分的栗菜。

因為java是一種面向語言的對象欠雌,所有我們coding中,會大量的使用到對象疙筹。那么基于這種考量富俄,jvm的設(shè)計上,基于前文所介紹的標(biāo)記對象是否為存活對象的方法而咆,以及我們我們得知霍比,jvm的方法的運行就是我們所說的棧幀的信息,當(dāng)一個方法發(fā)生return的時候暴备,這個方法中所創(chuàng)建的對象悠瞬,大部分都不會和根對象(棧變量)再有聯(lián)系。所有我們大致可以推測出java中的對象大致有兩個特點:

  • 朝生夕死

  • 躲過GC次數(shù)越多的對象涯捻,越難以被回收浅妆。

所有基于以上原則,我們給java劃分出來兩大塊區(qū)域:

  • 新生代

  • 老年代

那么新生代的對象如何進入老年區(qū)呢障癌,通常有一下幾種方式:

  • 分別對應(yīng)具有以上兩種特性的內(nèi)存區(qū)域凌外,而在對象頭中,又會有一個記錄年齡的標(biāo)記混弥,通常情況下趴乡,躲過一次GC对省,對象年齡就+1,當(dāng)年齡大于15時晾捏,將會進入老年區(qū)蒿涎。

  • 當(dāng)新生代的區(qū)域不夠用時,新生代中躲過本次GC的對象可以直接晉升到老年代惦辛。為了將清除這個問題劳秋,我們必須弄清楚,新生代的GC方法胖齐。

模型大致如下:


image-20200808195224675.png

為什么這樣設(shè)計呢玻淑?首先我們來說明發(fā)生在新生代上的GC算法:復(fù)制算法。

通常情況下呀伙,我們回收一次性回收一整塊的內(nèi)存區(qū)域一定比一個一個的回收對象的速度來的更快补履,就像是將硬盤格式化一樣,很快剿另,那么我們在回收之前箫锤,首先要將這部分的內(nèi)存中的存活對象,復(fù)制到另一個區(qū)域雨女,然后回收內(nèi)存谚攒,那么另一塊區(qū)域就是有用的對象。

同時這種算法不僅塊同時沒用內(nèi)存隨便氛堕,我們復(fù)制到的另一個區(qū)域的內(nèi)存中對象分配的可以很規(guī)整馏臭。

image-20200808213558834.png

這時候提出疑問:這樣確實很快,也確實滿足了GC的算發(fā)讼稚,那么為什么前文中的新生代是分為了3部分括儒,并不是兩部分呢?

確實锐想,這個是個問題塑崖,但是我們仔細想想,很容易就發(fā)現(xiàn)這種算法有一個致命的缺陷痛倚,就是對內(nèi)存的利用率僅僅只有50%,同時我們知道內(nèi)存是有限且昂貴的(雖然現(xiàn)在并不算太貴)澜躺,為了解決這個問題我們設(shè)計出了Eden區(qū)蝉稳,(伊甸園)。

那么這個設(shè)計優(yōu)勢如何讓我們的內(nèi)存利用率提高的呢掘鄙?

答案可以從兩個方面的解釋:

  • 首先耘戚,我們只要縮小from和to區(qū)的大小,因為這兩區(qū)域就是發(fā)生復(fù)制算法的區(qū)域操漠,他們所占的比重小了收津,利用率自然就高了饿这。

  • 其次,前文所講撞秋。對象是朝生夕死的长捧,所有我們也不需要給from和to區(qū)他打的地方,因為在Eden區(qū)的大部分對象吻贿,都躲不過被垃圾回收的命運串结。

所有他們的分配比例大致為8:1:1。

那么他們Eden舅列,出發(fā)的垃圾回收又是怎樣的呢肌割?

答案就是Appel式的回收,那么什么是Appel式的回收呢帐要?

我們可以把自己想象成為上帝把敞,我們每次new對象出來的時候,都像是上帝捏了一個人出來榨惠,那么隨著不斷的new奋早,使用內(nèi)存資源,總會有一個時刻冒冬,Eden的資源不夠用了伸蚯,那么怎么辦,作為上帝的你只好重啟這個世界简烤,但是突然發(fā)現(xiàn)一些很有趣也很有用的對象剂邮,你不能夠直接回收掉,那么就只好把它們放到另一個地發(fā)横侦,也就說From或者是to區(qū)的挥萌。這個就是Eden區(qū)的規(guī)則。

這個回收算法還有三個問題:

  • 如果碰到一個很大的對象枉侧,比如說一個數(shù)組String[]引瀑,讓Eden都放不下了,更別說From和to區(qū)了榨馁,那么這個時候即便我們出發(fā)垃圾回收也不可能使得這個大對象放到我們新生代中憨栽。他總會達到年齡現(xiàn)在,晉升到老年代翼虫。這樣顯然浪費了CPU屑柔。

    既然如此,那么我們就一開始就讓這個大的對象進入老年代珍剑,就不會有GC的發(fā)生了

  • 第二個問題就是:在Eden空間慢了掸宛,復(fù)制到From或者是To區(qū)的時候,發(fā)現(xiàn)From和To區(qū)放不下這些對象了招拙。那么怎么辦唧瘾。

    這個時候措译,就會將這些幸存下來的對象,統(tǒng)一的丟到老年代中饰序。

  • 第三個問題就是:當(dāng)新生代向老年代晉升的時候领虹,發(fā)現(xiàn)老年代的空間也不夠了。那么怎么辦菌羽?這個時候就是我們JVM的空間分配擔(dān)保

    想象一下掠械,當(dāng)old區(qū)也不夠空間了,那么我們是不是需要發(fā)生oldGC注祖,那么我們不能總是相信Eden區(qū)過來的對象可以放得下猾蒂,所有大致有兩種原則:

    • 悲觀思想:總是認(rèn)為young過來將導(dǎo)致內(nèi)存不夠,因此在每次發(fā)生YoungGC的時候是晨,都會去觸發(fā)oldGC肚菠,

    • 樂觀思想:樂觀的認(rèn)為Young過來的對象我們發(fā)的下,只有當(dāng)放不下的時候才會觸發(fā)oldGC罩缴。

    事實上蚊逢,連old區(qū)都放不下的情況少之又少,如果發(fā)生了箫章,那么理oom也就不遠了烙荷。因此我們當(dāng)然采用樂觀的思想。在觸發(fā)YoungCG的時候檬寂,放不下了终抽,需要去Old區(qū)了,我就去檢查一次Old區(qū)的空間桶至,再來觸發(fā)oldGC昼伴。同時多說一句,在觸發(fā)OldGC的時候镣屹,說明我們的內(nèi)存空間很緊張了圃郊,那么我們可以近似的認(rèn)為,這次GC一定是一次FullGC女蜈。

老年代的GC

我們考察一個高級語言持舆,那么他的延遲就必須很低。早期java被人詬病的就是我們GC伪窖,因為GC的時候通常需要stop work吏廉,所以說會很慢。為什么呢惰许?

因為我們要知道,很多的GC器都是代整理功能的史辙,但是這樣帶來一個問題汹买,就會導(dǎo)致我們正在引用這些對象的指針變換佩伤,所有我們必須停下這些工作,來去GC晦毙,并且給正在引用的對象分配的變量生巡,附上正確的地址。

有人會問见妒,那為什么在老年代這個問題才會嚴(yán)重孤荣,新生代難道不需要嗎?要知道復(fù)制算法是十分快速的须揣,這就意味著盐股,及時有stop work,那也是很短的耻卡》柚基于這樣的場景,我們介紹老年代的兩種算法卵酪。

  • 標(biāo)記清楚算法

    • 產(chǎn)生碎片

    • 效率稍低

    • 兩邊掃描:1幌蚊、掃描那些位置上游對象2、標(biāo)記那些對象為存貨對象

    以前很多系統(tǒng)的維護就是重啟溃卡,為什么呢溢豆,因為重啟會使得對象整齊的排列在內(nèi)存中,大對象將便于存放瘸羡。

  • 標(biāo)記整理算法

    • 沒有內(nèi)存碎片

    • 效率很低

    • 兩次掃描漩仙,同時需要指針調(diào)整。

分配在棧上的對象

我們在堆上的對象最铁,用在棧上只能拿到引用讯赏,那么為什么不把對象直接放在棧上呢?

當(dāng)然可以了冷尉,只不過需要滿足幾個條件:

  • 首先漱挎,是我們這個對象要比較小,畢竟椚干冢空間有限磕谅,一般只有1M,

  • 其次雾棺,我們需要保證這個對象膊夹,不會被其他線程用的到。

滿足這些條件捌浩,JVM就認(rèn)為這個對象無論如何也逃不出這個線程放刨,那么就可以認(rèn)為這個對象是這個線程特有的,這個叫做逃逸分析尸饺,因此這個時候就可以吧對象分配在棧中間中进统。

那么也可以舉一個離子來看

/**
 * -XX:+PrintGC 
 * 
 * 
 * -XX:+DoEscapeAnalysis
 */
public class StackObject implements Runnable {
?
 public String name;
?
 public String getName() {
 return name;
 }
?
 public void setName(String name) {
 this.name = name;
 }
?
 public static void main(String[] args) {
?
?
 for (int i = 0; i < 20000000; i++) {
 StackObject object = getObject();
//            System.out.println(object.getName());
//            Thread thread = new Thread(new StackObject());
//            thread.run();
 }
 }
?
?
 public static  StackObject getObject(){
 StackObject stackObject = new StackObject();
 return stackObject;
 }
?
 @Override
 public void run() {
 getObject();
 }
}

如果是我們把-XX:-DoEscapeAnalysis助币,逃逸分析關(guān)閉,同時打印GC日志螟碎,那么就會發(fā)生GC眉菱,因為所有的對象都產(chǎn)生在堆上,但是如果是按照J(rèn)VM的默認(rèn)配置掉分,就沒有GC日志俭缓。因為這樣的棧上分配的對象,不僅僅可以減少內(nèi)存的消耗酥郭,同事不需要GC华坦,而且更快。此外private byte[] bytes = new byte[10*1024*1024];
添加一個大對象褥民,及時在開啟了逃逸分析的情況下季春,也還是有了GC日志,說明棧上的對象不能太大消返。

十二载弄、幾種GC器

前文對對象的分配和GC的主流算法做了介紹,下面介紹幾種常見的垃圾回收器

  • Serial和Serial Old

    早期的單線程的GC器撵颊,因為在分帶模型下宇攻,新生代采用復(fù)制回收算法,不在贅述倡勇。Serial Old標(biāo)記整理算法逞刷。很慢。只適用于幾十兆到幾百兆妻熊,STW很高

  • Parallel Scavenge和Paraller Old

    多線程的回收方式夸浅。JDK1.8的默認(rèn)配置

image-20200810201234265.png

匹配這種GC的兩種方式
image-20200810201453916.png

吞吐量 = 運行代碼的時間/(運行代碼時間+GC時間)
image-20200810201809528.png

可以根據(jù)我們的當(dāng)前內(nèi)存大小自適應(yīng)的調(diào)整JVM的內(nèi)存空間扔役。這樣可以保證我們的圖圖量帆喇。
注:此處的多線程并不是指我們一個線程去跑業(yè)務(wù)代碼,一個線程去跑GC亿胸,這個GC的場景還是發(fā)生在STW的場景下坯钦,此處的多線程是指:使用線程去標(biāo)記內(nèi)存,去整理內(nèi)存

  • CMS第一款真正的并發(fā)垃圾回收期侈玄,和Parallel New配對

    全程Concurrent Mark Sweep,如何做到并發(fā)清楚的呢婉刀?

    image-20200810203530438.png
    • 1、首先暫停所有線程序仙,進行初始表突颊,這次標(biāo)記只標(biāo)記根對象所引用的對象,這個速度是很快的,這就像是大樹(JVM)上分出了幾根主干道(根對象的引用)洋丐,那么我們數(shù)清楚有多少主樹干是很快的呈昔。

    • 2、第二部就是去查看這些主樹干上面又有多少引用的對象友绝,就像是要去數(shù)有多少分叉,這樣的時間是很漫長的肝劲,但是沒關(guān)系迁客,這個時候我們不需要STW,可以和業(yè)務(wù)線程同時進行辞槐。

    • 3掷漱、暫停程序,再次標(biāo)記榄檬,為什么需要這樣能卜范,因為在我們上一步標(biāo)記的時候,業(yè)務(wù)線程還在不斷的產(chǎn)生垃圾對象鹿榜,因此想要徹底的清除海雪,必須要暫存并且徹底標(biāo)記,當(dāng)然了舱殿,這一步是很快的奥裸。

    • 4、接下來就是將剛才標(biāo)記的垃圾對象回收沪袭。

    那么CMS采用的算法是標(biāo)記清除算法湾宙。那么在依據(jù)上面的幾步我們發(fā)現(xiàn)了CMS有幾種問題:

    • CPU敏感,在某一時刻冈绊,對CPU的需求會驟然增高侠鳄,因為在多線程的處理,但是隨著計算機算力的不斷加強死宣,這個問題也會越來越小伟恶。

    • 浮動垃圾

      在第三部并發(fā)處理垃圾的時候,還是有可能繼續(xù)產(chǎn)生垃圾對象的十电。那么這個時候產(chǎn)生的垃圾對象知押,在CMS本次走完后還是沒有足夠的空間放下,那該怎么辦呢鹃骂?就是采用最原始的Serial Old的算法初烘,這可能是CMS最大的弱點吧。

    • 內(nèi)存碎片

      如果太多的內(nèi)存碎片也會開始使用Serial Old來整理浴讯。

  • G1垃圾回收器

    雖然CMS有很多的問題椭坚,但是還是為我們提供了一些并發(fā)清理的思想,那么G1又為我們提供了新的一種思想。

    G1雖然也是使用的分帶劃分蒿叠,但是G1將內(nèi)存換分為一個一個的邏輯區(qū)域稱之為Region明垢,這樣的好處是:

    • 我們可以預(yù)測什么時候去做停頓,哪一個部分回收的效率最高市咽。

    • 當(dāng)一個From區(qū)的對象要晉升到老年代的時候痊银,只需要將這個區(qū)域的標(biāo)致改變一次來提高效率

    • 同時提供了Humongous區(qū),存放大對象施绎。

    運行過程


    image-20200810214935829.png

    和CMS很相似溯革,圖上的不同點就是篩選回收是需要STW的,因為是標(biāo)記整理算法谷醉,需要改變對象地址的致稀。但是,在此之前是進過篩選算法計算出需要回收的地址俱尼,目標(biāo)很明確抖单,所有會很快。

    G1中用到的兩種技術(shù)概念:

    • TAMS

      去標(biāo)記在并發(fā)標(biāo)記時新產(chǎn)生的對象遇八,因為這些對象及時不能確認(rèn)是否為垃圾對象矛绘,但應(yīng)該首先去標(biāo)記為一種待定的狀態(tài)

    • SATB

      一種快照的算法。

    如何選擇GC方法呢押蚤?當(dāng)堆空間大于6G的時候建議使用G1

    image-20200810221556616.png

十三蔑歌、GC器的實現(xiàn)細節(jié)

上一節(jié)講了幾種GC器的工作原理,其中有一個很重要的概念就是解決了一大部分STW的并發(fā)標(biāo)記揽碘。那么CMS和G1是如何實現(xiàn)的次屠,主要靠的是一種三色標(biāo)記。

三色標(biāo)記

image-20200812205832137.png

三色標(biāo)記我的理解是雳刺,將線程對掃描對象的三種狀態(tài):

  • 1.已掃描(黑色)

  • 2.未掃描(白色)

  • 3.正在掃描(灰色)

那么這種標(biāo)記算法和業(yè)務(wù)線程是同時在跑的劫灶,就有可能產(chǎn)生引用的改變,所有就會產(chǎn)生漏標(biāo)記的可能掖桦。注意:我們此處的漏標(biāo)記本昏,并非上一節(jié)所說的業(yè)務(wù)線程產(chǎn)生的其他垃圾我們沒有去標(biāo)記,這種算法是去標(biāo)記有用的對象枪汪。

那么我們?nèi)绾谓鉀Q三色標(biāo)記所帶來的漏標(biāo)問題呢涌穆?

還記得我們上節(jié)所說的最終標(biāo)記嗎?就是來解決這一步驟的雀久。

  • CMS中的解決方案

    CMS中是采用了Incremental Update 算法宿稀,例如:上圖鎖展示的一樣,在線程1已經(jīng)結(jié)束掃描后赖捌,C有被A對象引用了祝沸,那么C此時根可達,如果不做任何處理的話,C對象就會被當(dāng)做垃圾對象處理掉罩锐。所有這種算法奉狈,在引用增加的時候,將A對象重新改變?yōu)榛疑螅@樣仁期,我們?nèi)珮?biāo)記算法,就會認(rèn)為該線程的任務(wù)還沒有完結(jié)嗎竭恬,需要重新掃描蟀拷。那么又會從此處的根節(jié)點開始,掃描一遍萍聊。這種算法就像是C是個良民,在入境時主動地申報自己的身份悦析,有我們的公職人員檢查無誤后寿桨,從而不被殺死

    具體C++層面的實現(xiàn)是:

    將并發(fā)標(biāo)記過程中所有業(yè)務(wù)線程新增的代碼引用都放入一個集合(使用了寫屏障强戴?亭螟??不懂具體是什么骑歹,但是看大概的意思就是在一個方法執(zhí)行前做了什么预烙,方法的執(zhí)行后做了什么,想AOP一樣)道媚,在重新標(biāo)記的時候扁掸,在將這些引用的源頭找到,標(biāo)記為灰色最域,然后再次掃描谴分。

  • G1中的解決方案

    SATB ,一種快照技術(shù)镀脂,大家都知道快照的基本思想,以GIT為例->所有的文件(歷史文件)有文件數(shù)=樹牺蹄,我們的文件在服務(wù)器發(fā)生變換,例如:被刪除薄翅,并不是真的被刪掉了沙兰,而是改變成了一種我們不可見的狀態(tài),那么這樣的好處就是:我不必關(guān)系原有的不變的翘魄,這些使用的都是和老版本的一樣的東西鼎天,我關(guān)注的是有變化的東西,那么這樣我們就可以很快的找到熟丸,那些對象的引用變化了训措,這些對象是否還是根可達,不必想CMS一樣再次掃描一遍。

從這個算法層面講G1顯然效率更高一些绩鸣。

卡表和記憶集

什么是卡表怀大?我們需要知道為什么需要卡表?

原因是:我們的對象很可能出現(xiàn)跨帶引用的現(xiàn)象呀闻,即:老年代中的對象引用新生代的化借,反之也是。那么我們難道因為根可達的原因要將所有的磁盤都掃描一次嗎捡多?不現(xiàn)實蓖康,太慢了,因為前文也說過垒手,我們說的CMS和G1都是發(fā)生在老年代中的回收蒜焊。那么這種引用關(guān)系就是靠著卡表來維護。

那么我們在進行YGC的時候科贬,出現(xiàn)了跨帶引用的對象是不能直接被GC掉的泳梆,但是如果不做任何處理的話,就會出現(xiàn)有很多本該被這次YGC回收掉的對象不能回收的情況榜掌,為了解決這種問題我們出現(xiàn)了記憶集优妙,記憶集,顧名思義:就是將我們這種跨帶引用對象的引用記錄下來憎账。如果具體的地址記錄下來會造成很大的空間浪費套硼,因為地址的值很大,記錄這些值就需要很大的空間胞皱,同時這種精確標(biāo)記也很不靈活邪意。那么記憶集中存儲的內(nèi)容大概就是:1->x引用,2->y引用朴恳,那么前面想key的這個東西是什么呢抄罕?這個其實就是卡表的索引,那么介紹下卡表的概念:在G1中我們將old區(qū)均勻的分成大小相等的幾部分(所以G1的最小單位Reigon大小是2的冪次方)于颖,比如說0-1024這里就位0呆贿,1025-2048就位1。那么這樣森渐,我們在YoungGC的時候就完全不需要去掃描全部的Old區(qū)的對象做入,我們只需要掃描出現(xiàn)跨帶引用的對象,這樣就提高了效率同衣。

image-20200816162408464.png

那么G1是分成了多個獨立的小塊竟块,所有記憶集存在于每個小塊中,所有集G1需要我們的堆空間大于6M的情況下使用耐齐,因為G1造成了空間的浪費浪秘。同事G1在分配對象的時候蒋情,會尋找每個Region是空的塊,那么當(dāng)Region被占用的空間大于一半的時候耸携,就會將自身標(biāo)記為非空棵癣,就不能再上面分配對象了。所有這也是造成一部分空間的浪費夺衍。

十四狈谊、安全點和安全區(qū)

安全點

在STW的時候,不能粗暴的直接暫停所有的線程沟沙,因為當(dāng)一個對象正在被創(chuàng)建河劝,但是他還暫時有沒有被引用,我們無法判斷他是否需要beiGC矛紫,所有做法是赎瞎,在GC開啟時,想所有線程發(fā)出一個信號颊咬,這個時候煎娇,在跑的線程將會主動的尋找到最近的一個安全點,然后掛起贪染,等待GC結(jié)束后重啟。

那么那些點可以設(shè)置為安全點呢催享?理論杭隙,safepoint可以放在任何字節(jié)碼的邊界,但是這些會占用額外的空間因妙,所有只選取一些特定的地點設(shè)置為安全點:方法調(diào)用痰憎,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)等攀涵。

安全區(qū)

sleep或者blocked狀態(tài)下的線程無法獲取GC給的STW的信息铣耘,也就無法跑到安全點,這個時候就需要安全區(qū)以故,一段不會發(fā)生引用變化的區(qū)域蜗细,在線程進入安全區(qū)時,標(biāo)識自己進入安全區(qū)怒详,如果在這段時間里發(fā)生GC炉媒,這些線程可以不必等GC完結(jié),可以繼續(xù)執(zhí)行到安全區(qū)的底部昆烁,等待GC完成吊骤。

其他GC器

GC器有3個指標(biāo):占用內(nèi)存的大小、吞吐量静尼、延遲

  • Eplison白粉,不做垃圾回收的GC器传泊,只用于調(diào)試

  • ZGC,有和G1一樣的Region鸭巴,但是沒有分帶眷细,采用染色指針來尋找對象,低延遲(10ms)奕扣,只有短暫的STW薪鹦,因為GC root還是需要掃描,只于GC ROOT的數(shù)量有關(guān)

  • Shenandoah惯豆,非Oracle的GC器池磁,100ms延遲,也是染色指針

-Xmn -Xms -Xmx –Xss 年輕代 最小堆 最大堆 椏蓿空間 
-XX:+UseTLAB 使用 TLAB地熄,默認(rèn)打開 
-XX:+PrintTLAB 打印 TLAB 的使用情況 
-XX:TLABSize 設(shè)置 TLAB 大小
-XX:+DisableExplicitGC 啟用用于禁用對的調(diào)用處理的選項 System.gc() 
-XX:+PrintGC 查看 GC 基本信息 
-XX:+PrintGCDetails 查看 GC 詳細信息 
-XX:+PrintHeapAtGC 每次一次 GC 后,都打印堆信息 
-XX:+PrintGCTimeStamps 啟用在每個 GC 上打印時間戳的功能 
-XX:+PrintGCApplicationConcurrentTime 打印應(yīng)用程序時間(低) 
-XX:+PrintGCApplicationStoppedTime 打印暫停時長(低) 
-XX:+PrintReferenceGC 記錄回收了多少種不同引用類型的引用(重要性低) 
-verbose:class 類加載詳細過程 
-XX:+PrintVMOptions 可在程序運行時芯杀,打印虛擬機接受到的命令行顯示參數(shù) 
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 打印所有的 JVM 參數(shù)端考、查看所有 JVM 參數(shù)啟動的初始值(必須會用) 
-XX:MaxTenuringThreshold 升代年齡,最大值 15, 并行(吞吐量)收集器的默認(rèn)值為 15揭厚,而 CMS 收集器的默認(rèn)值為 6却特。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市筛圆,隨后出現(xiàn)的幾起案子裂明,更是在濱河造成了極大的恐慌,老刑警劉巖太援,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽晦,死亡現(xiàn)場離奇詭異,居然都是意外死亡提岔,警方通過查閱死者的電腦和手機仙蛉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碱蒙,“玉大人荠瘪,你說我怎么就攤上這事∪停” “怎么了巧还?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坊秸。 經(jīng)常有香客問我麸祷,道長,這世上最難降的妖魔是什么褒搔? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任阶牍,我火速辦了婚禮喷面,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘走孽。我一直安慰自己惧辈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布磕瓷。 她就那樣靜靜地躺著盒齿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪困食。 梳的紋絲不亂的頭發(fā)上边翁,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音硕盹,去河邊找鬼符匾。 笑死,一個胖子當(dāng)著我的面吹牛瘩例,可吹牛的內(nèi)容都是我干的啊胶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼垛贤,長吁一口氣:“原來是場噩夢啊……” “哼焰坪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起聘惦,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤琳彩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后部凑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡碧浊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年涂邀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箱锐。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡比勉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驹止,到底是詐尸還是另有隱情浩聋,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布臊恋,位于F島的核電站衣洁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抖仅。R本人自食惡果不足惜坊夫,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一砖第、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧环凿,春花似錦梧兼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至到推,卻和暖如春考赛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背环肘。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工欲虚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悔雹。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓复哆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腌零。 傳聞我的和親對象是個殘疾皇子梯找,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355