天天看点

JVM优化之G1垃圾收集器

简单介绍G1

G1垃圾收集器在jdk1.7中开始使用,oracle官方是计划在1.9中变成默认的垃圾收集器替代GMS垃圾收集器,可以看出来垃圾收集器的未来使用的是G1
           

G1的设计原则:简化JVM的性能调优,以前在做调优的时候针对老年代、年轻代等进行调优,G1就变得简单,只需要三个步骤即可以调优,1、开启G1垃圾收集器;2、设置最大的堆内存;3、设置最大的停顿时间。在G1中提供了三种垃圾收回收模式,包括Young GC(年轻代gc)、Mixed GC(混合gc)、Full GC(堆内存的全量gc)

G1原理:与串行,并行、GM的最大区别在与取消了内存中年轻代和老年代的物理划分,取而代之的是将整个堆内存空间划分成若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代,这样做的好处就是不用在去单独的给年轻代老年代进行划分从而不用担心每个内存(年轻代、老年代等)是否够用;

JVM优化之G1垃圾收集器
JVM优化之G1垃圾收集器

在G1的划分的区域中,年轻代的垃圾收集器依然采用暂停应用线程的方式,将存活的对象复制到Survivor空间或老年代中,G1这样就完成了清理工作(复制法),不过在复制的过程中G1有了性能的优化,过程如下

JVM优化之G1垃圾收集器

整个内存划分成若干个Region区域,每个Region有着逻辑上的老年代(old)和年轻代(包括Survivor和Elden),Elden区域在gc的时候将对象复制到Survivor区域,那么原本的Elden区域就变成了空闲的内存,同理Elden区域也可以复制到old区域;在gc之后原本的内存空间就变得连续这就意味着在gc的过程中 G1完成了内存压缩(至少是部分的内存压缩)解决了内存碎片化严重的问题;

在G1的内存中有一个特殊的区域叫做Humongous区域对存放大对象进行优化,Humongous的作用就是 如果有一个对象占用了内存空间超过了50%,这些巨型对象默认会被分配在老年代,但是如果这些巨型对象只是一个短期的巨型对象,在老年代gc的时候严重的影响了gc的性能,会对垃圾收集器造成负面影响,为了解决这个问题G1中设置了Humongous(H区)专门存放这些巨型对象如果但是一个H区域装不下这个巨型对象G1会寻找一个连续的H区域存放,有时候为了寻找到连续的H区域就不得不进行Full GC

G1垃圾收集器中的Young GC

Young GC 主要是对Edlen进行gc,当Elden区域不够用的时候才会被触发,Young GC的过程为:1、Eden空间的数据移动到Survivor空间中,如果Survivor空间不够Elden区中部分的数据就会直接转移到老年代;2、Survivor区的数据移动到Survivor中(两个相同大小的Survivor只有一个会被使用),如果Survivor的空间不够也会有部分数据直接移动到老年代空间中;3、最终所有的Eldn空间被清空,GC停止工作,应用程序继续运行;

JVM优化之G1垃圾收集器
JVM优化之G1垃圾收集器

G1垃圾收集器中的Remembered Set(已记忆集合)

Remembered Set是G1垃圾收集器中的亮点,在GC年轻代的时,首先需要找年轻代中的根对象,但是根对象可能存在年轻代中也可能存在老年代中,但是老年代中并不是所有的对象都是根对象,如果进行全盘扫描老年代,那么会浪费大量的时间,为了解决这个问题G1引入了新的概念Rset(Remembered Set),其作用是跟踪指向某个堆内的对象引用;原理如下

JVM优化之G1垃圾收集器

G1将整个内存划分成N个Region,每个Region初始化的时候回初始化一个Rest集合用来记录并跟踪其他Region中指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是 xx Region的 xx Card。在gc的时候就不用去扫描根据rset就能够找到根对象而不用去扫描全部;

G1垃圾收集器中的Mixed GC

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注

意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。

MixedGC什么时候触发? ---->由参数 -XX:InitiatingHeapOccupancyPercent=n 决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。

MixedGC步骤

  1. 全局并发标记(global concurrent marking) 2. 拷贝存活对象(evacuation)

全局并发标记

1、初始标记(initial mark,STW(应用线程同步))

标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿

2、根区域扫描(root region scan)

G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。

3、并发标记(Concurrent Marking)

G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断

4、重新标记(Remark,STW)

该阶段是 STW 回收,因为程序在运行,针对上一次的标记进行修正。(因为第2步会产生新的对象)

5、清除垃圾(Cleanup,STW)

清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来

回收

拷贝存活对象

Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理

G1垃圾收集器中相关参数

-XX:+UseG1GC 使用 G1 垃圾收集器

-XX:MaxGCPauseMillis设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是 200 毫秒。

-XX:G1HeapRegionSize=n设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划

分出约 2048 个区域。c默认是堆内存的1/2000。

-XX:ParallelGCThreads=n设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多

为 8。

-XX:ConcGCThreads=n设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。

-XX:InitiatingHeapOccupancyPercent=n设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。

代码测试G1垃圾收集器

import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;

public class testGC {
    //不断的产生新的对象,随机的废弃对象
    public static void main(String[] args) throws Exception {

        ArrayList<Object> list = new ArrayList<Object>();
        while (true){
            //
            int sleep = new Random().nextInt(100);
            if(System.currentTimeMillis() % 2 ==0){
                //100y以内如果是偶数,就将list清空(在调用clear的时候内部是将list对象指向null,执向null 就相当于是垃圾对象)
                list.clear();
            }else{
                //如果不是垃圾对象就向list 对象中添加10000个对象
                for (int i = 0; i < 10000; i++) {
                    Properties properties = new Properties();
                    properties.put("key_"+i, "value_" + System.currentTimeMillis() + i);
                    list.add(properties);
                }
            }
            //随机的停顿
            Thread.sleep(sleep);
        }

    }
}
在这里插入代码片
           
JVM优化之G1垃圾收集器

运行测试程序可以很快的看到有内存溢出的报错

JVM优化之G1垃圾收集器

截取部分日志

JVM优化之G1垃圾收集器

官方

官方对G1优化建议

1、年轻代大小

避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。

2、暂停时间目标不要太过严苛G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开

销,而这会直接影响到吞吐量。

可视化GC分析工具

在上面的测试程序中通过**-XX:+PrintGCDetails**可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观,可以借助于第三方的GC日志分析工具进行查看,在日志打印输出涉及到的参数如下:将日志生成gc.log文件放到E盘

-XX:+PrintGC 输出GC日志

-XX:+PrintGCDetails 输出GC的详细日志 -**

XX:+PrintGCTimeStamps** 输出GC的时间戳(以基准时间的形式) -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

-Xloggc:…/logs/gc.log 日志文件的输出路径

JVM优化之G1垃圾收集器

将生成的gc文件上传

JVM优化之G1垃圾收集器

上传gc文件后可以生成gc的报表

JVM优化之G1垃圾收集器