JVM垃圾回收机制

jvm中需要了解垃圾回收,首先地了解下面过程

内存是如何分配和回收的 ——> 哪些垃圾需要被回收 ——> 什么时候回收 ——> 如何回收

一、JVM内存分配与回收

Java中的自动内存管理主要是针对对象内存的回收与对象内存的分配。同时,Java中自动内存管理最核心的功能是对 中对象的分配与回收

1.1 堆内存分配

  • 新生代

    • Eden区
    • Survivor From 区
    • Survivor To 区
  • 老年代
  • 元空间(jdk1.8及以后,1.8以前是永久代)

1.2 堆内存常见分配策略

  • 对象优先在Eden区分配
  • 大对象直接在老年代分配
  • 长期存活的对象将会移入老年代

1.2.1 对象优先在Eden区分配

目前主流的垃圾回收算法都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾回收器。

在大多数情况下,对象在新生代中的Eden区分配。当Eden区没有足够的内存来分配时,将会触发一次Minor GC

1.2.2 大对象在老年代分配

大对象就是需要大量连续的内存空间(比如:数组)

1.2.3 长期存活的对象将进入老年代

如果对象在Eden区出生并经历过第一次Minor GC后仍然能够存活,并且能够放入Survivor区,将被移入到Survivor区中,并将对象的年龄设为1,对象在Survivor中每熬过一次Minor GC,年龄就增加1岁,当增加到15岁时,将会晋升到老年区, 对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

1.3 哪些对象需要被回收

堆中几乎存放着所有的对象实例,对堆内存中垃圾回收第一步需要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

判断一个对象是否存活,常用的方法有

  • 引用计数法
  • 可达性分析法

1.3.1 引用计数法

引用计数法就是给对象中添加一个计数器,每当有一个地方引用它,计数器加1,当引用失效,计数器减1;任何时候计数器为0的对象就不能再被使用,即该对象可以被回收。

引用计数法实现简单,效率高,但是它存在无法解决对象之间的循环引用问题。

1.3.2 可达性分析法

可达性分析法就是从一个“GC ROOT”节点开始往下寻找对应的引用节点,找到引用节点后继续往下寻找下面的引用节点,当所有的引用节点寻找完毕后,剩余的节点则被认为是没有引用的节点,即可以被垃圾回收器回收的对象。

在Java中,可以作为”GC ROOT”节点的对象可以包括:

  • 虚拟机栈中引用的对象(栈帧中的局部变量表)
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

二、垃圾回收算法

常见的垃圾回收算法有

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法
  • 分代收集算法

1. 标记-清除算法

标记-清除算法分为“标记”和“清除”两个阶段,首先标记出需要回收的对象,在标记完成后统一回收被标记的对象。它是最基础的收集算法,效率很高,但是缺点是会产生大量的内存碎片,碎片太多的话,可能会导致后续过程中需要为大对象分配内存空间时无法找到连续的内存空间而触发新的一次垃圾回收动作

2. 复制算法

复制算法就内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块中的内存用完后,将还存活的对象复制到另一块中去,然后再把使用的那块一次性清理掉,这样每次内存回收就只对内存空间的一半进行回收。

缺点是需要2倍的内存空间。

3. 标记-整理算法

标记-整理算法是根据老年代特出的一种标记算法,标记过程仍然和“标记-清除”算法一样,标记需要回收的对象,然后让将所有存活的对象向一端移动,然后清理端边界以外的对象。标记-整理算法解决了内存碎片问题,但是成本较高。

4. 分代收集算法

分代收集算法是当前大部分JVM垃圾收集器采用的算法,它的核心思想是根据对象存活的生命周期划分为若干个不同的区域。一般情况下将堆分为新生代、老年代和元空间。

新生代的特点是每次垃圾回收时都有大量的对象需要被回收,老年代的特点是每次垃圾回收只有少量的对象需要被回收,所以可以根据不同代的特点选择最合适的收集算法。

目前大部分收集器对于新生代都采用复制算法,因为新生代中每次垃圾回收都要回收大部分对象,只有很少对象存活,所以需要复制的操作较少。

但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(一般为8:1:1),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

而由于老年代的特点是每次回收都只回收少量对象,一般使用的是标记-整理算法。