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