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

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

[[188968]]

概述

通过上篇关于数组动态扩容导致频繁GC的文章假笨说-又抓了一个导致频繁GC的鬼--数组动态扩容大家或许GET到了这么一些点。

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

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

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

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

新数组在哪里分配

老实说,如果之前线上碰到的那个问题新数组是在老生代分配的话,那就不会有上篇文章,更不会有这篇文章,但是到底有没有可能在老生代分配呢?其实是有可能的。

上面的代码是慢速路径分配的代码,先判断是否应该到新生代分配

其中_pretenure_size_threshold_words的值是jvm参数PretenureSizeThreshold指定的,如果我们指定了这个值,那意味着如果我们单次要求分配的超过了这个值就想到老生代去分配,当然这个值默认是0,表示不会对对象的大小做check,都优先到新生代分配。

如果不到新生代分配,或者新生代分配不了,然后有判断是否会到老生代分配的条件。

  • 如果要分配的内存超过了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之前的代码。

如果这个判断成立,那就直接return了。

而collection_attempt_is_safe在ParNew下的实现如下

***一条相对比较关键,具体实现如下:

如果老生代可用的空间足以容得下之前的新生代平均晋升的size,或者容的下新生代现在使用的size,那说明是可以正常做YGC的,那接下来就会准备做YGC,但是如果上面的条件都不满足,那就会认为这次YGC做起来会没什么效果,或者比较危险,***不做,于是就会直接返回,但是这种情况下,YGC的日志还是照常会打的,你看到的现象就是YGC前后内存大小不变。

总结

还是总结下吧,针对动态数组扩容的问题,可以有两种情况

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

【本文是51CTO专栏作者李嘉鹏的原创文章,转载请通过微信公众号(你假笨,id:lovestblog)联系作者本人获取授权】

戳这里,看该作者更多好文

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2017-04-17 11:07:19

GC数组动态扩容

2014-03-26 11:15:26

Hive

2021-11-12 08:07:31

SQL缓存RabbitMQ

2012-08-16 10:43:10

GC

2023-04-30 12:44:28

GC应用性能

2010-08-25 10:24:40

2009-06-24 17:32:40

动态加载AppDoma

2020-02-16 11:13:39

远程办公工具技术

2021-11-19 11:36:42

语言string字符串

2020-10-23 06:56:00

C语言动态字符串

2009-01-11 10:23:00

网络掉线频繁掉线

2017-06-09 08:49:07

加载器Full GCJVM

2012-05-15 09:49:03

TIME_WAITMySQL

2021-01-19 05:46:45

背包数组容量

2017-06-27 08:41:04

JVM设计缺陷GC

2017-11-14 07:32:14

ONOS动态扩容

2022-02-22 11:50:16

Python字典代码

2010-08-20 08:59:27

MongoDB

2023-10-25 09:35:38

Java性能

2010-07-20 16:14:42

点赞
收藏

51CTO技术栈公众号