JVM內(nèi)存模型系列(堆、方法區(qū)以及對象和GC)

上文已經(jīng)介紹了JVM內(nèi)存模型中線程私有的部分(虛擬機棧瞄桨、本地方發(fā)棧、程序計數(shù)器)讶踪,那么本篇文章就來探討下JVM中線程共享的區(qū)域:堆芯侥、方法區(qū)

方法區(qū)

方法區(qū)是JVM規(guī)范中定義的一個邏輯內(nèi)存,在JDK1.8以前對方法區(qū)的實現(xiàn)叫做“永久代”乳讥,然而在JDK1.8以后講“永久代”廢棄柱查,改為“元空間”對其方法區(qū)進(jìn)行實現(xiàn),并且存儲位置是本地內(nèi)存云石,但是它依舊是JVM的邏輯區(qū)域唉工。方法區(qū)存儲了每一個類的結(jié)構(gòu)信息 、常量留晚、靜態(tài)變量酵紫、即時編譯器編譯后的代碼緩存等數(shù)據(jù)告嘲。


在這里插入圖片描述

常量池

靜態(tài)常量池

Class文件中除了有類的版本错维、字段、方法橄唬、接口等信息外赋焕,還有一個常量池用于存放編譯器間生成的字面量和符號引用。這些內(nèi)容在類加載完成后轉(zhuǎn)存到運行時常量池中仰楚。

  • 字面量:給基本類型變量和包裝類隆判、字符串賦值的方式就是字面量犬庇,比如int a = 1,這里的1就是字面量;比如String b = "b",這里的"b"就是字面量侨嘀。
  • 符號引用:符號引用是以一組符號來描述引用的目標(biāo)臭挽。因為在java文件編譯成class文件的時候,虛擬機并不知道字段所引用的對象的實際內(nèi)存地址咬腕,就用符號引用來替代欢峰,等到類加載解析階段將其符號引用轉(zhuǎn)為內(nèi)存地址的直接引用。符號引用是可以以任何形式的字面量涨共,比如類的全限定名纽帖。只要能唯一定位到目標(biāo)位置都可以。

運行時常量池

運行時常量池具有動態(tài)特性举反,它是每一個類(或接口)在運行時的表現(xiàn)形式懊直,它存儲了類加載解析后的符號引用和字面量。在JDK1.7以后將其實際內(nèi)存移入堆中火鼻,但邏輯上仍屬于方法區(qū)室囊。

字符串常量池

在JDK1.8后字符串常量池劃入堆內(nèi)存,因為String類型的對象在開發(fā)過程中使用的頻率和它的內(nèi)存結(jié)構(gòu)特殊魁索,所以JVM開發(fā)者設(shè)計了字符串常量池來提升性能波俄。

堆內(nèi)存作為JVM內(nèi)存模型中占比最大的空間它的重要性顯而易見。下文的對象創(chuàng)建和GC算法蛾默、GC收集器都和堆內(nèi)存息息相關(guān)懦铺。我們程序中幾乎所有(不是全部)對象都存放在堆內(nèi)存中,堆內(nèi)存也是支持動態(tài)伸縮支鸡,當(dāng)申請不了更多的可用內(nèi)存時便會拋出OOM冬念。我們可以通過JVM啟動參數(shù)對其設(shè)置內(nèi)存大小:
-Xms 堆的最小內(nèi)存牧挣,例 -Xms512m
-Xmx 堆的最大內(nèi)存
-Xmn 堆的新生代大小
-XX:NewSize 新生代最小值
-XX:MaxNewSize 新生代最大值


在這里插入圖片描述

對象的內(nèi)存布局

在這里插入圖片描述

一個對象在堆內(nèi)存中的內(nèi)存布局可以分三塊

  • 對象頭:1.存儲對象在運行時的數(shù)據(jù)急前,比如哈希碼、GC分代年齡瀑构、鎖狀態(tài)標(biāo)志裆针、偏向線程ID等等,這部分區(qū)域稱之為Mark Word寺晌。2.類型指針:JVM通過這個指針來確定此對象屬于哪個類的實例世吨。3.如果對象是一個數(shù)組,還會存儲數(shù)組的長度信息呻征。
  • 實例數(shù)據(jù):存放的就是我們代碼定義的實例變量所對應(yīng)的數(shù)據(jù)和父類繼承下來的實例變量耘婚,實例數(shù)據(jù)的順序受我們編碼順序相關(guān)。
  • 對齊填充:沒有特殊的含義陆赋,僅是為了起到占位符的作用沐祷。JVM管理對象嚷闭,對象的大小必須是8字節(jié)的整數(shù)倍,方便管理赖临,如果對象的大小不是8的整數(shù)倍就需要對齊填充之最近的8整數(shù)倍胞锰。


    在這里插入圖片描述

對象的訪問/定位

我們Java程序員常說的對象引用,就是通過虛擬機棧中reference數(shù)據(jù)來定位堆內(nèi)存中的對象兢榨。因為JVM規(guī)范中并沒有指定這個引用通過什么方式去實現(xiàn)胜蛉,所以目前定位方式大致分為兩種:

1.句柄

在這里插入圖片描述

2.直接引用

在這里插入圖片描述

目前HotSpot虛擬機默認(rèn)采用直接引用方式,更加方便色乾。但是并不代表句柄方式不適合誊册,句柄也有很大的好處,就是句柄存儲的是穩(wěn)定的句柄地址暖璧,當(dāng)對象被移動的時候不需要修改棧中的數(shù)據(jù)(垃圾收集會涉及對象地址的移動)案怯。而使用直接引用方式最大的好處就是訪問速度更快,節(jié)省了一步尋址的時間開銷澎办。

對象的創(chuàng)建和內(nèi)存分配

在這里插入圖片描述

對象的創(chuàng)建分為兩種模式:1.指針碰撞嘲碱。2.空閑列表。

1.指針碰撞

如果堆內(nèi)存中的空閑內(nèi)存是絕對整齊的(用的的內(nèi)存放一邊局蚀,空閑的放另一邊)麦锯,此時JVM在創(chuàng)建對象分配內(nèi)存的時候只需要把指針向空閑的區(qū)域移動指定大小(對象的大小在類加載的時候已經(jīng)可以確定,且是8字節(jié)的整數(shù)倍大小)琅绅。這種分配方式稱之為指針碰撞扶欣。

在這里插入圖片描述

2.空閑列表

空閑列表相對于指針碰撞要復(fù)雜很多,如果堆內(nèi)存中可用內(nèi)存不是整齊的那么就不能使用指針碰撞這種方式千扶,那就JVM就需要額外維護一個數(shù)據(jù)池料祠,用來記錄內(nèi)存中哪些地址是可用的內(nèi)存。當(dāng)分配的時候從數(shù)據(jù)池中找出足夠的空間用來分配對象澎羞,并且更新數(shù)據(jù)池里的數(shù)據(jù)記錄髓绽,這種分配方式稱之為空閑列表

那么問題來了妆绞,JVM到底選擇哪種模式進(jìn)行的對象分配呢顺呕?

選擇哪種方式是由堆內(nèi)存的整齊度決定,而內(nèi)存的整齊度取決于采用的是哪種GC收集器是否帶有空間壓縮整理功能決定括饶。當(dāng)使用Serial株茶、Paraller Scavenge、ParNew等收集器時系統(tǒng)采用指針碰撞巷帝,如果使用CMS收集器只能用空閑列表方式忌卤。每種垃圾收集器的區(qū)別下文中解答扫夜。

對象分配并發(fā)安全問題

Java程序天生是多線程的楞泼,如果一個創(chuàng)建對象的程序處于多線程并發(fā)執(zhí)行驰徊,那么就會產(chǎn)生線程安全問題,試想:如果A線程準(zhǔn)備將對象分配在堆內(nèi)存的x_0001處堕阔,此時B線程也執(zhí)行到創(chuàng)建對象,在申請空間的時候發(fā)現(xiàn)x_0001處未分配....后續(xù)的問題就不用說了棍厂。那么JVM是怎么解決這個問題的呢?

1.CAS無鎖機制

這種方式最易想到超陆,采用鎖機制進(jìn)行線程并發(fā)問題可以很好地解決牺弹。


在這里插入圖片描述

但是畢竟全局使用CAS鎖機制,如果在高并發(fā)情況而且創(chuàng)建對象的時間比較長时呀,那么這種方式性能的問題就凸顯了出來张漂,于是第二種方式孕育而生。

2.TLAB(本地線程分配緩沖Thread Local Allocation Buffer)

TLAB顧名思義谨娜,JVM將每個線程在堆中預(yù)先分配一塊私有內(nèi)存空間 航攒,這用每個獨立的線程都有屬于自己的Buffer,如果有對象需要分配那么就會在自己的Buffer中創(chuàng)建對象,當(dāng)Buffer用滿了以后就會重新分配緩沖區(qū)趴梢,此時這個階段是需要CAS鎖機制保證安全漠畜。JVM可以通過制定啟動參數(shù)-XX:+/-UseTLAB來指定。

內(nèi)存空間初始化

對象分配內(nèi)存完成之后坞靶,JVM需要將對象的實例數(shù)據(jù)賦予默認(rèn)值憔狞,比如對象類型賦值null,基本類型賦值默認(rèn)值彰阴,例如int -> 0,boolean -> false瘾敢。

設(shè)置

JVM還需要堆對象進(jìn)行必要的設(shè)置,例如這個對象屬于哪個類的實例尿这、GC分代年齡等,這些信息存儲在對象頭中廉丽。需要注意的是對象的哈希碼并不是在此刻計算(實際上的哈希碼會延后到真正調(diào)用Object::hashCode()方法時才計算賦值)

初始化

執(zhí)行構(gòu)造方法,實例變量賦真實值妻味。

對象的引用和分配策略

對象的引用

在JDK1.2以后正压,Java對引用的概念擴充為4類:

  • 強引用:例如Object o = new Object()方式都屬于強引用,只要強引用關(guān)系存在责球,那么所引用的對象都不會被回收
  • 軟引用(SoftReferenct):指定一些有用但是非必須的引用關(guān)系焦履,如果系統(tǒng)將要發(fā)生OOM之前,這些引用的對象將被回收雏逾。
/**
 * -Xms20m   -Xmx50m
 * @author Minor
 */
public class JvmDemo {
    public static void main(String[] args) {
        Student student = new Student("張三",24);
        // 創(chuàng)建軟引用關(guān)系
        SoftReference<Student> softReference = new SoftReference<>(student);
        // 此時嘉裤,將student對象置為null
        student = null;
        // 第一次GC,看看是否會空指針
        System.gc();
        System.out.printf(softReference.get().toString());

        // 模擬OOM
        List<byte[]> oomList = new LinkedList<>();
        try {
            for (;;){
                oomList.add(new byte[1024*1024*10]);
            }
        }catch (OutOfMemoryError error){
            System.out.printf(softReference.get().toString());
        }
    }

    @Data
    private static class Student{
        private String name;
        private Integer age;

        Student(String name ,Integer age){
            this.name = name;
            this.age = age;
        }
    }
}

我們來看看軟引用的結(jié)果:


在這里插入圖片描述
  • 弱引用(Weak Rererence):比軟引用更弱一級的引用關(guān)系栖博,此關(guān)系引用給的對象在下一次垃圾回收的時候必定被回收屑宠。
/**
 * -Xms20m   -Xmx50m
 * @author Minor
 */
public class JvmDemo {
    public static void main(String[] args) {
        Student student = new Student("張三",24);
        // 創(chuàng)建弱引用關(guān)系
        WeakReference<Student> softReference = new WeakReference<>(student);
        // 此時,將student對象置為null
        student = null;
        System.gc();
        System.out.printf(softReference.get().toString());

    }

    @Data
    private static class Student{
        private String name;
        private Integer age;

        Student(String name ,Integer age){
            this.name = name;
            this.age = age;
        }
    }
}

我們來看看弱引用的結(jié)果:


在這里插入圖片描述
  • 虛引用:又稱作“幽靈引用”仇让,它是Java中最弱的一種引用關(guān)系典奉,目的只是為了能在這個對象被回收時收到一個系統(tǒng)通知躺翻。

對象的分配策略

1.棧上分配

上文說到,對象“幾乎”都是在堆內(nèi)存中存儲的卫玖,但是也有例外公你。

  • 逃逸分析:分析對象的作用域,如果一個對象在方法中定義后它可以被外部方法所引用假瞬,那么此對象成為逃逸對象陕靠。否則,對象無法逃逸方法的作用域脱茉,如果一個對象不會逃逸出方法之外剪芥,那么在對象分配的時候可以考慮在虛擬機棧上分配對象,提高效率琴许。因為此類對象不會逃逸出方法中粗俱,方法執(zhí)行完畢,棧幀出棧虚吟,線程結(jié)束時對象也跟著消亡寸认,不用垃圾回收。

2.優(yōu)先在新生代Eden區(qū)分配

在大部分情況下對象是在Eden區(qū)中分配串慰,如果此時Eden區(qū)沒有足夠的空間進(jìn)行分配時將會發(fā)生新生代的一次回收Young GC/Minor GC偏塞。

3.大對象直接進(jìn)入老年代

大對象指的是需要大量連續(xù)空間的Java對象,比如大的數(shù)組,很長的字符串(底層也是數(shù)組)。HotSpot虛擬機提供了-XX:PretenureSizeThreshold參數(shù)來設(shè)置指定大于該值的對象直接分配在老年代邦鲫。避免了大的對象在新生代中來回復(fù)制產(chǎn)生性能消耗(后文會講到GC回收算法)
設(shè)計目的:1.避免頻繁的內(nèi)存復(fù)制造成性能消耗.
2.避免提前垃圾回收
注意:-XX:PretenureSizeThreshold參數(shù)只能對Serial和ParNew收集器生效灸叼。

4.長期存活的對象直接進(jìn)入老年代(分代年齡滿15)

每個對象都已一份對象頭,對象頭中存儲著一份對象的分代年齡庆捺。目前主流的新生代垃圾回收算法采用復(fù)制算法古今,那么對象在Eden區(qū)出生并經(jīng)過第一次Minor GC時如果對象還存活,那么對象的分代年齡+1滔以,然后將其放入幸存區(qū)捉腥。幸存區(qū)有兩個大小相等的區(qū)域From和To,每一次Minor GC以后你画,如果對象仍然存活那么對象就會在From和To區(qū)之間來回復(fù)制抵碟,每次分代年齡+1。當(dāng)對象的分代年齡達(dá)到15坏匪,那么對象將進(jìn)入老年代拟逮。

5.動態(tài)年齡判斷進(jìn)入老年代

顧名思義,并不是所有的對象必須要求分代年齡滿15才能進(jìn)入老年代适滓。如果在幸存區(qū)中相同年齡的對象大小總和大于幸存區(qū)的50%時敦迄,幸存區(qū)內(nèi)>=該年齡的對象直接進(jìn)入老年代。

空間分配擔(dān)保:在發(fā)生 Minor GC 之前, 虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間罚屋, 如果這個條件成立苦囱, 那么 Minor GC 可以確保是安全的。 如果不成立沿后, 則虛擬機會HandlePromotionFailure 設(shè)置值是否允許擔(dān)保失敗沿彭。 如果允許朽砰, 那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小尖滚, 如果大于, 將嘗試著進(jìn)行一次 Minor GC瞧柔, 盡管這次 Minor GC 是有風(fēng)險的漆弄, 如果擔(dān)保失敗則會進(jìn)行一次 Full GC(回收整個堆和方法區(qū)); 如果小于造锅, 或者 HandlePromotionFailure 設(shè)置不允許冒險撼唾, 那這時也要改為進(jìn)行一次 Full GC。

對象存活的判定

棧上分配的對象因為和線程的生命周期一致所以不存在對象存活的判定哥蔚,也不涉及垃圾回收倒谷。

1.引用計數(shù)法

在對象中添加一個引用計數(shù)器,每當(dāng)有引用指向它時計數(shù)器+1糙箍,當(dāng)引用失效時就-1渤愁;任何對象只要計數(shù)器不為0,那么他就不會被回收深夯。
特點:

  • 原理簡單抖格,效率高
  • 存在循環(huán)引用問題,無法回收此類對象
    在這里插入圖片描述

    在Java領(lǐng)域引用計數(shù)法并沒有選擇此類型的算法咕晋,但是微軟的COM技術(shù)雹拄、Python語言在使用這種方式。

2.可達(dá)性分析算法

在這里插入圖片描述

目前主流的高級語言(例如Java掌呜、C#)都是通過可達(dá)性分析算法來確定對象是否可以被回收滓玖。此算法的基本思路就是通過一系列的GC Roots對象作為起點,從這個節(jié)點向下遍歷质蕉,形成一個引用鏈呢撞,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,那么此對象是無用對象需要回收饰剥。比如上圖中白色部分的對象殊霞,即便對象有引用,但是引用連追溯不到GC Roots汰蓉。
那么固定可作為GC Roots的對象包括以下幾種:

  • 虛擬機棧(本地變量表)中引用的對象绷蹲,棧中使用到的參數(shù)、局部變量、臨時變量祝钢。
  • 方法區(qū)中類變量(靜態(tài)變量)引用的對象比规。
  • 方法區(qū)中常量引用的對象,比如字符串常量池里的引用拦英。
  • 本地方法棧中JNI引用的對象(Native方法)蜒什。
  • JVM的內(nèi)部引用(Class對象、異常對象疤估、類加載器)灾常。
  • 被同步鎖(synchronized)持有的對象。
  • 反應(yīng)JVM內(nèi)部狀況的JMXBean铃拇、JVMTI中注冊的回調(diào)钞瀑、代碼緩存。
  • JVM運行時臨時性的對象慷荔,比如GC跨帶引用的對象雕什。

垃圾回收算法

名詞解釋:

  • 新生代垃圾回收Minor GC/Young GC
  • 老年代垃圾回收Major GC/Old GC
  • 全回收Full GC/回收整個堆和方法區(qū)

1.復(fù)制回收算法

在這里插入圖片描述

將內(nèi)存按照容量劃分為大小相等的A、B兩個區(qū)域显晶,每次只是用一個區(qū)域贷岸。當(dāng)A區(qū)域內(nèi)存用完,將存活著的對象復(fù)制到B區(qū)域,然后清空A區(qū)磷雇,這樣每次都是對半進(jìn)行內(nèi)存回收偿警。
特點:

  • 內(nèi)存利用率只有50%
  • 不存在內(nèi)存碎片問題
  • 復(fù)制對象時還需要修改對象的引用地址
    目前復(fù)制回收算法用于新生代,因為大部分對象的生命都很短暫倦春,需要復(fù)制的對象并不是很多所以效率相對較高户敬。

1.1JVM的Appel式回收

將堆內(nèi)存的新生代劃分為較大的Eden區(qū)和較小的兩個一樣大小的Survivor區(qū)(From、To)睁本,他們的比例在前文的圖中已經(jīng)說明是8:1:1尿庐,因為絕大部分的對象的生命很短暫,幸存區(qū)沒必要設(shè)置太大呢堰。

2.標(biāo)記-清除算法

此算法分為兩個階段:標(biāo)記清除

  • 標(biāo)記:根據(jù)可達(dá)性分析算法掃描出可回收的對象抄瑟,然后將對象進(jìn)行回收標(biāo)記。
  • 清除:再次掃碼被標(biāo)記回收的對象進(jìn)行垃圾回收枉疼。

特點:

  • 因為需要掃描兩遍皮假,效率上略低
  • 存在內(nèi)存碎片問題
  • 適用于老年代

內(nèi)存碎片

在這里插入圖片描述

使用標(biāo)記-清除算法如上圖所示,如果此時一個大對象(需要連續(xù)內(nèi)存)需要分配骂维,雖然堆內(nèi)存總的空間是夠用的惹资,但是并沒有連續(xù)的可用空間導(dǎo)致大對象放不下,提前造成GC航闺。

3.標(biāo)記-整理算法

此算法分為兩步:標(biāo)記和整理

  • 標(biāo)記:根據(jù)可達(dá)性分析算法掃描出可回收的對象褪测,然后將對象進(jìn)行回收標(biāo)記猴誊。
  • 整理:將無用對象進(jìn)行回收,將存活的對象往內(nèi)存的一端移動侮措,進(jìn)行空間整理懈叹。

特點:

  • 需要整理內(nèi)存,效率略低
  • 因為有整理分扎,所以沒有內(nèi)存碎片問題


    在這里插入圖片描述

因為涉及到對象的移動澄成,改變對象的引用地址,所以在整理的時候需要暫停用戶線程畏吓,加重系統(tǒng)負(fù)擔(dān)墨状,目前此算法用戶老年代的回收算法支持。

垃圾收集器

在這里插入圖片描述

1.Serial / Serial Old 收集器

JVM最早的垃圾收集器庵佣,串行化執(zhí)行模式歉胶,它是一個單線程的收集器汛兜,適合內(nèi)從只有幾十到一兩百兆的堆空間進(jìn)行回收巴粪。因為它是單線程進(jìn)行垃圾回收所以用戶線程停頓十分明顯,目前JDK默認(rèn)已經(jīng)沒有使用此收集器粥谬,但是可以使用-XX+UseSerialGCz指定開啟串行收集器肛根。Serial使用復(fù)制回收算法進(jìn)行新生代回收,Serial Old使用標(biāo)記-整理算法進(jìn)行老年代回收工作漏策。

在這里插入圖片描述

Parallel Scavenge / Parallel Old 收集器

從JDK1.3開始派哲,JVM就采用了多線程的垃圾回收機制,Parallel收集器更關(guān)注系統(tǒng)的吞吐量掺喻,能有效的利用CPU時間盡快完成垃圾回收任務(wù)芭届,此收集器使用戶幾百到幾千兆的堆內(nèi)存空間進(jìn)行回收;可以通過-XX+UseParallelGC開啟此收集器感耙,JDK1.8默認(rèn)使用此垃圾收集器褂乍。Parallel Scavenge使用復(fù)制回收算法進(jìn)行新生代回收,Parallel Old使用標(biāo)記-整理算法進(jìn)行老年代回收工作即硼。

在這里插入圖片描述

ParNew 收集器

實質(zhì)上是Serial收集器的多線程版本逃片,它只負(fù)責(zé)新生代的垃圾回收,根據(jù)上文圖中的垃圾收集器分配圖來看只酥,它一般配合Serial和CMS配合使用褥实。在JDK9以后將其合并到CMS中。


在這里插入圖片描述

CMS 收集器

CMS收集器是一款追求最短回收停頓時間為目標(biāo)的收集器裂允。比較特殊的是损离,CMS收集器是回收老年代,而且是基于標(biāo)記-清除算法實現(xiàn)绝编,他的整個工作過程分為4步:

  • 初始標(biāo)記:此過程很短暫僻澎,僅僅只是標(biāo)記一下GC Roots直連的對象。
  • 并發(fā)標(biāo)記:和用戶線程同時進(jìn)行,此過程進(jìn)行GC Roots引用鏈的掃描怎棱,此過程相對于第一階段比較漫長哩俭。
  • 重新標(biāo)記:因為并發(fā)標(biāo)記階段和用戶線程共同執(zhí)行,導(dǎo)致標(biāo)記產(chǎn)生變動的一部分對象拳恋,相對于第二階段此階段過程比較快凡资。
  • 并發(fā)清除:和用戶線程同時執(zhí)行,清除標(biāo)記的對象谬运,完成垃圾回收工作隙赁。

我們可以使用-XX:UseConcmarkSweepGC開啟CMS老年代和ParNew新生代的回收器。
CMS收集器是一個具有劃時代意義的垃圾收集器梆暖,但是它也有缺點:

  • CPU資源敏感:采用并發(fā)手機機制伞访,當(dāng)處理器核心數(shù)比較少時,CMS工作時會對用戶體驗造成影響轰驳。
  • 浮動垃圾:因為CMS在并發(fā)清理階段用戶線程仍然一起執(zhí)行著厚掷,伴隨著程序運行當(dāng)然會有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后级解,CMS無法處理冒黑。所以CMS收集器會預(yù)留一部分內(nèi)存用來處理這一部分?jǐn)?shù)據(jù),在JDK1.6版本中老年代空間使用率的閾值是92%勤哗,如果預(yù)留的內(nèi)存不夠用的時候會出現(xiàn)Concurrent Mode Failure抡爹,導(dǎo)致新的Full GC產(chǎn)生,這是JVM會臨時啟用Serial Old來替代CMS芒划。
  • 內(nèi)存碎片:前文已經(jīng)提到,CMS是唯一一個采用標(biāo)記-清除算法進(jìn)行回收的收集器冬竟,所以會產(chǎn)生內(nèi)存碎片問題。
    之所以CMS采用標(biāo)記-清除算法是因為如果采用標(biāo)記-整理算法會涉及到對象內(nèi)存地址的移動民逼,此時需要暫停用戶線程來修改棧中的地址泵殴,這和CMS設(shè)計的初衷不符。
    在這里插入圖片描述

G1 收集器

有一個劃時代意義的收集器缴挖,因為傳統(tǒng)的垃圾收集器仍然對線程暫停的時間不可預(yù)測袋狞,為了實現(xiàn)線程暫停的時間變?yōu)榭深A(yù)測,G1收集器將堆內(nèi)存做了較大改變映屋,將其劃分為大小相等且獨立的區(qū)域Region苟鸯,每一個Region都可以根據(jù)需要扮演Eden、Survivor棚点、old早处。Region中有一類特殊的Humongous區(qū)域,專門用來存儲大對象瘫析,只要對象大小超過一個Region容量的一般即判定為大對象砌梆,如果對象更大將會放在多個連續(xù)的Humongous中默责,Humongous默認(rèn)當(dāng)做老年代來看待。我們可以通過-XX:+UseG1GC來開啟使用G1收集器咸包。G1新生代采用復(fù)制算法桃序,老年代使用標(biāo)記-整理算法

在這里插入圖片描述

在這里插入圖片描述

G1的過程分為以下幾步:

  • 初始標(biāo)記:僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象, 并且修改 TAMS 指針的值烂瘫, 讓下一階段用戶線程并發(fā)運行時媒熊, 能正確地在可用的 Region 中分配新對象。這個階段需要停頓線程坟比, 但耗時很短芦鳍, 而且是借用進(jìn)行 Minor GC 的時候同步完成的, 所以 G1 收集器在這個階段實際并沒有額外的停頓
    TAMS:要達(dá)到 GC 與用戶線程并發(fā)運行葛账, 必須要解決回收過程中新對象的分配柠衅, 所以 G1 為每一個 Region 區(qū)域設(shè)計了兩個名為 TAMS(Top at Mark Start) 的指針,
    從 Region 區(qū)域劃出一部分空間用于記錄并發(fā)回收過程中的新對象籍琳。 這樣的對象認(rèn)為它們是存活的菲宴, 不納入垃圾回收范圍。
  • 并發(fā)標(biāo)記:從 GC Root 開始對堆中對象進(jìn)行可達(dá)性分析巩割,遞歸掃描整個堆里的對象圖裙顽,找出要回收的對象付燥,這階段耗時較長宣谈,但可與用戶程序并發(fā)執(zhí)行。 當(dāng)對象圖掃描 完 成以后并發(fā)時有引用變動的對象键科,這些對象會漏標(biāo)漏 標(biāo)的對象會被一個叫做SATB(snapshot-at-the-beginning)算法來解決闻丑,我們以后來探討一下這個算法。
  • 最終標(biāo)記:對用戶線程做另一個短暫的暫停勋颖, 用于處理并發(fā)階段結(jié)后仍遺留下來的最后那少量的漏標(biāo)對象嗦嗡。
  • 篩選回收:負(fù)責(zé)更新 Region 的統(tǒng)計數(shù)據(jù), 對各個 Region 的回收價值和成本進(jìn)行排序饭玲, 根據(jù)用戶所期望的停頓時間來制定回收計劃侥祭, 可以自由選擇任意多個 Region 構(gòu)成回收集, 然后把決定回收的那一部分 Region 的存活對象復(fù)制到空的 Region 中茄厘, 再清理掉整個舊 Region 的全部空間矮冬。 這里的操作涉及存活對象的移動,是必須暫停用戶線程次哈, 由多條收集器線程并行完成的胎署。

G1收集器的特點:

  • 充分利用CPU資源,縮短用戶線程停頓時間窑滞。
  • 分代收集
  • 不存在內(nèi)存碎片琼牧,空間整齊
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恢筝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巨坊,更是在濱河造成了極大的恐慌撬槽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趾撵,死亡現(xiàn)場離奇詭異恢氯,居然都是意外死亡,警方通過查閱死者的電腦和手機鼓寺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門勋拟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妈候,你說我怎么就攤上這事敢靡。” “怎么了苦银?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵啸胧,是天一觀的道長。 經(jīng)常有香客問我幔虏,道長纺念,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任想括,我火速辦了婚禮陷谱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑟蜈。我一直安慰自己烟逊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布铺根。 她就那樣靜靜地躺著宪躯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪位迂。 梳的紋絲不亂的頭發(fā)上访雪,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死猪钮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肝陪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼刑顺,長吁一口氣:“原來是場噩夢啊……” “哼氯窍!你這毒婦竟也來了饲常?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤狼讨,失蹤者是張志新(化名)和其女友劉穎贝淤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體政供,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡播聪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了布隔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片离陶。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衅檀,靈堂內(nèi)的尸體忽然破棺而出招刨,到底是詐尸還是另有隱情,我是刑警寧澤哀军,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布沉眶,位于F島的核電站,受9級特大地震影響杉适,放射性物質(zhì)發(fā)生泄漏谎倔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一猿推、第九天 我趴在偏房一處隱蔽的房頂上張望片习。 院中可真熱鬧,春花似錦彤守、人聲如沸毯侦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至试幽,卻和暖如春筝蚕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铺坞。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工起宽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人济榨。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓坯沪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親擒滑。 傳聞我的和親對象是個殘疾皇子腐晾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345