java 虚拟机(jvm)垃圾回收(GC)算法

  • GC中的垃圾特指于内存中不会再使用的对象,垃圾回收有很多算法:

    • 引用计数法
    • 标记压缩法
    • 复制算法
    • 分代,分区的思想
  • 引用计数法

    • 古老而经典,但是有一些严重的问题
    • 核心是当对象被引用的时计数器加1,引用失效时减1
    • 问题:
      • 无法处理循环引用的情况
      • 每次进行加减操作比较浪费系统性能
  • 标记清除法

    • 分为标记和清除俩个阶段进行处理内存中的对象
    • 弊端:
      • 导致空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作小徐要低于连续的空间
  • 复制算法*

    • 核心思想是将内存空间分为俩块,每次只使用其中一块,垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中的所有对象,反复去交换俩个内存的角色,完成垃圾收集。
    • java新生代中的from和to空间就是使用这个算法
  • 标记压缩法*

    • 标记压缩在标记清除法之上做了优化,把存活的对象压缩到内存一端(避免空间碎片问题),而后进行垃圾清理
    • java老年代使用的是标记压缩算法
  • 为啥新生代和老年代使用不同的算法?

    • 年轻代对象因为生命周期短,每次有约90%以上对象的占用空间被回收,采用“复制-清除”算法清理,具体过程:
      将新生代分为一个Eden空间和两个Survivor空间,默认Eden空间和Survivor空间的比例为8:1,对象分配到Eden和其中一个Survivor空间,回收时将存活的对象复制到另一个Survivor空间,然后将Eden空间和先前使用的Survivor空间清理。
    • 老年代因对象生命周期较长,每次回收只有少部分对象没清理,如果使用“复制-清理”算法的话需要额外预留更多的空闲空间用于复制生存对象,( 例如,100M的老年代占用空间 每次能回收50% ,那么他需要预留50M的空间 内存使用上不经济 )所以回收时使用“标记-整理”算法。
  • 分代算法

    • 呵呵,看上一个问题:为啥新生代和老年代使用不同的算法?
  • 分区算法

    • 被oracle收购后,提出的新算法,应用还不广泛,还在摸索,G1使用的就是这个算法
    • 将整个内存分为N多个小的独立空间,每个小空间可以独立使用,这样细粒度的控制一次回收多少个小空间和那些小空间,而不是对整个空间进行GC,进而提升性能,减少GC的停顿时间
  • GC的停顿时间

    • 为了高效的让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态,停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态咋某一瞬间的一致性,也有益于更好的标记垃圾对象,因此在垃圾回收时,都会产生应用程序的停顿
  • 对象如何进入老年代

    • 对象首次创建时会被放在新生代的eden区,如果没有gc介入,则对象不会离开eden区,一般来讲,只要对象的年龄达到一定的大小,就会自动离开年轻代,进入老年代,对象的年龄是由对象经历gc的次数决定的,在新生代如果对象没有被回收则年龄加一,虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升到老年代,这个参数就是 -XX:MaxTenuringThreshold 默认情况下为15
    • 另外,大对象,即新生代eden区无法装入时,也会直接进入老年代,jvm有个参数可以设置对象的大小超过指定的大小后,直接晋升老年代-XX:PretenureSizeThreshold,但是要注意TLAB区域有限分配空间,虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此会失去在老年代分配的机会
  • 关于TLAB区

    import java.util.HashMap;
    import java.util.Map;
    
    public class Test{
    
        public static void main(String[] args) {
          
            //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
            //这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会
            //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB
            Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
            for(int i=0; i< 5*1024; i++){
                byte[] b = new byte[1024];
                m.put(i, b);
            }
        }
    }
    

    以上程序启动(注意加上jvm参数)之后你会看到老年代的使用率几乎为0,这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会,java的每个线程都是默认使用TLAB区的,如果要禁用,则加上jvm参数-XX:-UseTLAB,会看到老年代使用的内存大致为5M

    • TLAB区的全称是Thread Local Allocation Buffer,线程本地分配缓存,线程专用的内存分配区域,为了加速对象分配而生的,每个线程都有一个TLAB,来避免多线程冲突的问题,提高对象分配的效率,TLAB空间一般不会太大,当大对象无法再TLAB分配时,则会直接分配到堆上
    • -XX:+UseTLAB 使用TLAB
    • -XX:+TLABSize设置TLAB大小
    • -XX:TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,他是一个比例值,默认是64,即如果对象大于整个空间的1/64,则在堆创建对象
    • -XX:+PrintTLAB查看TLAB信息,要与-XX:DoEscape Analysis 配合使用,禁用逃逸分析参数
    • -XX:ResizeTLAB自动调整TLABRefillWasteFraction阈值
    • jdk1.7之后,TLAB区是自动调整的,不建议修改
  • 对象的创建流程

    • 尝试栈上分配,能分配则在栈上分配
    • 不能分配,尝试TLAB分配
    • 对象大小未超过TLAB阈值,分配到TLAB
    • 超过TLAB阈值,尝试在堆上分配,
    • 超过PretenureSizeThreshold(jvm参数,可以设置对象的大小超过指定的大小后,直接晋升老年代),分配到老年代
    • 否则在eden区分配
  • 垃圾收集器

    • 串行垃圾回收器
      使用单线程镜像垃圾回收的回收器,垃圾收集时只有一个工作线程,设置方式:-XX:+UseSerialGC,此时新生代和老年代都是使用串行垃圾回收器,对于并行能力较弱的计算机来说,串行的垃圾回收器会有更好的性能表现
    • 并行垃圾回收器
      • 使用多线程同时进行垃圾回收,对于并行能力较强的计算机而言,可以缩短垃圾回收所需的实际时间
      • ParNew回收器是一个工作在新生代的垃圾收集器,他只是简单的将串行回收器多线程化,他的回收策略和算法与串行回收器一样
        • 使用 -XX:+UseParNewGC,新生代使用ParNew回收器,老年代还是使用串行回收器
        • -XX:ParallelGCThreads可以指定工作线程数,一般和计算机的cpu个数相当,避免过多的线程影响性能
      • *新生代ParallelGC回收器,使用了复制算法的收集器,也是多线程独占形式的收集器,但ParallelGC回收器有个非常重要的特点--非常关注系统的吞吐量,提供了俩个非常关键的参数控制系统的吞吐量
        • -XX:MaxGCPauseMillis设置最大垃圾收集停顿时间,虚拟机会把GC的停顿时间控制在这个值以内,但是该值设置过小,会导致GC频繁,从而增加了GC的总时间.
        • -XX:GCTimeRatio设置吞吐量大小,他是个0到100之间的数,默认99,那么系统将花费不超过1/(1+n)的时间用于垃圾回收,也就是1/(1+99),即1%的时间
        • 另外还可指定-XX:+UseAdaptiveSizePolicy,打开自适应模式,这种模式下,新生代的大小,eden,from/to区的比例,以及晋升老年代的对象年龄参数会被自动调整,以达到在堆大小,吞吐量和停顿时间之间的平衡点
      • *老年代ParallelOldGC,多线程回收器,也是一种关注吞吐量的回收器,使用了标记压缩法进行实现
        • -XX:+UseParallelOldGC进行设置使用
        • -XX:+ParallelGCThreads设置垃圾收集时线程数量
    • CMS回收器(主流)
      • 全称为Concurent Mark Sweep ,并发标记清除,使用标记清除法,主要关注系统停顿时间
      • 使用-XX:+UseConcMarkSweepGC进行设置使用
      • 使用-XX:ConcGCThreads设置并发线程数量
      • CMS并不是独占的回收器,在CMS回收的过程中,应用程序仍然在不停的工作,同时会有新的垃圾产生,所以在使用CMS的时候应确保应用程序的内存足够可用,CMS不会等到应用程序饱和的时候才去回收垃圾,而是在某一阈值的时候开始回收,可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认是68,即当老年代的空间使用率达到68%的时候,会执行CMS回收,如果内存使用率增长的很快,在CMS执行的过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收,这会导致应用程序中断,知道垃圾回收完成后才会正常工作,这个过程GC的停顿时间可能较长,所以-XX:CMSInitiatingOccupancyFraction参数的设置要根据实际的情况
      • 标记清除法有个缺点是产生空间碎片,CMS有个参数:-XX:+UseCMSCompactAtFullCollection可以使CMS回收完成之后进行一次碎片整理,-XX:CMSFullGCsBeforeCompaction可以设置进行多少次CMS回收后,对内存进行一次压缩
    • G1回收器(发展中)
      • Garbage-First是jdk1.7中提出的垃圾回收器,从长期目标来说是为了取代CMS回收器,有独特的垃圾回收策略,G1属于分代垃圾回收器,区分新生代和老年代,依然有eden和from/to区,它并不要求整个eden区或者新生代,老年代的空间都连续,它使用了分区算法
      • 并行性:G1回收期间可以多线程同时工作
      • 并发性:G1拥有与应用程序交替执行能力,部分工作可以与应用程序同时执行,在整个gc期间不会完全阻塞应用程序
      • 兼顾新生代和老年代一起工作,之前的垃圾收集器他们或者在新生代或者老年代工作,这是一个很大的不同,
      • 空间整理:G1在垃圾回收过程中,不会像CMS那样若干次GC之后需要进行碎片整理,G1采用了有效复制对象的方式,减少空间碎片
      • 可预见性:由于分区的原因,G1可以只选取部分区域进行回收,缩小了回收的范围,提升了性能
      • 使用-XX:+UseG1GC 配置使用
      • 使用-XX:MaxGCPauseMillis指定最大停顿时间
      • 使用-XX:ParallelGCThreads设置并行回收的线程数量
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容