• 欢迎光临flyzy小站!分享一些学习路上遇到的坑坑洼洼~

adad

Java垃圾收集器与内存分配策略(GC机制)

本文介绍Java对象是否死亡判定方法(引用计数算法,可达性分析算法),垃圾收集算法,HotSpot虚拟机的垃圾收集器,JVM内存分配与回收策略以及新生代GC(Minor GC)与老年代GC(Major GC/Full GC)的区别。

 

Java对象是否死亡判定方法

在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死亡”(即不可能被任何途径使用的对象)。

引用计数算法

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

但是,主流的Java虚拟机里面没有选用引用计数器算法来管理内存,其中最主要的原因是它很难解决对象间循环引用的问题:

class A{
  public B b;
   
}
class B{
  public A a;
}
public class Main{
    public static void main(String[] args){
    A a = new A();
    B b = new B();
    a.b=b;
    b.a=a;
    }
}

在函数的结尾,a和b的计数均为2。

先撤销a,然后a的计数为1,在等待b.a对a的引用的撤销,也就是在等待b的撤销。

对于b来讲,也是同理。

两个对象都在等待对方撤销,所有这两个资源均不能释放。

可达性分析算法

在主流的商用程序语言(Java、C#、Lisp)的主流实现中都是通过可达性分析(Reachability Analysis)来判定对象是否存活的。

通过一些列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

java-gc1

在Java语言中,可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

 

垃圾收集算法

标记-清除算法

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

主要不足有两个:

  1. 效率问题,标记和清除两个过程的效率都不高
  2. 空间问题,标记清除后会产生大量不连续的内存碎片

复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存一次清理掉。

实现简单,运行高效,但是代价是将内存缩小为原来的一半。在对象存活率比较高时就要进行较多的复制操作,效率将会降低。

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

空间分配担保:如果出现大量对象在Minor GC后仍然存活的情况,就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。

标记-整理算法

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

这种算法适合老年代这种对象基本不会回收的情况。

分代收集算法

根据对象存活周期的不同将内存划分为几块。对于新生代,每次垃圾收集时都有大批对象死去,就用复制算法;而对于老年代,对象存活率比较高,则使用标记-清理或者标记-整理进行回收。

 

垃圾收集器

HotSpot虚拟机的垃圾收集器如下图所示:

java-gc2

 

内存分配与回收策略

java-gc3

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。年轻代分为3个部分:Enden区和Survivor From和Survivor To区。

长期存活的对象将进入老年代(熬过一次Minor GC,年龄就增加1岁,当年龄增加到一定程度(默认15岁),就会进入老年代)

大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。

 

Minor GC与Major GC的区别

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

老年代GC(Major GC/Full GC):指发生在老年代的垃圾收集工作,出现了Major GC,经常会伴随至少一次的Minor GC(但并非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程),Major GC的速度一般会比Minor GC慢10倍以上。

点赞