java虚拟机5

并发标记与三色标记

  • 并发垃圾回收器中为了应对并发场景需要用到三色标记算法

三色标记算法

  • 把对象用三种颜色标记

    • 黑色:一般代表根对象,除了根对象以外,如果这个对象以及它的子对象都被扫描了,这种也是黑色的
    • 灰色:只扫到当前对象,但是它的子对象还没有被扫描到,这种是灰色
    • 白色:没有扫到的对象就是白色,如果扫描完所有对象后,最终是白色的为不可达对象,即垃圾对象
  • 优点

    • 可以异步执行,扫描颜色的线程进行的同时,用户线程也可以运行

三色标记的问题

GC并发情况下的漏标问题

  • 线程1和线程2属于垃圾回收线程,假如线程1完成了所有的标记,把所有可达的都变成黑色了,线程2还处于半完成状态,还没有把gc roots引用链上的对象全部扫描完,也就是还有一些对象是白色的,如果此时用户线程把线程2负责扫描的引用链上未扫描到的对象C连接到线程1已经扫描完的引用链上的某一个对象A连接起来了,同时去掉对象C和线程2负责扫描的引用链上对象B的连接,因为线程1已经完成对对象A的扫描,那么对象C就不会再扫描,也就被漏标了

CMS中的解决方案

  • Incremental Update算法
    • 发现一个白色对象被一个黑色对象的时候,就把这个黑色对象重置为黑色,垃圾回收器就会这条链路没有扫完,就会再扫一次,这样白色对象就被修正为黑色
  • 这种算法关注的是引用的增加

G1中的解决方案

  • STAB算法(snapshot at the beginning)
    • 每次扫描的时候都会进行一个快照,如果发现对象B和对象C(对象B是对象C的父对象)的连接消失了,就要把快照信息推送到GC的堆和栈,这就意味着之前的关系和引用仍然存在,再根据快照信息,就会发现对象C漏掉了,就会再扫一次
    • 把两张快照叠在一起,对模子一样,一下找到变化的地方,针对这个变化的地方做处理,发现有一个连接消失了
  • 这种算法关注的是引用的删除,如果这个引用删除了,并不是意味着对象C就是垃圾

解决方案对比

  • CMS关注的是引用的增加,发现引用增加了就会去重新扫描,需要从头开始扫
  • G1关注的是引用的删除,相比CMS要快,不用从头开始,但是快照信息也占一点内存

G1中的技术细节

G1的内存区域不固定

  • 某个区域是eden区,之后可能通过标记的方式直接改成old区或者survivor区

##跨代引用

  • 无论G1,还是其他传统的垃圾回收器,都会存在老年代的对象引用了新生代的对象,这种引用是跨代的

  • 如果要进行新生代的单独回收?

    • 这种老年代引用新生代的情况,如果JVM单独对新生代进行垃圾回收,不管这个老年对象是什么,它就会认为这个老年代的对象是gc roots
    • 此时就需要对整个老年代里面的对象进行扫描,找出跟这些老年代对象关联的新生代对象
  • 如何避免每一次进行young gc或者minor gc时,扫描整个老年代?

    • 两种方式

      1.跨代引用,通过记忆集记录,类似于hashmap的结构,key值来自于卡表数组索引,value是哪个引用,对应于下面的例子就是Rset(1,x)

      2.每一个eden块都要存放一个Rset,而region只有1M,而Rset会占据很大的内存空间,对于cms而言是一整个区域,只要一个Rset就行了,所以不推荐堆空间比较小的情况下使用g1垃圾回收器

Rset与CardTable

  • Rset:记忆集,记录跨代引用的集合

    • 对于跨代引用,是很容易辨别的,引用地址的开始和结束相差很大,可以快速扫描出来,并放到Rset里面,如果只是单独的记录x引用(c通过x引用指向d),还是意味着整个老年代都要去看
  • CardTable:卡表,如果没有卡表,不知道跨代引用的位置,就需要扫整个老年代,所以要把记忆集和卡表结合使用

    • 就是一个数组card[x] = {0或者1},数组元素要么是0要么是1,0代表没有跨代引用,1代表有
    • 假设使用的卡页是2的10次幂(与g1中的region大小对应),即1M,内存的起始地址是0x0000的话,数组CARD_TABLE的第0、1、2号元素,分别对应了地址范围为0x00000x03FF(03FF是1023)、0x04000x07FF、0x0800~0x011FF的卡页内存
    • 假设记忆集中的x引用关联的老年代对象在card[1]中,即card[0]=0、card[1]=1、card[2]=0
    • 垃圾回收器在扫描的时候,首先看卡表,只有card[1]有跨代引用,就不需要去其他的区域扫描

安全点与安全区域–与stop the world相关

  • JVM在具体实现上,stop the world并不是强制把用户线程停止,是采用的主动式中断的方式,假如GC开始工作需要stop the word,首先设置一个标志S,用户线程会轮询这个标志位,如果标志位从0变成1,必须跑到最近的一个安全点上挂起,方便垃圾回收

  • 安全点:方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点

    • 安全点的设置一定要跟引用的变化有关系,引用变化就会不安全
    • 但是如果线程本来就挂起了,或者阻塞了,根本没有去轮询这个标志位,就需要引入另一个概念安全区域
  • 安全区域:JVM引进的一个概念,假设某一段代码有10行,发现从第4行到第7行引用关系不会发生变化(JVM根据字节码的变化来确定),这块区域就称为安全区域,如果此时用户线程正在执行这块区域上的代码,gc线程是不会管的

    • GC线程就不再管已经表示在安全区域的线程,不管它是否挂起
    • 当线程即将离开安全区域,需要坚持虚拟机是否完成了根节点的枚举,这里必然是要stop the world的,也就是说如果垃圾回收器完成了那些需要暂停的阶段(需要stop the world的部分,比如根节点的枚举判断、标记整理的时候),用户线程才可以继续往下面执行,如果没有完成,用户线程就一直等到完成才可以离开安全区域

低延迟的垃圾回收器

垃圾回收器三项指标

  • ①内存占用

    • 堆内存到底占用多少
  • ②吞吐量

    • 垃圾回收要吞吐量高
  • ③延迟

    • stop the world的时间
  • 传统的垃圾回收器–cms和g1,不可能满足三者都最优,如果内存越来越高或者吞吐量越来越大,就会造成延迟变高,如果要求延迟比较低,相应的内存和吞吐量就不能太大

  • 目前主流的垃圾回收器都在往延迟方向走,因为内存占用是必然的,现在很多服务器都是TB级了,吞吐量也会越来越高,延迟是一个很关键的点

Eplison

  • “不干活”的垃圾回收器

  • 调测内存布局、解释器、编译器的接口,不会发生垃圾回收

ZGC

  • 类似于G1的region,但是没有分代
  • 延迟可以控制在10ms
  • 染色指针技术-----ColoedPointers(要求内存不能超过4TB)、垃圾回收效率高
  • 短暂的STW也只与GC roots大小相关(gc roots的数量相关)而与堆空间内存大小无关,哪怕内存很大,延迟也只有10ms

Shenandoah

  • 非oracle公司开发的垃圾回收器
  • 延迟在100ms,也是用的染色指针技术

GC日志详解

  • package ex4;
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * VM参数:
     * -XX:+PrintGCDetails -XX:+UseSerialGC
     * -XX:+PrintGCDetails
     * -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
     * -XX:+PrintGCDetails -XX:+UseG1GC
     */
    public class TestGC {
    
        /*不停往list中填充数据*/
        //就使用不断的填充 堆 -- 触发GC
        public static class FillListThread extends Thread{
            List<Object> list = new LinkedList<>();
    
            @Override
            public void run() {
                try {
                    while(true){
                        if(list.size()*512/1024/1024>=990){
                            list.clear();
                            System.out.println("list is clear");
                        }
                        byte[] bl;
                        for(int i=0;i<100;i++){
                            bl = new byte[512];
                            list.add(bl);
                        }
                        Thread.sleep(1);
                    }
    
                } catch (Exception e) {
                }
            }
        }
    
        /*每100ms定时打印*/
        public static class TimerThread extends Thread{
            public final static long startTime = System.currentTimeMillis();
            @Override
            public void run() {
                try {
                    while(true){
                        long t =  System.currentTimeMillis()-startTime;
                       // System.out.println(t/1000+"."+t%1000);
                        Thread.sleep(100); //0.1s
                    }
    
                } catch (Exception e) {
                }
            }
        }
    
        public static void main(String[] args) {
            //填充对象线程和打印线程同时启动
            FillListThread myThread = new FillListThread(); //造成GC,造成STW
            TimerThread timerThread = new TimerThread(); //时间打印线程
            myThread.start();
            timerThread.start();
        }
    
    }
    
    

专业名字解释

  • [GC (Allocation Failure) [ParNew: 69376K->8640K(78016K), 0.0688117 secs] 69376K->62616K(251456K), 0.0762846 secs] [Times: user=0.47 sys=0.00, real=0.08 secs]
    
    • GC:YGC
    • Allocation Failure:GC原因
    • ParNew: 69376K->8640K(78016K):新生代:回收前新生代大小->回收后新生代大小(新生代总大小)
    • 69376K->62616K(251456K), 0.0762846 secs:回收前堆空间大小->回收后堆空间大小(总堆空间)
    • Times: user=0.47 sys=0.00, real=0.08 secs:用户time 内核time 总计time
  • 标识了secs的是秒,没有标识的默认是毫秒

GC常用参数

  • -Xmn -Xms -Xmx –Xss 年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB 使用 TLAB(本地线程缓冲),默认打开
  • -XX:+PrintTLAB 打印 TLAB 的使用情况
  • -XX:TLABSize 设置 TLAB 大小(默认是eden区的百分之一)
  • -XX:+DisableExplicitGC 启用用于禁用对的调用处理的选项 System.gc(),禁用System.gc()命令
  • -XX:+PrintGC 查看 GC 基本信息
  • -XX:+PrintGCDetails 查看 GC 详细信息
  • -XX:+PrintHeapAtGC 每次一次 GC 后,都打印堆信息
  • -XX:+PrintGCTimeStamps 启用在每个 GC 上打印时间戳的功能
  • -XX:+PrintGCApplicationConcurrentTime 打印应用程序时间(低)
  • -XX:+PrintGCApplicationStoppedTime 打印暂停时长(低)
  • -XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用(重要性低)
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions 可在程序运行时,打印虚拟机接受到的命令行显示参数
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 打印所有的 JVM 参数、查看所有 JVM 参数启动的初始值(必须会用)
  • -XX:MaxTenuringThreshold 升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。

Parallel 常用参数

  • -XX:SurvivorRatio 设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为 8
  • -XX:PreTenureSizeThreshold 大对象到底多大,大于这个值的参数直接在老年代分配
  • -XX:MaxTenuringThreshold 升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS 常用参数

  • -XX:+UseConcMarkSweepGC 启用 CMS 垃圾回收器
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始 CMS 收集,默认是 68%(近似值),如果频繁发生 SerialOld 卡顿,应该调小,(频繁 CMS 回
    收)
  • -XX:+UseCMSCompactAtFullCollection 在 FGC 时进行压缩
  • -XX:CMSFullGCsBeforeCompaction 多少次 FGC 之后进行压缩
  • -XX:+CMSClassUnloadingEnabled 使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。
  • -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行 Perm 回收,JDK 8 中不推荐使用此选项,不能替代。
  • -XX:GCTimeRatio 设置 GC 时间占用程序运行时间的百分比(不推荐使用)
  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代

G1 常用参数

  • -XX:+UseG1GC 启用 CMS 垃圾收集器
  • -XX:MaxGCPauseMillis 设置最大 GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且 JVM 将尽最大的努力(G1 会尝试调整 Young 区的块数来)来实
    现它。默认情况下,没有最大暂停时间值。
  • -XX:GCPauseIntervalMillis GC 的间隔时间
  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长
  • -XX:G1NewSizePercent 新生代最小比例,默认为 5%
  • -XX:G1MaxNewSizePercent 新生代最大比例,默认为 60%
  • -XX:GCTimeRatioGC 时间建议比例,G1 会根据这个值调整堆空间
  • -XX:ConcGCThreads 线程数量
  • -XX:InitiatingHeapOccupancyPercent 启动 G1 的堆空间占用比例,根据整个堆的占用而触发并发 GC 周期
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值