JVM:秒杀场景模拟jvm内存参数,垃圾回收算法,垃圾回收器,三色标记
2022-03-02 23:18:56 1 举报
从serial、parallel到cms到G1整体理解和个人见解分析。以及其底层实现并发标记的根基:三色标记
作者其他创作
大纲/内容
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eurek a‐server.jar
整个过程分为四个步骤:
JVM优化模块
设置大对象内存阈值为1M,基本上1M大小的对象是非常大的。
垃圾回收算法 及具体应用的垃圾收集器解析
CMS(concurrent mark sweep并发标记清理)收集器使用的该算法。应用于老年代。
方法区内存自动伸缩机制:
G1垃圾回收器,堆内存的分代划分 是用到才划分。初始化时,会先安装默认比例,划分出5%给年轻代,并按8:1:1的比例分配Eden区与S0/S1区。在JVM运行过程中,会不断的从空白区,为年轻代划分Regin,但年轻代的比例不会超过规定的比例(默认60%),当触发monitor gc时,会从空白区划分出老年代。逻辑分区的变化:一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是说Region的区域功能 可能会动态变化
算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标 记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
card n
大对象的限定规则:对象内存大于一个Regin内存大小的一半,就会直接分配到Humongous区
方法区-XX:MetaspaceSize-XX:MaxMetaspaceSize
线程1的虚拟机栈
每秒40单
卡页
方法区512M
MixedGC
配置CMS垃圾回收器配置
年轻代
应用程序线程
日活用户500W
简单的例子:比如平时的项目包都是几百M或者几个G,如果没有设置方法区大小,使用默认的,就会频繁触发FULL-GC,通过自动扩容机制从21M,慢慢的扩展到合适的大小。直观的现象就是你启动项目会花费很长时间。
保留内存
每个对象1kb一笔交易大概涉及到20个对象。
对于JDK8默认的垃圾回收器是-XX:+UseParallelGC(年轻代)和-XX:+UseParallelOldGC(老年代),如果内存较大(超过4个G,只是经验 值),系统对停顿时间比较敏感,我们可以使用ParNew+CMS(-XX:+UseParNewGC -XX:+UseConcMarkSweepGC)
很明显,只使用了一半的内存,所以只适合年轻代。因为年轻代的对象都是生命周期非常短,一次gc就基本回收了,而且没有大对象。
GC线程
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停 顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。CMS整体一次gc的时间肯定是比Parallel大,但是STW时间远小于Parallel。因为CMS并发标记和并发清理都是
但是这样的配置,如果系统双十一并发突增,峰值访问时,每秒200M,此时系统处理订单的速度降低,一笔订单可能要3秒才完成。这样可能导致存在S区的对象超过200M。堆积起来,可能二三十分钟就会触发一次FULL GC.Full gc的频率也不会太高,推荐设置每次gc后就进行一次整理。
逻辑分代,物理不再分代。将内存分为2048块Regin(独立空间),逻辑上有年轻代(E和S)、老年代、大对象区(Humongous)。默认初始化时年轻代对堆内存的占比是5%,年轻代占堆内存的最大比例是60%。
以将内存分为大小相同的两块,每次使用其中的一块。当这一块的 内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对 内存区间的一半进行回收。
B
源码示例
默认初始化值是21M,最大内存取决于硬件。当加载存放的类信息超过21M时,也会触发FULL-GC。GC后,如果有效的对象基本没有回收(比如还剩15M,代表有效对象很多,说明方法区内存需要扩大),jvm就会自动扩容方法区的内存大小。如果有效对象很少(比如还剩1M,代表不用那么大内存),jvm就会自动缩减方法区内存。
垃圾收集器
STW
当走第4行代码时,此时正好cms处于初始标记,加载a对象,a的属性有俩,扫描b,d是null不用扫描,此时a的所有引用都已经被扫描过了,可以标记为黑色。加载a的b对象,就需要加载c和d对象,当c扫描,d还没扫描时,这时候,b就是还有引用没有被扫描,此时b就是灰色对象。而d还没被扫描此时就是白色对象。c没有其他引用,扫描后就是黑色。
Drity?
此时的标记颜色
注意:GC停顿时间不能过低,一般要在100-300ms。eg:设置停顿时间为2ms,可能仅走完标记过程,还没进行gc回收就结束了。这样长期堆积就会触发full gc。
优点:并发收集、低停顿。缺点:
D
按特点分类
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8‐XX:MaxTenuringThreshold=5 ‐XX:PretenureSizeThreshold=1M ‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC‐XX:CMSInitiatingOccupancyFraction=92 ‐XX:+UseCMSCompactAtFullCollection ‐XX:CMSFullGCsBeforeCompaction=0
每秒300*20kb大概6M对象生成
有效对象
card table
card 1
调优思想:将生命周期短的短暂对象,尽量留在年轻代。(比如电商系统的订单对象,调大年轻代内存空间)将生命周期长的长期有效的对象,尽早进入老年代。(比如缓存,spring内部bean,通过修改年龄阈值从默认值15改为5。)
标记-复制算法
GC分类
线程2
多核小内存的(4-8G)
并发重置
JVM内存参数:堆:-Xms -Xmx 里面的新生代 -Xmn方法区:-XX:MetaspaceSize-XX:MaxMetaspaceSize虚拟机栈:-Xss
G1垃圾回收器(多核大内存)作用于整个堆
见核心参数5和6,配置触发fullgc的比例阈值,还可以配置自动调整阈值。这样就可以根据实际情况,预留出空间。
Eden1.6G
堆-Xms初始值 -Xmx最大值
图解
筛选回收
虚拟机栈-Xss
重新标记
亿级流量,每日一亿次点击
S0200M
1 public class ThreeColorRemark {2 3 public static void main(String[] args) {4 A a = new A();5 D d = a.b.d; // 1.读6 a.b.d = null; // 2.写7 e = new E();8 }9 }10 11 class A {12 B b = new B();13 D d = null;14 E e = null; }15 16 class B {17 C c = new C();18 D d = new D();19 }20 21 class C {22 }23 24 class D {25 } 26 class E {27 }
如何选择垃圾收集器
开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)
每秒1000单
初始标记
缺点及解决方案
一般设置512k或者1M足够了。
1. -XX:+UseConcMarkSweepGC:启用cms 2. -XX:ConcGCThreads:并发的GC线程数3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片) 4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次。与full gc的发生频率负相关,如果只是搞秒杀时,才会触发二三十分钟一次,就可以配置0.如果服务一直处于高压力,gc频率比较高,就要配置3-5次full gc才进行一次整理,但是不能太高,长时间不整理,会导致内存碎片化。5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比) 6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整 7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段 8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW 9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
1、分代收集(年轻代和老年代都是由G1收集)2、与cms一样也是并发并行3、通过并发标记+复制算法回收内存,实现类似于标记整理算法的回收方式。4、可指定停顿时间。
E
缺点
卡表card table是使用一个字节数组实现:CARD_TABLE[ ],每个元素对应着其标识的内存区域一块特定大小的内存块,称为“卡页”。卡页大小为512 bytes
可回收对象
执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并 发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是\"concurrent mode failure\",此时会进入stop the world,用serial old垃圾收集器来回收
考虑到还有其他操作,比如防重查询等等,最多每秒6*10M对象生成,放到Eden区。下完单交易结束,也就是说1秒后会变成可回收对象。
服务器14核8G
仅适用老年代,标记清理算法
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region,比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回 收时间有限情况下,G1当然会优先选择后面这个Region回收。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。
老年代
复制算法8G及以上
并发标记
标记-清理算法
暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
回收性价比(我自己定义的方便理解,源码中没有这个名词):花同样时间,回收的内存更多,也就是说region中的垃圾对象越多,通过复制算法,复制的存活对象就越少,性价比就越高。
核心参数
ParNew收集器(-XX:+UseParNewGC)
此时a.b.d已经置空,引用已经无效,但是之前标记D为有效对象,这样就导致了多标,产生浮动对象。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除
1.优先调整堆的大小让服务器自己来选择2.如果内存小于100M,使用串行收集器3.如果是单核,并且没有停顿时间的要求,串行或由JVM自动选4.如果允许停顿时间超过1秒,选择并行或者JVM自动选5.如果响应时间最重要,并且不能超过1秒,使用并发收集器6.4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上甚至几个T就用ZGC
只能等下次gc再进行回收。
发生引用字段赋值时,Hotspot使用写屏障维护卡表状态。
单核小内存的
读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来
每个用户点击20次
日常
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但 是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
satb_mark_queue().enqueue(pre_val)
老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收年轻代、部分老年代(按照回收性价比排序)和大对象区。G1正常回收都是做MixedGC,在设定的预期停顿时长内,进行gc。
当走第6行代码时,此时方法中新对象d指向了(原a.b.d所指向的对象),原a.b.d所指向的对象引用置空。
JVM
C
多标
card 2
“-XX:G1NewSizePercent”设置新生代初始占比-XX:G1MaxNewSizePercent设置最大比例
分代初始化及后面的演化过程
写屏障。类似于Java里的aop,当对象的成员变量的引用发生变化时,在赋值操作 前 或者 后 进行记录。增量更新:通过后置处理来讲新加的引用记录下来。SATB:在删除引用之前,前置将原始引用记录下来。
新增引用
... ...
为什么cms使用增量更新,G1使用SATB?
比如老年代此时有1000个 Region都满了,但是预期停顿时间设置的是200ms,那本次垃圾回收只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集 合),尽量把GC导致的停顿时间控制在我们指定的范围内。并且会按每个region回收所花费的时间来排序,选择花费时间更少的优先回收。
并发清理
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
cms会根据配置的修正方式进行修正漏标,宗旨是通过多标来解决漏标,因为多标可以等下次回收,但漏标会导致标记后创建的新有效对象 因为没有被标记颜色,而清理掉。
A
d
YoungGC
特点
基础知识
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体 验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程 (基本上)同时工作。
双十一
堆
多核小内存的(2-3G)
标记-整理算法
S1200M
三色标记法
重置本次GC过程中的标记数据。
old1G
update_barrier_set_pre
例子
黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。标记后,再创建的对象会被标记为黑色对象,使其下次GC再回收。
年轻代:标记复制算法老年代:标记整理算法
集中在4个小时
筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期 望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划。
如果是在第6行进行的重新标记,那执行到7行时,后面的E对象就是白色没有标记的,这就是漏标
50万笔交易
并发标记最耗时占80%
重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对 象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,但远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记。
无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
将原值放到队列里,原因:这样可以异步从队列里取出再记录。
线程n的虚拟机栈-Xss
初始标记、并发标记与cms一样最终标记与cms的重新标记一样
垃圾回收算法
每秒60M27秒占满Eden区20多秒触发minor gc
复制回收时,发现没有足够的region存放存活对象时,触发FULL GC
10%的付费转化率
停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这 个过程是非常耗时的。(Shenandoah优化成多线程收集了)
有两大用途:一种用途是在JDK1.5 以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
可以通过参数设置,在一次回收过程中指定做几次筛选回收(默认8次)即回收过程中间要停顿几次,整个过程是STW进行gc,停顿一次,唤醒用户线程运行,然后再STW进行gc,这样在设定的 期望停顿时长内 进行8次筛选回收。
举例:如果此时cms处于并发标记
Serial(串连)收集器:开启年轻代-XX:+UseSerialGC开启老年代-XX:+UseSerialOldGC
CMS收集器(-XX:+UseConcMarkSweepGC(old))
服务器34核8G
Eden区放满后,不会直接进行gc,而是先判断当前内存进行收集所花费的时长,如果回收时 间远远小于参数 -XX:MaxGCPauseMills 设定的值,就对年轻代进行扩容,增加region。直到再次触发且回收时长接近 设定的停顿时长,才会触发young gc。
G1收集器参数设置
1、-XX:+UseG1GC:使用G1收集器 2、-XX:ParallelGCThreads:指定GC工作的线程数量 3、-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区 4、-XX:MaxGCPauseMillis:目标暂停时间(默认200ms) 5、-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%) 6、-XX:G1MaxNewSizePercent:新生代内存最大空间,默认:-XX:G1MaxNewSizePercent:60(代表60%)7、-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个 年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代 8、-XX:MaxTenuringThreshold:最大年龄阈值(默认15) 9、-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了 10、-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大。 11、-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。 12、-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都 是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清 理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立 即停止混合回收,意味着本次混合回收就结束了。
Full GC
服务器24核8G
目前仅应用于年轻代。Parallel(并发)收集器和Serial(串连)收集器和ParNew收集器,他们的年轻代版本都是使用的该算法。
有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 各垃圾收集器:CMS:写屏障 + 增量更新G1,Shenandoah:写屏障 + SATBZGC:读屏障
回收算法主要用的是复制算法,将一个region中的存活对象复制到临近的另一个region中,这种不会像CMS那样 回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。
1. 效率问题 (如果需要标记的对象太多,效率不高) 2. 空间问题(标记清除后会产生大量不连续的碎片)
一般都要设置好方法区大小‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
高并发电商场景估算
线程1
它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收 集器,后面会介绍到)配合工作
cms适用于4-8G内存的服务器,内存不是很大,通过增量更新记录所有变化的对象,再对这些对象进行寻根问底,扫描引用对象。而G1适用于8G以上甚至上百G的服务器,内存比较大,如果还使用增量更新去扫描会比较费时,选择使用SATB进行记录原始口罩,使所有发生变化的对象都记录下来,并将其标记为黑色,不用再次深度扫描,通过产生更多的浮动垃圾的方式来提高效率。
优点:简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的 单线程 收集效率,适用于单核服务器。缺点:多核CPU时,仍使用单线程收集,可收集上限内存较低。
漏标的解决方案:
300单/秒
一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0.标识1代表此卡页已经drityGC时,只要筛选本收集区的卡表中变脏的元素加入GCRoots里。
标记步骤
年轻代E:S内存比例:8:1:1
ParNew收集器其实跟Parallel收集器很类似。区别主要在于它可以和CMS收集器配合使用
JDK8默认的新生代和老年代收集 器: Parallel Scavenge收集器和Parallel Old收集器。在注重吞吐量以及 CPU资源的场合,优先考虑。
在并发操作时,与用户应用线程争抢资源,吞吐量比Parallel低。
最终标记
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8 ‐XX:MaxTenuringThreshold=5 ‐XX:PretenureSizeThreshold=1M
集中在10分钟内
JVM内存模型参数图解
Parallel收集器其实就是Serial收集器的多线程版本,默认的收集线程数跟cpu核数相同,当然也可以用参数(- XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
新生代 -Xmn
新去除引用
见核心参数3和4,可以配置几次gc后进行整理。
它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生
Parallel Scavenge(并行)收集器开启年轻代-XX:+UseParallelGC开启老年代-XX:+UseParallelOldGC
0 条评论
下一页