当前位置: 首页 > 科技观察

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

时间:2023-03-17 14:31:40 科技观察

概述通过之前关于数组动态扩容导致频繁GC的文章,是假设-导致频繁GC的另一个鬼魂--动态数组展开,大家可能GET到这样的地步。List中的新数组在新生代分配,老年代使用率达到阈值触发的CMSGC会将新生代中的对象作为GCROOT的一部分,从而防止那些字节数组被回收通过参数-XX:+CMSScavengeBeforeRemark就可以解决这个问题,你是否还想过这样的问题?List中的新数组可以在老年代分配吗?参数-XX:+CMSScavengeBeforeRemark会触发YGC吗?接下来,我们将重点关注这两个问题,这是对上一篇文章的补充。新数组分配在哪里?说实话,如果在老年代分配新数组之前网上遇到的问题,之前的文章都没有,更别说这篇了,但是在老年代分配有没有可能呢?事实上,这是可能的。上面的代码是慢速路径分配的代码。首先判断是否应该分配给新生代。_pretenure_size_threshold_words的值由jvm参数PretenureSizeThreshold指定。如果我们指定了这个值,就意味着如果我们要求单次分配超过这个值,就期望在老年代进行分配。当然这个值默认为0,也就是不检查对象的大小,在新生代先分配。如果没有分配给新生代,或者新生代无法分配,那么就有条件判断是否分配给老年代。如果要分配的内存超过了eden的大小,毫无疑问只能分配给老年代。如果GC_locker在工作,有线程通过JNI操作临界内存,操作后会触发gc,那么会先到老年代。分配紧急需求。如果上次的YGC表现不好,比如提升失败,或者你因为预测上次的YGC可能是失败的YGC而没有做YGC等等,那就直接去老年代分配吧!所以,新的数组分配在老年代分配还是有多种可能的,因为随着数组的不断扩大,数组会越来越大,当达到一定程度,或者上面的某个条件成立时有的时候,可能还是直接在老年代分配。如果新数组是在老年代分配的,老年代不可达的新数组在CMSGC后会被回收,不会出现新生代到老年代的跨代引用,所以实际上不会出现这样的问题。CMSScavengeBeforeRemark会触发YGC吗?CMSScavengeBeforeRemark的参数是为了在CMSGCremark之前做一次YGC。一般情况下,它实际上会做一个YGC。这个参数的好处是如果YGC更有效的话,可以有效的减少remarktime的长度,可以简单理解为如果新生代中的大部分对象都被回收了,那么作为roots的部分就会变少,从而提高备注效率。但是,这个YGC一定会发生吗?你在CMSGCremark之前看到的以下现象分为三种情况:你完全看不到YGC日志,你可以看到YGC日志,同时你可以看到内存被回收你可以看到YGClog,却发现内存根本没有被回收。对于看不到GC日志的情况,可以肯定没有发生YGC。这种情况是上面提到的GC_locker造成的,有些线程在访问critical区的内存,访问这些内存时不允许GC,因为他们是直接操作内存,GC会迁移对象。另外,平时你也可能会观察到一个很奇怪的现象。偶尔,您会看到两个连续的YGC。后面的时间,你会看到新生代使用的内存其实很小,但是也会触发一次YGC。其实是因为GC_locker有补偿GC的逻辑。第二种情况,你看YGC日志,发现内存被回收了。毫无疑问,这是一个普通的YGC。对于第三种情况,YGC其实可能做不出来。当然也不排除YGC做过,只是效果不好。那么什么情况下YGC不会做。下面先看看做YGC之前的代码。.如果这个判断为真,那么直接返回。ParNew下collection_attempt_is_safe的实现如下。最后一个比较关键,具体实现如下:如果老年代的可用空间足以容纳上一个新生代的平均提升大小,或者新生代当前使用的大小,那就说明它正常做YGC是可以的,然后准备下次做YGC,但是如果不满足上面的条件,那么我会认为这次YGC会失效或者危险,所以不想做.所以会直接返回,但是这种情况下,YGC的日志还是会照常打,看到的现象是YGC前后内存大小没有变化。综上所述,总结一下,对于动态数组扩展的问题,有两种情况。如果新扩容的数组在老年代,如果数组不可达,CMSGC后会回收数组内容。如果新扩容的数组在新生代,如果数组不可达,CMSScavengeBeforeRemark不能完全保证YGC能顺利进行。如果YGC真的搞定了,那么数组中不可达的字节数组肯定是可以回收的。如果由于各种限制,YGC没有做,所以还是无法回收数组的内容。【本文为专栏作家李嘉鹏原创文章,转载请微信公众号(你个假笨蛋,id:lovestblog)联系作者授权转载】点此查看本作者更多好文