垃圾收集算法
常见的垃圾收集算法包括:
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
JVM 的垃圾收集算法是使用了分代收集算法,复制算法、标记-整理算法。三种算法都有使用。使用分代收集算法,将 JVM 中的内存分为新生代和老年代,新生代采用复制算法收集,而老年代采用的是标记-整理算法。
1 标记-清除算法
标记-清除算法分为“标记”和“清除”两个阶段,首先通过可达性分析,标记出所有需要回收的对象,然后统一回收所有被标记的对象。
标记-清除算法有两个缺陷,一个是效率问题,标记和清除的过程效率都不高,另外一个就是,清除结束后会造成大量的碎片空间。有可能会造成在申请大块内存的时候因为没有足够的连续空间导致再次 GC。
2 复制算法
为了解决碎片空间的问题,出现了“复制算法”。复制算法的原理是,将内存分成两块,每次申请内存时都使用其中的一块,当内存不够时,将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。java培训
复制算法解决了空间碎片的问题。但是也带来了新的问题。因为每次在申请内存时,都只能使用一半的内存空间。内存利用率严重不足。
JVM 中新生代采用的就是复制算法进行的GC。针对内存利用率不足的问题做了一些优化。
IBM公司的专门研究表明,新生代中的对象 98% 是“朝生夕死”的,意思是说,在新生代中,经过一次 GC 之后能够存活下来的对象仅有 2% 左右。
所以并不需要按照1:1的比例划分出两块内存空间。而是将内存划分出三块,一块较大的 Eden 区,和两块较小的 Survivor 区。其中 Eden 区占 80% 的内存,两块 Survivor 各占 10% 的内存。在创建新的对象时,只使用 Eden 区和其中的一块 Survivor 区,当进行 GC 时,把 Eden 区和 Survivor 区存活的对象全部复制到另一块 Survivor 区中,然后清理掉 Eden 区和刚刚用过的 Survivor 区。
这种内存的划分方式就解决了内存利用率的问题,每次在创建对象时,可用的内存为 90%(80% + 10%) 当前内存容量。
3 标记-整理算法
复制算法在 GC 之后存活对象较少的情况下效率比较高,但如果存活对象比较多时,会执行较多的复制操作,效率就会下降。而老年代的对象在 GC 之后的存活率就比较高,所以就有人提出了“标记-整理算法”。
标记-整理算法的“标记”过程与“标记-清除算法”的标记过程一致,但标记之后不会直接清理。而是将所有存活对象都移动到内存的一端。移动结束后直接清理掉剩余部分。
4 分代收集算法
分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老年代。