JVM常见GC算法
1.标记-清除算法(Mark-Sweep)
-
算法分为标记和清除两个阶段:
- 首先标记出所有需要回收的对象,
- 然后回收所有需要回收的对象。
-
缺点:
- 效率问题,标记和清理两个过程效率都不高。效率不高,需要扫描所有对象,堆越大,GC越慢。(GC的速度跟堆里的对象成正比)
- 空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。
存在内存碎片问题。GC的次数越多,碎片越严重。
Runtime Stack运行时虚拟机栈----用根搜索算法
栈看作Root
通过根搜索算法,计算出 D、G、F、J、M没有引用,所以就回收了
2.标记-整理(压缩)算法(Mark-Compact)
标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象向一端移动,然后直接清理掉这端边界以外的内存。
- 优点:没有内存碎片
- 缺点:比Mark-Sweep耗费更多的时间进行compact
3.复制算法(Copying)
将可用内存划分为两块,每次只是用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉。
这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就行,实现简单,运行高效。
- 缺点:
- 这种算法的代价是将内存缩小为原来的一半,代价高昂。
- 复制收集算法在对象存活率高的时候,效率有所下降。
现在的商业虚拟机中都是用了这种复制算法来回收新生代(刚new出来的对象都在新生代中)。
- 只需要扫描存活的对象,效率更高
- 不会产生碎片
- 需要浪费额外的内存作为复制区
- 复制算法非常适合生命周期比较短的对象,因为
每次GC总能回收大部分的对象,复制的开销比较
小 - 根据IBM的专门研究,98%的Java对象只会存活1
个GC周期,对这些对象很适合用复制算法。而且
不用1: 1的划分工作区和复制区的空间 - Oracle Hotspot虚拟机默认eden和survivor的大小比例是8:1:1,也就是每次只有10%的内存是“浪费”的。(被使用的eden+survivor空间叫To Survivor,空闲的10%叫From Survivor)
-
- 将内存分为一块较大的eden空间和2块较少的survivor空间,
- 每次使用eden和其中一块survivor
- 当回收时将eden和survivor还存活的对象一次性拷贝到另外一块survivor空间上
- 然后清理掉eden和用过的survivor
如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。