JVM(Java Virtual Machine)垃圾回收算法

327 阅读5分钟

当下动态分配,内存回收技术已经相当成熟,为什么还要了解垃圾回收机制?

当排查内存溢出,内存泄漏,当系统达到更高的并发瓶颈时候,垃圾回收机制,就需要开发人员实施必要监控和调节。

程序计数器,本地方法栈,虚拟机栈3个区域是线程私有的,随着我们线程的消亡而消亡,所以这些区域就不需要考虑过多的垃圾回收问题。

堆和方法区有着不同的区别:一个接口有多个实现类,所需需要的内存不一定,一个方法所执行不同条件分支所需要的内存也不一样,只有在运行期才能知道会创建多少个对象,创建那些对象,这部分的内存分配和回收是动态的。

如何判断对象已死

  1. 引用计数法

    在对象中添加一个计数器,每当有一个地方引用它的时候,计数器就进行加一,当引用失效就进行减一,计数器为0时对象就可在被使用。

    缺点:当对象之间存在相互引用的情况下,无法判定是垃圾。无法回收

  2. 可达性分析法

    通过一系列GCROOT的根对象作为起始节点,从根节点开始,向下搜索,搜索走过的链路称为“引用链”,如果某个对象到GCROOT间没有任何引用链相连或者就是这个对象不可达时,证明该对象不可用。

垃圾回收算法

分类:直接垃圾回收和间接垃圾回收

分代垃圾回收

绝大多数的对象都是朝生夕灭,但熬过多次垃圾回收的对象就越难以消除。基于如上思想也就提出了分代垃圾回收想法。

如果一个区域绝大多数的对象都是朝生夕灭,难以熬过垃圾回收,那么把他们放在一个区域,每次垃圾回收只关注那些存活时间长的对象,如果进行垃圾回收就能以低价回收到大量的空间。

jvm设计者把堆划分为新生代和老年代俩个区域,在新生代中每次进行垃圾回收都有大批对象死去,将存活下来的对象逐步提升到老年代。

标记-清除
如同名字一样“标记”-“清除”,首先对回收的对象进行标记,在标记完成后统一进行垃圾清除。也可反过来对存活对象进行标记,然后统一回收未被标记的对象。他是最基础的收集算法,后续收集算法大多都已标记-清除为基础。

image.png 缺点:

1.执行效率不稳定(如果有大量对象进行垃圾回收,需要标记大量的标记和清除对象到这标记和清除这俩个过程执行效率都随着对象数量的增加而降低)

2.内存空间碎片化(通过大量标记清除会导致内存空间产生大量不连续的内存碎片,影响以后在存储大对象时候无法找到足够的连续内存,不得不在触发一次垃圾回收)

标记-复制

为解决大量垃圾回收对象时执行效率低的问题。 他将内存按容量大小划分为大小相等的俩块,每次在使用时就使用其中的一块,当一块使用完毕后将还存活的对象呢复制到另一块,然后在把使用过的内存空间清理。分配时候也不用担心内存碎片化问题,只需要移动堆订指针,按顺序分配就行。

缺点:

1.如果大量对象都是存活的这种会产生大量的内存复制开销。

2.标记-复制这种算法代价是将原有内存空间缩小到原来的一半,大量空间浪费

现在呢大多数商用虚拟机都会使用这种垃圾回收来回收新生代

更优的半区复制策略

把新生代划分为一块较大的Eden区,和俩块比较小的Survivor(幸存区)空间,每次分配空间只使用其中一块幸存区,当发生垃圾回收时将幸存区内的存活对象复制到另一块幸存区上,然后清理已经用过的那一块。他们之间的比例是8:1:1

内存担保问题: 如果一块幸存区没有足够空间存放上一次新生代所存活下来的对象,这些对象通过分配担保机制直接放入老年代。

标记-整理

标记清除算法存活较多就回进行大量的复制操作,效率会降低,如果不想浪费50%的空间,就需要有额外的空间担保。所以在100%存活对象下,老年代不使用这种算法。

标记整理算法和标记清除算法一样,但后续步骤不是清除,而是将存活对象都向内存的一端进行移动,直接清理边界以外的内存。俩者的区别是一种是非移动式回收算法,另一种是移动式回收算法。

缺点:向老年代都是每次回收都有大量存活对象,移动并更新所有引用这些对象的地方 将会是一种极为负重的操作。,是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会更复杂。

解决方式:让虚拟机在平常都是用,标记清除当产生大量的碎片内寸,知道影响到大的对象存储分配时候,在采用标记整理算法来一次,以获取规整的内存空间。CMS垃圾回收器就是采用这种算法。