共计 1077 个字符,预计需要花费 3 分钟才能阅读完成。
导读 | 我们聊一下 JVM 是如何进行回收的。 |
顾名思义,其过程分为两个阶段,分别是 标记 和清除。首先标记出所有需要回收的对象,然后统一对标记的对象进行回收。这个算法的十分的局限,首先标记和清除的两个过程效率都不高,而且这样的清理方式会产生大量的内存碎片,什么意思呢?
就是虽然总体看起来还有足够的剩余内存空间,但是他们都是以一块很小的内存分散在各个地方。如果此时需要为一个大对象申请空间,即使总体上的内存空间足够,但是 JVM 无法找到一块这么大的连续内存空间,就会导致触发一次 GC。
其大致的思路是,将现有的内存空间分为两半 A 和 B,所有的新对象的内存都在 A 中分配,然后当 A 用完了之后,就开始对象存活判断,将 A 中还存活的对象复制到 B 去,然后一次性将 A 中的内存空间回收掉。
这样一来就不会出现使用 标记 - 清除 所造成的内存碎片的问题了。但是,它仍然有自己的不足。那就是以内存空间缩小了一半为代价,而在某些情况下,这种代价其实是很高的。
堆中新生代就是采用的复制算法。刚刚提到过,新生代被分为了 Eden、From Survivor、To Survivor,由于几乎所有的新对象都会在这里分配内存,所以 Eden 区比 Survivor 区要大很多。因此 Eden 区和 Survivor 区就不需要按照复制算法默认的 1:1 的来分配内存。
在 HotSpot 中 Eden 和 Survivor 的比例默认是 8:1,也就意味着只有 10% 的空间会被浪费掉。
看到这你可能会发现一个问题。
既然你的 Eden 区要比 Survivor 区大这么多,要是一次 GC 之后的存活对象的大小 大于Survivor 区的总大小该怎么处理?
的确,在新生代 GC 时,最坏的情况就是 Eden 区的所有对象都是存活的,那这个 JVM 会怎么处理呢?这里需要引入一个概念叫做 内存分配担保。
当发生了上面这种情况,新生代需要老年代的内存空间来做担保,把 Survivor 存放不下的对象直接存进老年代中。
标记 - 整理其 GC 的过程与标记 - 清楚是一样的,只不过会让所有的存活对象往同一边移动,这样一来就不会像标记 - 整理那样留下大量的内存碎片。
这也是当前主流虚拟机所采用的算法,其实就是针对不同的内存区域的特性,使用上面提到过的不同的算法。
例如新生代的特性是大部分的对象都是需要被回收掉的,只有少量对象会存活下来。所以新生代一般都是采用 复制算法。
而老年代属于对象存活率都很高的内存空间,则采用 标记 - 清除 和标记 - 整理 算法来进行垃圾回收。
![post-qrcode](https://www.xgss.net/wp-content/uploads/2023/07/二维码-微信公众号256x256.jpg)