text: # 前言
  >文本已收录至我的GitHub仓库，欢迎Star：https://github.com/bin392328206/six-finger                             
  > **种一棵树最好的时间是十年前，其次是现在**   
  >我知道很多人不玩**qq**了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群，群聊号码：**549684836** 鼓励大家在技术的路上写博客
  ## 絮叨 
  前面的章节
  
  - [JVM从入门到入土之详解G1垃圾回收器](https://juejin.im/post/5e366c895188254dfd43df80)
  
  ## 回顾
  上面一篇文章大家其实可以搞懂G1的动态内存管理策略，它会根据情况动态的把Regiuon分配给 新生代，Eden,Survivor,老年代和大对象，但是新生代和老年代有一个各自最大的占比，然后在新生代Eden满的时候，触发新生代垃圾回收。
  
  新生代的垃圾回收还是采用了复制算法，只不过会考虑预设的GC停顿时间，保证垃圾回收的停顿时间不难超过设置的时间，因此会挑选一些Region来进行垃圾回收。
  
  然后跟之前说的一样，如果一些对象在新生代熬过拉框一定次数的GC，或者触发了动态年龄判定规则，则会进入老年代
  
  而大对象则是进入单独的Regin，不再进入老年代
  
  所以实际上在G1中，还是会存在新生代的对象会因为各种情况进入老年代
  
  ### 什么时候G1触发新生代+老年代混合垃圾回收？
  
  G1 有个参数 是-XX:InitiatingHeapOccupancyPercent 它的默认值是45%
  
  意思是说 如果老年代占了 堆内存的45%的Region的时候，此时就会触发混合回收阶段 如下图
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005ae255e8e0ea?w=612&h=415&f=png&s=30552)
  
  ### G1垃圾回收的过程
  
  首先会触发一个 初始标记的操作，这个过程需要 Stop the world 的，但是这个过程很快
  
  如下图，先停止系统程序的运行，然后对各个线程栈内存中的局部变量代表GC Roots,以及方法区代表静态变量的Roots,进行扫描，标记出他们直接引用对象。
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005b0eecccd9bc?w=987&h=464&f=png&s=53577)
  
  接下来就会进入到 并发标记的阶段，这个阶段会允许系统程序继续运行，同时进行GC Root追踪,如下图所示
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005b3518a44c7f?w=735&h=419&f=png&s=50520)
  
  这个并发标记阶段还是很耗时的，因为要追踪全部的存活对象
  
  但是这个阶段是可以跟系统程序并发进行的，所以对系统程序的影响不太大
  
  而且JVM会对并发标记阶段对象做出一些记录，比如哪个对象新建了，哪个对象失去引用了，等等
  
  接着下一个阶段就是 最终标记阶段，这个阶段也会进入Stop the world ,系统程序是禁止运行的，但是会根据并发标记记录那些 对象修改，最终标记哪些对象存活，如下图所示
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005b84a2911318?w=787&h=506&f=png&s=58061)
  
  最后一个阶段 是混合回收阶段，这个阶段会计算老年代每个Region中的存活对象数量，存活对象占比，还有执行垃圾回收的预期性能和效率。
  
  接着会停止系统程序，然后全力以赴尽快进行回收，此时会选择部分Region进行回收，因为必须让垃圾回收的停顿时间控制在我们指定的范围内。
  
  比如说 老年代此时有1000Region都满了，但是因为设置的时间，只能停顿200ms,不那么我只能回收800个，那么就会只回收800个Region,把GC的时间控制在我们指定的范围之内，如下图
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005bcce06d3152?w=943&h=617&f=png&s=70768)
  
  ## 案例背景引入
  这是一个百万级注册用户的在线教育平台，主要目标用户群体是几岁到十几岁的孩子，注册用户大概是几百万的规模，日活跃规模大概在几十万。
  
  系统的业务流程其实也不复杂，而且我们可以排除掉一些选课，排课，浏览课程详情这些低频行为。
  
  为啥呢？因为它不是一个电商平台，不是说每个人进去都会去浏览课程详情，
  所以 一般的业务流程就是，有人进来浏览一下，考虑一段时间，然后买拉课程，所以它的高频行为是什么呢？我想了一下，应该是上课
  
  也就是说，孩子们白天要在学校上课，一般也是晚上 8 9点的样子，是这个平台最活跃的时候，还有就是周末也是最活跃的时候，
  
  所以晚上二三个小时的时间段，将会是平台的高峰期，而且白天几乎没有什么流量，可能90%的流量读在晚上，如下图所示
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005d641abbb986?w=625&h=211&f=png&s=13731)
  
  ### 系统核心业务流程
  接着我们来明确一下，这样的一个系统，孩子们在上课的时候主要高频的使用哪些功能呢
  
  其实非常的简单，现在如果大家家里有孩子，平时对一些在线教育App有一定的了解的话，应该知道现在在线App都会主打互动环节
  
  给大家举个例子，比如说给五六岁的孩子上的幼儿园英语课，大家觉得还会像以前一样吗，机械的跟读嘛
  
  那肯定不是了，现在尤为强调的是在欢快的愉快的游戏中进行教学，让孩子们快乐的学习英语，数学之类学科的知识
  
  所以说  在那几十万用户 晚上最高峰的时间使用系统上课的时候，尤为核心的业务流程就是大量的游戏互动环节
  
  
  ![](https://user-gold-cdn.xitu.io/2020/2/2/17005e2cd9169bd9?w=543&h=224&f=png&s=16291)
  
  也就是说，这个游戏互动功能，一定会承担用户高频点击，大量的互动点击
  
  比如在完成什么任务的时候必须点击很多的按钮，频繁的进行互动，然后系统后台需要大量的接收大量的互动请求，并记录用户互动的结果，
  
  系统得记录下来用户完成了多少个任务，作对了几个，做错了几个。
  
  ### 系统的运行压力
  
  现在我们开始来分析一下这个系统运行时候对内存使用的一个压力
  
  其实核心点就是搞明白晚上二三个小时高峰期内，每秒钟会有多少请求，每个请求会连带产生多少对象，占用多少内存，每个请求处理多长时间。
  
  首先我们来分析一下晚上高峰期内几十万用户同时在线使用平台，每秒钟会产生多少请求？
  
  我们可以大致来估算一下，比如说晚上3小时高峰期内总共60万活跃用户，平均每个用户大概会使用1小时左右来上课，一个小时内会进行60次互动操作
  
  那么20W活跃用户因为需要大量的互动操作，所以大致可以认为是每分钟进行1次互动操作，一小时内会进行60次互动操作
  
  那么20万用户在1小时内会进行1200万次互动操作，平均每秒大概是3000次左右的互动操作，这个是一个很合理的数字
  
  那么每秒要承载3000并发请求，根据经验来看  一般核心系统需要部署5台 4核8G的机器来抗住是差不多的，每台机器能抗住600请求，这个压力可以接受，一般不会导致宕机问题。
  
  ### 那么每个请求会产生多少个对象呢？
  一次互动请求不会有太复杂的对象，他主要是记录一些用户互动过程的，可能会跟一些积分类的东西有关联
  
  所以大致估算一下，一次互动请求大致会连带创建几个对象，占多大的内存，比如我们就认为是5kb吧那么一秒600请求会占用3MB左右的内存
  
  ### G1垃圾回收的默认内存布局
  
  接着我们来看看G1垃圾回收器的默认内存布局，我们采用4核8G的机器来部署系统，然后每台机器每秒会有600个请求会占用3Mb左右的内存空间。
  
  那么假设我们对机器上的JVM，分配4G给堆内存，其中新生代默认初始占比5%，最大占比60%，每个Java线程的栈内存为1MB，元数据区域的内存为256M，此时的JVM参数如下
  
  
  ![](https://user-gold-cdn.xitu.io/2020/2/3/17009c61fd44881c?w=943&h=97&f=png&s=17224)
  
  -XX:G1NewSizePercent 参数是用来设置新生代初始占比的，不用设置，维持默认值5%就可以了
  
  -XX:G1MaxNewSizePercent 参数是用来设置新生代最大占比的，也不用设置 维持默认的60%就可以了
  
  此时堆内存共4G，那么此时会除以2048，计算每个Region的大小，此时每个Region的大小就是2MB，刚开始新生代就占5%的Region,可以认为新生代就只有100个Region,有200MB的内存空间，如下图所示。
  
  ![](https://user-gold-cdn.xitu.io/2020/2/3/17009cdff2ac47f3?w=308&h=212&f=png&s=9141)
  
  ### 设置GC停顿时间
  
  在G1垃圾回收器中有一个至关重要的参数会影响到GC的表现，就是 -XX:MaxGCPauseMills,它的默认值是200毫秒
  
  也就是我们每次触发GC的时候导致的系统停顿时间 Stop the world 不要超过200ms,避免系统因为GC长时间卡死。
  
  这个参数我们可以先保持一个默认值，继续往下分析看看，不着急忙下结论
  
  ### 到底多长时间会触发新生代GC？
  
  有一个问题，就是系统运行起来之后，会不停的在新生代的Eden区域分配对象，按照之前的推算是每秒分配3MB的对象，如下图
  
  
  ![](https://user-gold-cdn.xitu.io/2020/2/3/17009d4ad705467c?w=477&h=289&f=png&s=16870)
  
  那什么时候Eden的区域会不够呢？
  
  前面我们说过 -XX:G1MAXNEWSIZE参数规定了新生代最大的堆内存空间
  
  那么难道必须得随着系统运行一直给新生代分配更多得Region,直到新生代占据了60%之后，再进行GC？
  
  G1肯定不是这样玩的
  
  我们假设G1回收300Region需要 200ms
  
  那么很有可能系统运行时，G1呈现出现如下的运行效果
  
  首先，随着系统运行，每秒创建3MB的对象，大概1分钟左右就会塞满100个Region,如下图所示
  
  ![](https://user-gold-cdn.xitu.io/2020/2/3/17009dc5a2e6d587?w=549&h=246&f=png&s=17818)
  
  此时很可能G1会觉得  ，要是我现在就触发GC，那么回收区区200MB 只需要大概几十ms,最多就让系统停止几十ms而已，跟我的主人设定的参数200Ms还相差很远。
  
  如果现在gc, 那么每分钟都要GC会不会太频繁了，好像没有这个必要
  
  所以还不如给新生代先增加一些Region,然后让系统继续运行再新生代Region中分配对象好了，这样就不用过于频繁触发新生代gc了，此时如图所示
  
  ![](https://user-gold-cdn.xitu.io/2020/2/3/17009e481247827b?w=729&h=381&f=png&s=29165)
  
  然后系统继续进行，一直到可能300个Region都占满了，此时通过计算发现回收300个Region大概需要200ms,那么可能这个时候就会触发一次新生代gc了
  
  G1是非常灵活的，它会根据你设置的时间 给新生代不停的分配更多Region
  
  然后到一定程度，感觉差不多了，就会触发新生代gc,保证新生代Gc的时候导致的系统停顿时间再你预设的范围内
  
  ### 新生代如何优化
  
  其实就是优化-XX:MaxGCPauseMills参数 
  
  如果这个参数设置小了 ，那么说明每次gc的停顿时间很短，但是很频繁
  
  如果这个参数设置大了 停顿的时间就会非常长，
  
  所以这个参数到底如何设置，需要结合工具来测试，来达到一个合理的值
  
  ### 老年代如何优化
  
  其实也是核ParNew +CMS 控制不必要的对象进入老年代就好了，也是-XX:MaxGCPauseMills这个参数，
  
  大家可以想象一下 如果这个参数很大，那么经过新生代的gc后，就会导致Survivor区域放不下那么多的对象，那么这些对象就会进入老年代了
  
  或者是因为动态年龄判断进入老年代了，所以说还是设置这个参数
  
  
  ## 结尾
  其实也就是把G1简单的说了一下，后面还有具体的干货
  
  > 因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见，让我多学习，一起进步。
  ## 日常求赞
  > 好了各位，以上就是这篇文章的全部内容了，能看到这里的人呀，都是**真粉**。
  
  > 创作不易，各位的支持和认可，就是我创作的最大动力，我们下篇文章见
  
  >六脉神剑 | 文 【原创】如果本篇博客有任何错误，请批评指教，不胜感激 ！
  