GC算法[通俗易懂]

2022-09-07 15:16:44 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

JVM(Java Virtual Machine)

GC是什么?

  • 频繁收集 Young 区
  • 较少收集 Old 区
  • 基本不动 Perm 区

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代,因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)

  • 普通GC(minor GC):只针对新生代区域的GC
  • 全局GC(major GC or Full GC):针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC。

GC的三大算法

其实还有一种:引用计数法,但是不用了。

复制算法:MinorGC(普通GC)

年轻代中使用的是 Minor GC,这种GC算法采用的是复制算法(Copying)

Minor GC会把Eden中的所有活的对象都移到 Survivor 区域中,如果 Survivor区中放不下,那么剩下的活的对象就被移到Oldgeneration中,也即一旦收集后,Eden是就变成空的了。

当对象在Eden(包括一个 Survivor区域,这里假设是from区域)出生后,在经过一次 Minor GC后,如果对象还存活,并且能够被另外一块 Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor区域(即to区域)中,然后清理所使用过的Eden以及 Survivor区域(即fom区域),并且将这些对象的年龄设置为1,以后对象在 Survivor区每熬过一次 Minor GC,就将对象的年龄 1,当对象的年龄达到某个值时(默认是15岁,通过 -XX:MaxTenuringThreshold 来设定参数),这些对象就会成为老年代。

解释: 年轻代中的GC,主要是复制算法(Copying) Hotspot JVM把年轻代分为了三部分:1个Eden区和2个 Survivor区(分别叫from和to)。默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次 Minor GC 后,如果仍然存活,将会被移到Survivor区。对象在 Survivor区中每熬过一次 Minor GC ,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。 在GC开始的时候,对象只会存在于Eden区和名为“From”的 Survivor区,Survivor区”To“是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过 -XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到 “To”区域。经过这次GC后,Eden区和“From”区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为“To”的 Survivor区域是空的。Minor GC 会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%(From)的活动区间与另外80%中存活的对象转移到10%(To)的空闲区间,接下来,将之前90%的内存全部释放,以此类推 。

口诀:

谁空谁是 to,复制要交换。

总结:

  1. 优点:不会产生内存碎片,完整度极高。
  2. 缺点:浪费了这10%(To)的内存空间。

复制算法弥补了标记/清除算法中,内存布局混乱的缺点。不过与此同时,它的缺点也是相当明显的

  1. 它浪费了一半(1:1的这个一半,就是那10%)的内存,这太要命了。
  2. 如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

标记清除/标记整理算法:FullGC又叫 MajorGC(全局GC)

老年代一般是由标记清除或者是标记清除标记整理的混合实现

标记清除(Mark-Sweep)

缺点:

  1. 首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲。
  2. 其次,主要的缺点则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随机的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM 就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

标记整理(Mark-Compact)

注释:上面标记的是活的。

缺点:

  1. 标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

小结:

  • 内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)
  • 内存整齐度:复制算法=标记整理算法>标记清除算法
  • 内存利用率:标记整理算法=标记清除算法>复制算法

可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说更平滑一些,但效率上依然不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程。

难道就没有一种最优算法吗?

回答:无,没有最好的算法,只有最合适的算法。==========>分代收集算法(根据 JVM 不同的(新生、养老、永久)带,采用各自不同的算法)


面试题:

1. JVM内存模型以及分区,需要详细到每个区放什么。

2. 堆里面的分区:Eden,survival from to,老年代,各自的特点。

3. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别阐述。

4. Minor GC 与 Full GC 分别在什么时候发生。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148274.html原文链接:https://javaforall.cn

0 人点赞