JVM垃圾收集器G1

JVM垃圾收集器G1

  • G1收集器
    • G1的垃圾回收过程
    • G1特点
    • G1垃圾收集分类
      • Young GC
      • Mixed GC
      • Full GC
    • G1收集器参数设置
    • G1收集器优化建议
    • G1使用场景
    • 每秒几十万并发的系统如何优化JVM
    • 如何选择垃圾收集器
    • 安全点与安全区域
      • 安全点
      • 安全区域

G1收集器

  • G1是面向服务器的垃圾收集器,主要针对配备多核处理以及大容器内存的机器,极高概率满足GC停顿时间要求,还具备高吞吐量特征;

JVM垃圾收集器G1_第1张图片
JVM垃圾收集器G1_第2张图片

  • G1将Java堆划分为多个大小相等的独立区域Region,JVM最多可以有2048个Region;

  • 一般Region大小等于堆大小/2048。
    比如堆大小为4096M,则Region大小为2M。
    可以使用参数“-XX:G1HeapRegionSize”指定region大小。

  • G1保留了年轻代与老年代的概念,但是年轻代与老年代的物理内存不再是连续的,而是一系列分散的Region集合。

  • 默认年轻代与堆内存的占比为5%,如果堆大小为4096,那么年轻代占据200M左右的内存,对应大概是100个Region。
    可以通过“-XX:G1NewSizePercent"调整。

  • 年轻代中的Eden和Survivor的比例默认是8:1:1,。

  • 一个Region的身份可能会发生变化,可能先是老年代,后变为年轻代。
    因为对Region进行了垃圾回收,变为了空Region,然后创建对象又分配到该Region,所以变为了年轻代。

  • G1可对大对象进行特殊处理;G1专门分配大对象的区域为Humongous区,而不是直接进入老年代。G1对于大对象的判定规则就是大于Region容量的一半。
    如果一个Region为2M,一个对象大于1M,会分配到Humongous区。如果对象的大小为Region的几倍,会使用多个连续的Humongous区域来存储这个大对象。

  • Humongous专门存放短期巨型对象,不用直接进入老年代,避免老年代提前爆满,从而出发Full GC、

  • Full GC的时候,除了收集年轻代与老年代,也会收集Humongous区。

G1的垃圾回收过程

  1. 初始标记
    暂停所有的其他工作线程,并记录GC Root直接引用的对象,速度快。(同CMS)
  2. 并发标记
    从GCroot直接引用的对象开始,遍历标记整个对象图,这个过程和工作线程并发运行,耗时比较长。(同CMS)
  3. 最终标记
    由于并发标记期间,应用程序还在跑,部分标记完的对象,其标记可能会发生变动。最终标记就是修正这部分标记发生变动的对象。(同CMS)
  4. 筛选回收
    筛选回收阶段会对各个Region的回收价值与成本进行排序,根据用户所期望的GC停顿时间(-XX:MaxGCPauseMillis)来制定回收计划,通过之前的回收成本计算得知,可能回收其中800个Region需要200ms,那么只回收这800个Region。尽量把GC导致的停顿时间控制在我们的指定范围内。这个阶段也可以做到与用户程序并发执行,但是因为只回收一部分Region,时间是用户控制,而且停顿用户线程将大幅度提高收集效率。不管是年轻代还是老年代,都是使用复制算法,将Region中存活的对象复制熬另一个Region中,这种不会像CMS产生很多内存碎片从而需要整理内存。G1采用复制算法回收几乎不会有太多的内存碎片。(注意:CMS回收阶 段是跟用户线程一起并发执行的,G1因为内部实现太复杂暂时没实现并发回收,不过到了Shenandoah就实现了并 发收集,Shenandoah可以看成是G1的升级版本)
    JVM垃圾收集器G1_第3张图片
    G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
    比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回 收时间有限情况下,G1当然会优先选择后面这个Region回收。
    这种使用Region划分内存空间以及优先级的区域回收方式,保证了G1收集器在有限时间内尽可能高的收集效率;

G1特点

1. 并行与并发:充分利用CPU、多核环境下的硬件优势,使用多个CPU缩短STW时间。
2. 分代收集:G1不需要与其他收集器配合就可管理整个堆内存,但是还是保留了分代的概念
3. 空间整合:与CMS的标记-清理不同,G1整体上是基于标记-整理算法实现的收集器;从局部看、是基于复制算法实现。
4. 可预测的停顿时间:G1相对于CMS的另一个大优势、降低停顿时间是G1与CMS共同关注点,G1除了追求低停顿外,还建立了可预测的停顿时间模型,能指定长度为毫秒的时间片段(XX:MaxGCPauseMillis)。

当然设置停顿时间应该设置合理的停顿时间,使得G1在吞吐量与延迟取得平衡。
如果设置的停顿时间短(几毫秒到几十毫秒)、那么每次回收的垃圾就会减少,而程序生成垃圾的速度大于回收垃圾的速度,进而堆内存爆满的速度变快,如果G1回收效果差,最终导致触发Full GC,暂停所有工作线程执行一次独占式清理。

G1垃圾收集分类

Young GC

当Eden区满后,Young GC不一定立刻被触发,G1先计算Eden区回收需要多长时间,如果回收时间远小于停顿时间,就增加年轻代的Region,继续给新创建的对象分配内存,不会立马做YoungGC,当下一次Eden区满后,再次计算回收时间是否接近停顿时间,是则触发Young GC进行回收。

Mixed GC

非Full GC;老年代对占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值就触发;回收所有的Young和部分Old(根据停顿时间确定Old区域垃圾收集的优先顺序)以及大对象区域,正常情况下,G1的垃圾收集先做MixedGC,主要使用赋值算法,需要把各个Region中存活的对象拷贝到别的Region里去,拷贝过程如果发现没有足够的空Region能够承载拷贝对象就会触发一次Full GC;

Full GC

停止应用程序,然后采用单线程进行标记,清理和压缩整理,清理出一批Region来供下一次MixedGC使用。这个过程非常耗时。

G1收集器参数设置

-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的线程数量
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
-XX:G1MaxNewSizePercent:新生代内存最大空间
-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个 年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
-XX:MaxTenuringThreshold:最大年龄阈值(默认15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大。
-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都 是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清 理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立 即停止混合回收,意味着本次混合回收就结束了

G1收集器优化建议

重点核心是-XX:MaxGCPauseMillis停顿时间的设置
如果设置时间过长,导致系统运行很久,Eden区域过多,年轻代占用堆比例过高,触发Young GC时,会有很多存活对象,如果存活对象大于S区的一半,触发动态年龄判定规则,导致一些对象进入老年代。
设置时间过短,导致回收区域数量变少,回收垃圾变少,垃圾对象堆积越来越多,且还需要不断新创建独享,这样就会频繁触发MixedGC;

G1使用场景

  1. 50%以上的堆被存活对象占用
  2. 对象分配和晋升速度变化大;
  3. 垃圾回收时间特别长
  4. 8G以上的堆内存
  5. 停顿时间500ms以内;

每秒几十万并发的系统如何优化JVM

像Kafka,每秒处理几万到几十万消息,一般部署Kafka需要大内存机器(64G),也即是说可以给年轻代分配30-40个G的内存来支撑并发处理。
对于Eden区的Young GC很快,对于G1之外的收集器(Serial,ParNew,Parallel)来说。但是对于三四十G的内存,每次收集垃圾最快也要几秒钟。让用户等几秒钟是致命的。
而Kafka的并发量放满三十个G的Eden也就一两分钟。
假设一个请求需要1KB内存(真正业务算少了),那么30万请求,那么每秒就有几个G的内存使用,每秒就300M内存,一分钟就18个G,两分钟左右就满了。然后一次YoungGC让用户等几秒,然后再处理新消息,显然不合理。
这使可以使用G1手机器,设置置 -XX:MaxGCPauseMills 为50ms,设置50ms能够回收3-4个G内存,50ms的卡顿是可以完全接受的,用户几乎无感知,那么系统可以一边进行垃圾收集,一边处理业务。

如何选择垃圾收集器

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100M,使用串行收集器
  3. 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
  4. 如果允许停顿时间超过1秒,选择并行或者JVM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器
  6. 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC 下图有连线的可以搭配使用
    JVM垃圾收集器G1_第4张图片
    JDK8默认使用Parallel 收集器组合;
    JDK8默认使用G1

安全点与安全区域

安全点

就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比 如GC等,所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。
这些特定的安全点位置主要有以下几种:

  1. 方法返回之前
  2. 调用某个方法之后
  3. 抛出异常的位置
  4. 循环的末尾
  5. 大体实现思想是当垃圾收集需要中断线程的时候, 不直接对线程操作, 仅仅简单地设置一个标志位, 各个线程执行过程 时会不停地主动去轮询这个标志, 一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 轮询标志的地方和 安全点是重合的。

安全区域

Safe Point 是对正在执行的线程设定的。 如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。 因此 JVM 引入了 Safe Region。 Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。

你可能感兴趣的:(JVM,Java基础,jvm,java)