JVM(三)--垃圾收集算法

    技术2022-07-11  84

    JVM(三)–垃圾收集算法

    这篇博客的内容包括:

    一、垃圾收集算法: 1,标记——清除算法:2,复制算法:3,标记——整理算法:4,分代收集算法: 二、涉及到的问题: 1,标记清除,标记整理,复制算法分别是什么,各有什么缺点2,新生代和老年代各用什么算法?3,为什么要划分新生代和老年代?4,新生代分为什么?年轻代为什么分为Eden和Survior区?5,新生代和老年代的年龄阈值是多少?

    初识GC

    自动垃圾回收机制,简单来说就是寻找 Java堆中的无用对象。打个比方:你的房间是JVM的内存,你在房间里生活会制造垃圾和脏乱,而你妈妈就是 GC。你妈妈每时每刻都觉得你房间很脏乱,不时要把你赶出门打扫房间,如果你妈一直在房间打扫,那么这个过程你无法继续在房间打游戏吃泡面。但如果你一直在房间,你的房间早晚要变成一个无法居住的猪窝。

    那么,怎么样回收垃圾比较好呢?有哪些垃圾收集器呢?

    先来看一看有哪些垃圾收集算法:

    一、垃圾收集算法:

    1,标记——清除算法:

    最基础的收集算法是“标记——清除”算法,算法分为“标记”和“清除”两个阶段:

    首先标记出所有需要回收的对象在标记完成后统一回收所有被标记的对象

    该算法的缺点:

    效率问题:标记和清除两个过程的效率都不高空间问题:标记清楚后产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的内存而不得不提前触发另一次垃圾收集动作。

    总结:

    就是当程序运行期间,若使用的内存被耗尽的时候,GC线程就会被触发并将线程暂停,随后,将依旧存活的对象标记一遍,最终再将堆中所有没有被标记的对象全部清除,接下来便让程序恢复运行

    2,复制算法:

    为了解决效率问题,一种称为“复制”的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。

    该算法的缺点:将内存缩小为原来的一半。

    现在的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1。

    因为复制的时候需要浪费很多的时间,因此要用复制算法需要对象的存活率较低。

    小总结:

    ①复制算法把内存空间划分成2个区间,在任意的时间点,所有动态分配的对象都只能分配其中一个区间,另外一个区间是空闲的。②当有效空间耗尽时,jvm将暂停线程,开启复制。GC会把活动区的存活对象全部复制到空闲区,并且严格按照内存地址排列,现在有效区里面存放的都是垃圾对象了,则全部一次性的销毁。

    3,标记——整理算法:

    复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在**老年代一般不能直接选用这种算法(一般不选用复制算法)**。

    “标记——整理”算法,标记过程仍然与“标记——清除”算法一样,但后续步骤不是直接对可回收对象进行整理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

    小总结:

    ①标记:它的第一阶段与标记/清除算法是一样的,均是遍历GC Roots,然后将存活的对象标记

    ②整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

    ③优点:1)弥补标记/清除算法中的内存碎片问题 2)弥补复制算法中内存减半的缺点

    ④缺点:效率问题,不仅要标记存活对象,还要整理存活对象的引用地址

    4,分代收集算法:

    根据对象存活周期的不同将内存划分为几块。一般把java堆分为新生代和老生代,这样就可以根据各个年代的特点采用最合适的收集算法。

    新生代中:每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用**复制算法**,只需要付出少量存货对象的复制成本就可以完成收集。

    老年代中:因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记——清理”或者“标记——整理”算法来进行回收

    常见的垃圾回收算法的总结:

    这是比较的:标记/清除+复制算法+标记/整理: ①三个算法都属于根搜索法来标记存活对象 ②gc开始时,他们都需要stop-the-word ③效率:复制算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此) ④内存整齐度:复制算法>标记/整理算法>标记/清除算法 ⑤内存利用率:标记/整理算法>标记/清除算法>复制算法

    二、涉及到的问题:

    看完以上的笔记,那么设涉及到的这个问题:

    问题1: 标记清除,标记整理,复制算法分别是什么,各有什么缺点?就迎刃而解了。

    问题2: 新生代和老年代各用什么算法? 在笔记上,这个题也迎刃而解了。

    问题3: 为什么要划分新生代和老年代?

    这样划分的目的:为了使jvm能够更好的管理堆内存中的对象,包括内存的分配和回收。

    问题4: 新生代分为什么?年轻代为什么分为Eden和Survior区?

    这样划分的目的是:

    ①为了使jvm能够更好的管理堆内存中的对象,包括内存的分配和回收②优化GC性能

    关于上图的一些说明:

    ①:jvm每次只会使用Eden和其中一块儿Survior区域来为对象服务。所以,无论什么时候,总有一块儿Survior区域是空闲的。

    ②:当对象在Eden(包括一个Survior区域,这里为from区域)出生后,在经过一次Minor GC(发生在新生代中的垃圾收集动作)后,若对象还存活,并且能够被另一块儿Survior区域(to区域)所容纳,则使用复制算法将这些仍然还存活的对象复制到另一个=块儿Survior(to区域),然后清理所使用过的Eden以及from区域,并且将这些对象的年龄设为1,以后对象在Survior区每熬过一次Minor GC,就将对象的年龄+1,当对象的年龄达到某个值时(默认是15岁,可通过-XX:MaxTenuringThreshold来设定),这些对象就会成为老年代。 (经过一次GC后,Eden区域和from区域已经被清空,这时候from和to会交换角色。因为不管怎样都会保证to的Survior区域是空的)。Minor GC会一直重复这样的过程,直到to区域被填满,to区域满之后会将所有对象移动到老年代中。 ③:以上②中的也不一定,对于一些较大的对象(即需要分配一块儿较大的连续内存空间),则直接进入老年代。 ④:老年代:里面的对象几乎个个都是在Survior区域中熬过来的。他们是不会那么容易就“死掉”的,所以Full GC(发生在老年代的垃圾收集动作)发生的次数不会有Minor GC那么频繁,并且做一次Full GC要比进行一次Minor时间要长。 ⑤:另外,标记-清除算法的时候会产生许多的内存碎片,此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前出发一次GC的收集动作。

    问题⑤: 新生代和老年代的年龄阈值是多少?

    ==>默认是15岁,可通过-XX:MaxTenuringThreshold来设定

    感谢并参考:

    https://blog.csdn.net/xiaojie_570/article/details/80199053

    Processed: 0.013, SQL: 9