【JVM垃圾回收】Garbage First+垃圾回收调优

本文更多的是一个大致层面上的理解,细节仍需深挖

一、G1(Garbage First)

1、适用场景

  • 同时注重吞吐量( Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
  • 超大堆内存,会将堆划分为多个大小相等的 Region,每个区域都可以独立地作为伊甸园、幸存区、老年代。
  • 整体上是标记 +整理算法,两个区域之间是复制算法

2、相关 JVM 参数

  • XX:+UseG1GC
  • XX:G1HeapRegionSize=size
  • XX:MaxGCPauseMillis=time

3、G1 垃圾回收阶段

在这里插入图片描述

4、Young Collection

  • 会 STW

在这里插入图片描述

新创建的对象会加载到伊甸园区域。伊甸园逐渐被占满,就会触发一次新生代的垃圾回收,相应地触发一次stw。幸存的对象以拷贝的算法放进幸存区。幸存区满了之后,就会触发又一次新生代的垃圾回收 。幸存区的部分对象晋升到老年代,新生代的一些对象也会复制到幸存区里。

在这里插入图片描述

在这里插入图片描述

5、Young Collection + CM

  • 在 Young GC 时会进行 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

在这里插入图片描述

【注】

O:老年代 E:伊甸园区 S:幸存区

6、Mixed Collection

会对 E、S、O 进行全面垃圾回收

  • 最终标记( Remark)会 STW (在并发标记的同时可能会漏掉一些可回收的对象)
  • 拷贝存活( Evacuation)会 STW

在这里插入图片描述

  • 另一些S区中不够年龄的对象 ,也会复制到一个s区中,相当于集中管理。
  • 为了满足最大暂停时间的目标,会有选择的对老年代对象进行复制。

7、Full GC

  • SerialGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1

  • 新生代内存不足发生的垃圾收集 - minor gc

  • 老年代内存不足

  • 当垃圾回收的速度跟不上垃圾产生的速度时,并发的垃圾回收就会失败,启动full GC。

8、Young Collection 跨代引用

  • 新生代回收的跨代引用(老年代引用新生代)问题

在这里插入图片描述

【注】若在老年代中对象引用了新生代对象,就将对应的卡标记为脏卡,减少搜索范围,提高扫描根对象的效率。

  • 卡表与 Remembered Set
  • 在引用变更时通过 post -write barrier + dirty card queue
  • concurrent refinement threads 更新 Remembered Set

在这里插入图片描述

9、Remark

pre -write barrier + satb_mark_queue

在这里插入图片描述

  • 箭头表示有引用,不会当做垃圾进行回收
  • 黑:已处理;灰:在处理中;白:尚未处理
  • 上图:并发标记阶段的对象处理状态

在这里插入图片描述

当对象的引用发生改变时,JVM会给这个对象加入一个写屏障
在这里插入图片描述

此时写屏障指令执行,将C加入一个队列当中。并将C变成灰色(灰:在处理中)

在这里插入图片描述
等到所有的并发标记结束之后,进入重新标记阶段。重新标记会stw,队列中的对象就会取出,C线程就不会被当做垃圾误回收。

10、JDK 8u20 字符串去重

  • 优点:节省大量内存
  • 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

XX:+UseStringDeduplication

String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列
  • 当新生代回收时, G1并发检查是否有字符串重复
  • 如果它们值一样,让它们引用同一个 char[]
  • 注意,与 String.intern() 不一样
    • String.intern() 关注的是字符串对象
    • 字符串去重关注的是 char[]
    • 在 JVM 内部,使用了不同的字符串表

11、JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用。

当一个类加载器的所有类都不再使用,则卸载它所加载的所有类

XX:+ClassUnloadingWithConcurrentMark 默认启用

12、 JDK 8u60 回收巨型对象

  • 一个对象大于 region 的一半时,称之为巨型对象
  • G1 不会对巨型对象进行拷贝
  • 巨型对象回收时被优先考虑
  • G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生
    代垃圾回收时处理掉

在这里插入图片描述

13、 JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC
  • JDK 9 之前需要使用 - XX:InitiatingHeapOccupancyPercent
  • JDK 9 可以动态调整
    • XX:InitiatingHeapOccupancyPercent 用来设置初始值
    • 进行数据采样并动态调整
    • 总会添加一个安全的空档空间

二、垃圾回收调优

1、调优领域

  • 内存
  • 锁竞争
  • cpu 占用
  • io

2、确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器
  • CMS ,G1,ZGC(低延迟选用的GC)
  • ParallelGC(高吞吐量所选用的GC)
  • Zing(一种VM。上面的都是Oracle的Hotspot虚拟机。)

3、最快的 GC

答案是不发生 GC

查看 FullGC 前后的内存占用,考虑下面几个问题

  • 数据是不是太多?

    resultSet = statement.executeQuery(“select * from 大表 limit n”)

  • 数据表示是否太臃肿?

    • 对象图
    • 对象大小 16 Integer 24 int 4
  • 是否存在内存泄漏?

    • static Map map = (至少会GC,有可能OOM。这种是长时间存活的对象)
    • 第三方缓存实现

    解决内存泄漏的3种策略。软引用和弱引用都会在内存吃紧时做一定的回收。类似缓存的数据,可以考虑使用第三方缓存实现。

4、新生代调优

  • 新生代的特点

    • 所有的 new 操作的内存分配非常廉价

    每个线程都会在伊甸园中分配一块私有的区域,即为TLAB。类似缓存,如果有这个对象

    TLAB:thread-local allocation buffer

    • 死亡对象的回收代价是零
    • 大部分对象用过即死
    • Minor GC 的时间远远低于 Full GC
  • 新生代的大小,越大越好吗?

    • 小:可用空间小,容易触发新生代的minor GC,造成stw,造成短暂的暂停。
    • 大:老年代的空间小了。容易导致full GC。而且新生代大了,GC时间会变长。
    • 25%到45%为好。

    -Xmn Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery).
    GC is performed in this region more often than in other regions. If the size for the younggeneration is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

  • 新生代能容纳所有【并发量 * (请求-响应占用的内存)】的数据

  • 幸存区大到能保留【当前活跃对象 +需要晋升对象】

  • 晋升阈值配置得当,让长时间存活对象尽快晋升

  • XX:MaxTenuringThreshold=threshold
  • XX:+PrintTenuringDistribution

5、老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
  • XX:CMSInitiatingOccupancyFraction=percent

6、调优的案例

(1)Full GC 和 Minor GC频繁

新生代的内存设置太小。调大幸存区的容量和晋升的阈值,让更多的对象留在新生代。而不是很快的晋升到老年代。

(2)请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)

CMS重新标记时间耗时长。打开CMS…BEFOREREMARK,在重新标记之前堆新生代对象进行一次垃圾回收,重新标记阶段所需要查找和标记的对象少多了。

(3)老年代充裕情况下,发生 Full GC (CMS jdk1.7)

【注】1.8之前使用永久代作为方法区的实现,而1.7使用的是元空间。

元空间或者永久代内存不足,会导致整个堆的full GC

学自黑马程序员,自己理解+整理,侵删

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值