jvm调优的一些思考

JVM垃圾回收需要考虑的两个点:停顿时间短、高吞吐量。

不同的要求,不同的收集模式(收集过程)。

如何保证垃圾收集准确,速率高,线程数多少,占用的内存情况都需要考虑。

 

为什么需要stw(stop the world)?

jvm GC优化:

  1. 硬件配置
  2. 业务场景(吞吐量,延迟,内存使用)

 

串行收集:

串行垃圾收集器有:

Serial收集器\Serial Old收集器:

优势:单线程也意味着复杂度更低、占用内存更少

缺点:如果是多核服务器,无法利用多核的优势。

使用场景:适合堆内存不高、单核甚至双核CPU的场合

使用算法:Serial(复制清除算法)、SerialOld(标记-整理算法)

收集区域:Serial-年轻代、SerialOld-老年代

调优参数:

 

并行收集:

有的资料说这种模型是并行,这里我并不觉的是并行,我觉得还是串行,这里只是在做GC的时候,用多个线程去处理。解决了SerialGC单线程无法利用多核cpu优势的问题。

Parallel Scavenge 收集器:

优势:多线程

缺点:

使用场景:

使用算法:复制-清除算法

收集区域:年轻代

调优参数:-XX:MaxGCPauseMills-XX:GCTimeRatio,调整新生代空间大小,来降低GC触发的频率

并发收集:

并发收集也就是用户线程后GC线程同时工作,这也就是说线程数会被GC工作占用一部分,所以说这里吞吐量可能不是你业务主要考虑的。

工作过程:初始标记(initial mark)->并发标记(concurrent mark)-> 预清理->可中断预处理->重新标记->回收

详细查阅:

jvm-cms垃圾收集器

CMS 收集器:

优势:

缺点:

  1. 用户线程和GC线程并发执行,这里GC线程会占用内存,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制。这时候会降级到串行老年代收集器,将会以STW的方式进行一次GC,从而造成较大停顿时间;
  2. 标记清除算法->空间碎片,老年代空间会被耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。

使用场景:

  1. 适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器
  2. 延时要求高,吞吐量要求低

使用算法:标记-清除算法

收集区域:老年代

调优参数:-XX:CMSFullGCsBeForeCompaction

 

Garbage First (G1)

他对分代的模型不一样,逻辑上将内存分为很多个小的区域(region)。

Region

将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
 

对比:

  1. 之前我们GC的区域是整个年轻代或者老年代,现在G1收集的是其中某个或者某几个region
  2. 收集的时间不一样,之前的会在内存将要耗尽的时候,或者分配不足的时候回收,G1会去检测老年代是否需要回收,主动进行垃圾的回收(应该是这样啊)
  3.  

优势:

缺点:

使用场景:

使用算法:

收集区域:年轻代\老年代

调优参数:

G1收集器什么时候发生GC

我们知道G1和其他收集器不同,是将内存划分成多个不同的region区(包括eden、survivor、Humongous、old 、free),之前的会根据新生代和老年代的大小限制来触发gc,现在没有这些了。G1是根据什么来触发GC的呢?

两个配置:

配置最大的eden region 百分比:-XXG1MaxNewSizePercent

设置的stopTheWorld时间:

这里不是非要达到这个百分比,才会触发GC。我们知道这里eden region会不断的增加(这里g1与其他收集器不同,开始分配的eden region比较少,不够的时候,会继续分配 eden region),G1会不断记录region中需要回收的card,并计算回收这些card(region)所需要的时间,如果这个时间和我们设置的(stw)时间差不多,则触发一次GC。

当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC

(保证新生代GC的时候导致的STW在你预设范围内)

ygc触发的契机就是在Eden分区数量达到上限时。一次ygc会回收所有的Eden和survivor区

 

 

TLAB(Thread Local Allocation Buffer)本地线程缓冲区: 应该是给用户线程分配的内存,然后新生成的对象,也就是在这块内存中,也就是eden region会在这个部分。

PLAB(Promotion Local Allocation Buffer) 晋升本地分配缓冲区
在ygc中,对象会将全部Eden区存货的对象转移(复制)到S区分区。也会存在S区对象晋升(Promotion)到老年代。这个决定晋升的阀值可以通过MaxTenuringThreshold设定。晋升的过程,无论是晋升到S还是O区,都是在GC线程的PLAB中进行。每个GC线程都有一个PLAB。
 

 

 

 

Remember Set (RSet):

每个region会有一个RSet,记录老年代中引用eden region中对象(每个card)的指针。

ygc的时候,只要扫描RSet中的其他old区对象对于本young区的引用,不需要扫描所有old区

mixed gc时,扫描Old区的RSet中,其他old区对于本old分区的引用,一样不用扫描所有的old区。提高了GC效率。因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。 
为了防止RSet溢出,对于一些比较“Hot”的RSet会通过存储粒度级别来控制。RSet有三种粒度,对于“Hot”的RSet在存储时,根据细粒度的存储阀值,可能会采取粗粒度。 


这三种粒度的RSet都是通过PerRegionTable来维护内部数据的

Per Region Table (PRT)

RSet在内部使用Per Region Table(PRT)记录分区的引用情况。由于RSet的记录要占用分区的空间,如果一个分区非常"受欢迎",那么RSet占用的空间会上升,从而降低分区的可用空间。G1应对这个问题采用了改变RSet的密度的方式,在PRT中将会以三种模式记录引用:

稀少:直接记录引用对象的卡片索引
细粒度:记录引用对象的分区索引
粗粒度:只记录引用情况,每个分区对应一个比特位
由上可知,粗粒度的PRT只是记录了引用数量,需要通过整堆扫描才能找出所有引用,因此扫描速度也是最慢的。
 


 

Collection Sets(CSets):

GC收集时,需要或者说被指定的需要回收的region集合。在任意一次收集暂停中,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。因此无论是年轻代收集,还是混合收集,工作的机制都是一致的。年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中

YoungCSets:只容纳年轻代分区

MixedCSets: 候选老年代分区的CSet准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比

-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限。

 

 

 

YoungGC年轻代收集
在分配一般对象(非巨型对象)时,当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC。每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。到Old区的标准就是在PLAB中得到的计算结果。因为YoungGC会进行根扫描,所以会stop the world。

YoungGC的回收过程如下:

根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
处理Dirty card,更新RSet.
扫描RSet,扫描RSet中所有old区对扫描到的young区或者survivor去的引用。
拷贝扫描出的存活的对象到survivor2/old区
处理引用队列,软引用,弱引用,虚引用(下一篇优化中会再讲一下这三种引用对gc的影响)


MixGC混合收集
MixedGC是G1 GC特有的,跟Full GC不同的是Mixed GC只回收部分老年代的Region。哪些old region能够放到CSet里面,有很多参数可以控制。比如G1HeapWastePercent参数,在一次younggc之后,可以允许的堆垃圾百占比,超过这个值就会触发mixedGC。G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,这个old分区会被放入CSet。源码可以看下gc/g1/collectionSetChooser。 
MixedGC一般会发生在一次YoungGC后面,为了提高效率,MixedGC会复用YoungGC的全局的根扫描结果,因为这个Stop the world过程是必须的,整体上来说缩短了暂停时间。

MixGC的回收过程可以理解为YoungGC后附加的全局concurrent marking,全局的并发标记主要用来处理old区(包含H区)的存活对象标记,过程如下:

1. 初始标记(InitingMark)。标记GC Roots,会STW,一般会复用YoungGC的暂停时间。如前文所述,初始标记会设置好所有分区的NTAMS值。 
2. 根分区扫描(RootRegionScan)。这个阶段GC的线程可以和应用线程并发运行。其主要扫描初始标记以及之前YoungGC对象转移到的Survivor分区,并标记Survivor区中引用的对象。所以此阶段的Survivor分区也叫根分区(RootRegion)

3. 并发标记(ConcurrentMark)。会并发标记所有非完全空闲的分区的存活对象,也即使用了SATB算法,标记各个分区。 
4. 最终标记(Remark)。主要处理SATB缓冲区,以及并发标记阶段未标记到的漏网之鱼(存活对象),会STW,可以参考上文的SATB处理。 
5. 清除阶段(Clean UP)。上述SATB也提到了,会进行bitmap的swap,以及PTAMS,NTAMS互换。整理堆分区,调整相应的RSet(比如如果其中记录的Card中的对象都被回收,则这个卡片的也会从RSet中移除),如果识别到了完全空的分区,则会清理这个分区的RSet。这个过程会STW。

清除阶段之后,还会对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。

FullGC
G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。FullGC使用的是stop the world的单线程的Serial Old模式,所以一旦触发FullGC则会STW应用线程,并且执行效率很慢。JDK 8版本的G1是不提供Full gc的处理的。对于G1 GC的优化,很大的目标就是没有FullGC。
 

更多可以参考(比较详细):

https://blog.csdn.net/coderlius/article/details/79272773 

https://blog.csdn.net/lijingyao8206/article/details/80513383

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值