深入剖析JVM垃圾回收-上

涉及问题

  • 垃圾回收设计问题
    JVM有哪些垃圾回收算法,各自优劣
    CMS垃圾回收器怎么工作,有哪些阶段
    服务卡顿的问题症结
    

JVM GC

  • GC触发条件
    内存达到一定条件,会自动触发
    GC只与活跃对象有关,进行记录;与堆的大小无关
    

GC流程

标记(Mark)

  • 定义
    定义:GC Roots遍历所有可达对象。
    找出活跃对象进行标记,且GC过程是逆向(先找到活跃对象,再根据触发条件进行回收)
    
  • 图示
    圆圈代表对象。绿色是GC Roots,红色的代表可以追溯的对象。灰色是被回收的对象。
    
    回收前后对象存活图示

清除(sweep)

  • 清除阶段就是把未被标记对象回收掉
    清除算法-清除未标记对象
  • 存在问题
    这种简单问题,在清除对象后,会带来内存碎片问题,如下图-未标记对象(2k和4k对象)清除前后
    如申请了1k,2k,3k,4k,5k内存,在对2k,4k进行回收后交给垃圾回收器。当再次一个5k内存时,无法申请,因为其未对内存空间进行整理,回收的内存仍是2k,4k
    此时,空间有6k内存。但是无法申请5k空间(因空间碎片存在),系统运行时间越长,碎片就越多。
    
    清除算法-对象为清除前
    清除算法-要出对象(划红斜线)清除后

复制(copy)

  • 解决内存碎片方法
    1.方法
    解决问题只能通过内存整理。
    2.思路
    提供一个对等内存空间,将存活的对象复制过去,然后清除原内存空间。3.缺点
    会造成一倍空间浪费
    4.应用
    HashMap内存扩容思路(声明同容量的数组,将其复制过去),Redis rehash
    
  • 实现方法
    复制-申请同等空间进行对象复制

整理(compact)

  • 思路
    1.复制清除方法弊端
     需要分配对等额外空间,完成内存整理工作。其实也可以不分配,仍能完成内存整理工作。
    2.思路: 
    将内存当做非常大数组,根据随机index删除一些数据。那么对整个数组进行清理,故不需要另外一个数组来进行支持,使用程序即可。
    3.步骤:
    移动所有存活对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部收回。
    4.注意
    仅有复制清除方式进行内存整理是理想状态。对象引用关系一般非常复杂。从效率上讲,一般整理算法低于复制算法(拿空间换时间)。
                      5.代码模拟
    last=0
    for(i=0;i<mems.length;i++){
    if(mems[i]!=null){
    mems[last++]=mems[i];
    changeReference(mems[last]);
    }
    }
    clear(mems,last,mems.length);
    
     
    
  • 图示
    标记-整理进行对象回收

分代

  • 常见朴素内存回收算法优劣

    1.复制(copy)
    复制算法是所有算法效率最高的算法,缺点会造成一定空间浪费
    2.标记-清除(Mark-Sweep)
    效率一般,缺点会造成内存碎片问题
    3.标记-整理(Mark-Compact)
    效率较前两者差,但无空间浪费,消除内存碎片问题
    
  • JVM对象分类

    1.大部分对象声明周期很长。如类信息,字符串对象
    2.其他对象存活时间很短。如局部变量,临时变量
    
  • 弱代假设

    1.定义: 大部分对象存朝生夕灭,剩余的活很久
    2.代际划分
    年轻代(young generation): 死得快的对象所占区域
    老年待(old generation):剩余活得久对象所占区域,又叫Tenured Generation。
    
  • 某应用对象存活时间统计
    某应用对象存活时间统计

年轻代

  • 垃圾回收算法
    复制算法,因为年轻代(minor gc)发生GC后,只有较少对象存储,通过申请相同大小空间(虽然空间浪费,但空间换时间),复制对象到另一相同大小空间,是非常高效的。
    
  • 具体分区
    1.分区
    一个伊甸园空间(Eden),两个幸存者空间(survivor),其中survivor必须时刻有一个是空的。
    2.Eden空间满时,触发年轻代GC (minor gc) 流程如下
    (a).Eden执行第一次GC之后,存活对象会被移动到其中一个survivor分区(from survivor)
    (b).Eden再次GC,此时采用复制算法,将Eden和from区一起清理。存活对象会复制到to区,接下来,只需要情况from区域。
    (c)此过程中总有一个survivor分区是空的。Eden、from、to默认比例是8:1:1,所有会有10%空间浪费。比例有 -XX:SurvivorRatio设置,默认是8
    
  • Thread Local Allocation Buffer
    1.Young Generation回收流程
    关于内存回收,除了上述年轻代Eden在第一次将活对象移动from区,二次minor gc时采用复制方法,将Eden和from区活对象复制到to区后,from变空。多次这样,知道to区变满会移动Old generation。
    2.TLAB
    还有TLAB,JVM默认给每个线程开辟一个buffer区域,用来加速对象分配,这个buffer放置Eden区。该作风同 java ThreadLocal相似,属线程私有,避免对共享区操作,以及一些锁竞争。
    TLAB通常很小,所以对象较大时,会在Eden共享区域进行分配。
    TLAB是一种优化技术,类似优化有对象的栈上分配(线程逃逸分析,默认开启)
    
  • TLAB分配图示
    TLAB分配图示

老年代(Old Generation)

  • 算法
    对象存活率较高,空间比较大,拷贝不划算,适合就地收集,因此采用标记-清除,标记-整理算法
    
  • 对象进入老年代途径
  • 提升(Promotion)
    对象够老,会通过”提升”进入老年代。
    通过它的年龄(age)来判断对象是否老。每当发生minor gc,存活对象年龄都会+1.知道达到一定阈值,就会把仍然活跃的对象提升到老年代
    如果这些对象不可达,知道老年代发生Major GC时,才会被清理
    年龄阈值通过-XX:+MaxTenuringThreshold(=15默认)配置。因为是用4bit存储。故将该值调大无根据
    
  • 分配担保
       年轻的对象,每次存活对象,都会放入其中一个survivor区域(比例是10%)。但是我们无法保证每次存活对象都是10%,当survivor空间不够,就需要依赖其他内存(老年代)进行分配担保。这时,对象会在老年代上分配
    
  • 大对象直接在老年代分配
    超出参数值(-XX:PretenureSizeThreshold)的对象,直接在老年代分配。默认是0,即全部首选在Eden区进行分配。
    
  • 动态对象年龄判断
    有的垃圾回收算法,并不要求age=15才能晋升到老年代,会使用动态计算方法。如survivor区相同年龄对象大小的和,大于survivor的一半,大于或等于age的对象直接进入老年代。
    
  •  四种对象进入老年代方式

卡片标记(card marking)

  • 统计对象引用
    对象引用关系时一个巨大的网状。有的对象可能在Eden区,有的可能在老年代。对于跨代引用(如年轻代对象minor gc在单独发生时要被回收,但老年代对象引用了它,要确保年轻代对象存活).
    对于是、否的判断,通常用位图和布隆过滤器来加快搜索速度,jvm也是用了类似方法。
    
  • 卡页,老年代是被分成众多的卡页(card page)的(一般数量是2的次幂)
  • 卡表(card table)就是用于标记卡页状态的一个集合,每个卡表项对应一个卡页。
    如果年轻代有对象分配,而且老年代有对象指向这个新对象,那么老年代所对应的卡页,就会标识为dirty,卡表值需要非常小的存储空间就可以保留这些状态。
    垃圾回收时,先读卡表,进行快速判断Eden代的对象是否被Tenure代引用,进而决定是否被回收。
    

HotSpot垃圾回收器

年轻代垃圾回收器

Serial 垃圾收集器

  • 串行垃圾收集器,GC时只有一条线程,并且垃圾回收时暂停一切线程(STW),会出现明显卡顿。
  • 使用在java开发的客户端上,不会频繁创建对象,用户不会感觉明显卡顿。使用资源少,更轻量级
  • 年轻代serial 垃圾收集器使用复制算法(适用对象少情况,不担心空间浪费,用时间换空间)

ParNew 垃圾收集器

  • 并行垃圾收集器(parallel gc),是Serial多线程版本。由多条GC线程并行地进行垃圾清理,清理过程仍然要暂停用户线程,产生卡顿
  • ParNew追求通过多线程进行垃圾回收,追求”低停顿时间”。多CPU环境性能会比串行垃圾收集器优异;但涉及线程切换需要额外开销,单核CPU表现不如串行收集器

Parallel Scavenge垃圾收集器

  • 多线程垃圾收集器,与ParNew区别如下
    Parallel Scavenge:追求CPU吞吐量,能在较短时间完成指定任务,适合无交互后台计算、弱交互计算
    ParNew:追求降低用户卡顿时间,适合交互式应用、强交互计算。
    

老年代垃圾收集器

Serial old垃圾收集器

  • 与年轻代serial垃圾收集器对应,都是单线程,同样适合客户端使用
  • 老年代serial使用标记-整理(空间有限,避免内存问题,回收时进行内存整理)算法

Parallel old

  • 是parallel scavenge 老年代版本,追求CPU吞吐量

CMS 垃圾收集器

  • 并发标记清除(concurrent mark sweep)垃圾收集器以获取最短GC停顿时间为目标的收集器,保证GC线程和用户线程并发执行,尽量减少卡顿

  • 长期来看CMS要被G1等垃圾回收器替换,java8 后使用其回收老年代对象将抛出警告

    JavaHotSpot(TM)64-BitServerVMwarning:OptionUseConcMarkSweepGCwasdeprecatedinversion9.0 and will likely be removed in a future release.
    

垃圾收集器配置参数

  • 除了上面几个GC,我们还有G1,ZGC等更高级GC,有专门配置参数设置来生效

    1.查看当前Java版本默认使用垃圾收集器。
    -XX:+PrintCommandLineFlags
    2.创建配置参数
    -XX:+UseSerialGC 年轻代和老年代都用串行垃圾收集器
    -XX:+UseParnew  年轻代用ParNew,老年代用Serial Old 
    -XX:+UseParalleGC年轻代用ParallelGC,老年代默认用serial old
    -XX:+UseParallelOldGC年轻代和老年代都用并行收集器
    -XX:+UseConcMarkSweepGC 年轻代用ParNew,老年代用CMS
    -XX:+UseG1GC 都使用G1垃圾回收器
    -XX:UseZGC 都使用ZGC垃圾回收器
    
  • java9 中,-XX:UseParNewGC已被抛弃,有些程序(ES)会报错,需留意

  • 参数对应分代垃圾收集器类型
    参数对应分代垃圾收集器类型

STW

  • jvm进行垃圾回收出现的卡顿就和STW有关
    垃圾回收(标记或整理复、制)时,如果有新的对象进来,理想情况下是,为了保证程序不乱套,最后办法是暂停用户一切线程。这段时间,不能new对象,只能等待。具体现象就是jvm出现短暂卡顿,即Stop the world ,简称STW
    
  • GC标记阶段,大多数需要STW
    如果不暂停用户进程,在标记对象时,有可能有其他用户线程会产生一些新的对象引用而未被统计到,造成混乱。
    
  • 优化方向
    现在垃圾收集器,都尽量减少STW时间。但即使是最先进的ZGC,也会有短暂STW。因此要做的是,在现有基础设施,尽量减少GC停顿
    
  • STW影响例子
    某高并发峰值流量10w /秒,后面10台负债均衡机器,那么每台需要1w/s.加上某天机器在一段时间发生STW,持续1s,那么本来需要10ms就可返回的1w个请求,至少需要等1s。
    在用户那里,表现为系统卡顿。如果GC频繁,卡顿明显,严重影响用户体验。
    

STW影响例子

小结

  • 小结
    归根接地,各色垃圾收集器为解决STW问题,减少GC时间,停顿更小,吞吐量更大
    现在的垃圾回收器,基于弱代假设,大多数是分代回收理念。针对年轻代、老年代,有多种不同垃圾回收算法,可组合使用
    
  • 年轻代垃圾回收
    1.年轻代是GC重灾区
    2.面试需要
    3.为理解G1,ZGC
    
  • 算法
    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 generation hypothesis
    2.分配担保
    3.提升
    4.大对象分配在永久代
    5.卡片标记年轻代对象被老年代引用
    6.STW
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值