文章>关于数组动态扩容导致频繁GC的问题,我还有话说>

关于数组动态扩容导致频繁GC的问题,我还有话说

你假笨
gc
2周前

概述

通过上篇抓了一个导致频繁GC的鬼–数组动态扩容关于数组动态扩容导致频繁GC的文章或许GET到了这么一些点

  • List里新数组在新生代分配
  • 通过老生代使用率达到了阈值触发的CMS GC,会把新生代里的对象作为GC ROOT的一部分,从而阻止了那些byte数组被回收
  • 通过-XX:+CMSScavengeBeforeRemark这个参数可以解决这个问题

那是否还想过这么一些问题呢?

  • List里新数组是否可以在老生代分配?
  • -XX:+CMSScavengeBeforeRemark该参数是否一定会触发YGC?

接下来主要围绕这两个问题展开,算是对上篇文章的一个补充

新数组在哪里分配

老实说,如果之前线上碰到的那个问题新数组是在老生代分配的话,那就不会有上篇文章,更不会有这篇文章,但是到底有没有可能在老生代分配呢?其实是有可能的
image.png
上面的代码是慢速路径分配的代码,先判断是否应该到新生代分配
image.png
其中 _pretenure_size_threshold_words 的值是jvm参数PretenureSizeThreshold指定的,如果我们指定了这个值,那意味着如果我们单次要求分配的超过了这个值就想到老生代去分配,当然这个值默认是0,表示不会对对象的大小做check,都优先到新生代分配
如果不到新生代分配,或者新生代分配不了,然后有判断是否会到老生代分配的条件
image.png

  • 如果要分配的内存超过了eden大小,那毫无疑问只能到老生代分配了
  • 如果GC_locker正在起作用,有线程正在通过JNI操作临界内存,并且操作完之后会触发一次gc的话,那先到old分配解燃眉之急
  • 如果上一次YGC效果并不好,比如晋升失败,或者因为预测到上一次YGC可能是一次失败的YGC而没做YGC了等,那就直接到老生代分配吧

所以新的数组分配还是有各种可能在老生代分配的,因为随着数组的不断扩容,数组也会变得越来越大,当大到某个程度,或者到上面的某个条件成立的时候,还是可能在老生代直接分配的

那如果新数组是在老生代分配的话,那经过CMS GC就会将老生代里不可达的那个新数组给回收了,那就不存在新生代指向老生代的跨代引用,因而其实并不会发生这样的问题

CMSScavengeBeforeRemark一定能触发YGC吗

CMSScavengeBeforeRemark这个参数本意是希望在CMS GC remark之前做一次YGC,正常情况下其实是会做一次YGC的,这个参数的好处是如果YGC比较有效果的话是能有效降低remark的时间长度,可以简单理解为如果大部分新生代的对象被回收了,那作为根的部分少了,从而提高了remark的效率

但是,但是这个YGC一定会发生吗?下面对CMS GC remark之前你看到的现象分为三种情况:

  • 你压根看不到YGC的日志
  • 你可以看到YGC日志,同时能看到内存被回收了
  • 你可以看到YGC日志,但是发现内存根本没被回收

对于看不到GC日志的情况,可以肯定是没有发生YGC,这种情况通过是因为上面提到的GC_locker导致的,有线程正在访问临界区的内存,访问这些内存的时候是不允许发生GC的,因为他们正在直接操作内存,而GC是会对对象做迁移的。另外你可能平时还会观察到一个非常奇怪的现象,偶尔你会看到有连续的两次YGC,其中后面那一次你会看到新生代使用的内存其实非常少但是也触发了一次YGC,其实就是因为GC_locker有补偿GC的逻辑

对于第二种情况,你看到了YGC日志,同时也发现内存被回收了,这个毫无疑问,就是真的做了一次正常的YGC

对于第三种情况,其实可能并没有做YGC,当然也不排除确实做了YGC,但是确实效果不好的情况,那什么情况下会不做YGC呢,我们看看下面在做YGC之前的代码
image.png

如果这个判断成立,那就直接return了。
而collection_attempt_is_safe在ParNew下的实现如下
image.png
最后一条相对比较关键,具体实现如下:
image.png
如果老生代可用的空间足以容得下之前的新生代平均晋升的size,或者容的下新生代现在使用的size,那说明是可以正常做YGC的,那接下来就会准备做YGC,但是如果上面的条件都不满足,那就会认为这次YGC做起来会没什么效果,或者比较危险,最好不做,于是就会直接返回,但是这种情况下,YGC的日志还是照常会打的,你看到的现象就是YGC前后内存大小不变

总结

针对动态数组扩容的问题,可以有两种情况

  • 如果新扩容的数组是在老生代的,如果该数组不可达了,那经过CMS GC是会回收数组里的内容的
  • 如果新扩容的数组是在新生代的,如果该数组不可达了,CMSScavengeBeforeRemark无法完全保证YGC能顺利进行,如果真的做了YGC,那肯定可以回收掉数组里的不可达的那些byte数组,如果因为各种限制导致YGC并没有做,那还是无法回收掉数组里面的内容
4002 阅读
请先登录,再评论

评论列表

暂无回复,快来写下第一个回复吧~