返回
JDK中因一个bug引发的反思
后端
2023-10-13 18:30:43
<br>
JDK的一个bug引发了关于并发和内存泄漏的思考。这个bug的存在条件是当一个CHM扩容时,由于扩容是发生在其他线程中,导致需要等待并发失败事件(当old segment没有free list,或者,没有足够大小的free list,无法容纳扩容后的大小时),导致扩容永远无法完成,那么在扩容过程中,就没有可用的CHM可用,而new allocation在持续发生,就会导致程序中的所有资源都将全部耗尽,同时每次GC或CHM ALLOCATE时候,程序就会触发一次并发事件,在并发失败的情况下,又会发生一次并发失败事件,直到把JVM卡死。
CHM 的扩容以 ConcurrentResizeInProgress 字段为依据,扩容是否完成,取决于这个字段的值,简化就是:只要并发失败事件发生了,就会导致 ConcurrentResizeInProgress = true。而当老年代经过 GC后,检查并发失败事件时,可以通过 `sc >>> RESIZE_STAMP_SHIFT == rs + MAX_RESIZERS`,进而判断并发失败事件是否发生了。
ConcurrentResizeInProgress 的值,可以通过 Unsafe.get/putXXX 读写,由专门的并发扩容线程更新,而其他线程访问这个字段,没有经过同步。
由此可以知道,并发失败事件会造成对象无法GC,从而导致内存泄漏,当内存泄漏后,ConcurrentResizeInProgress 字段可能会一直为 true,但已经因为内存泄漏,从而导致程序中断运行。
一些编译器有一个潜在的风险,未设置 `-XX:-ResizePLAB` 选项,就会可能导致 JVM 在运行时内存溢出。对于 JDK1.8 版本,若 HeapSize 较大,则需要明确设置 `-XX:+ResizePLAB`,否则会出现内存泄漏问题。
随着 JVM 版本的更新,这个问题也已经修复了,在 JDK17、19 等版本中,ConcurrentResizeInProgress 这个字段已经变成 atomicField 了,并加锁确保了正确性,因此就算发生了内存泄漏,也不会导致 ConcurrentResizeInProgress 字段无法正确更新。在 JDK17 中, `-XX:-ResizePLAB` 已经作为默认参数。
总之,这个 bug 的启示在于,并发和内存泄漏的问题,可能会导致程序出现难以预料的情况。在实际开发中,需要特别注意并发和内存泄漏的问题,并采取措施来避免这些问题。在使用 JDK 时,也需要关注 JDK 的版本和相关的设置,以确保程序的稳定性和可靠性。