深入理解JVM_4_垃圾收集策略

引:Java与C++之间有一堵内存动态分配与垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。那我们就先来看看JVM到底是怎么进行垃圾收集的?

垃圾回收,回收哪里?

之前介绍过Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具有确定性,方法结束或者线程结束,内存自然就跟着回收了,但是Java堆和方法区(元空间)则不一样,我们只有在运行期间才知道会创建哪些对象,这部分内存的分配和回收都是动态的,所有垃圾回收就是回收这里。

对象已死么?

垃圾收集器在对堆进行回收前,第一件事就是要确定这些对象之中还有那些还“存活”者,哪些已经“死去”(即不可能再被任何途径使用的对象)。

  1. 引用计数算法

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

  2. 可达性分析算法(Java使用)

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

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

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

引用类型

  1. 强引用:类似”Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  2. 软引用:用来描述还有用但非必须的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。它是通过SoftReference类来实现软引用的。
  3. 弱引用:用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,它是通过WeakReference类来实现弱引用的。
  4. 虚引用:只要发生垃圾回收,它就会被收集,它唯一的目的就是能在这个对象被收集器回收时收到一个系统通知,它是用过PhantomReference类来实现虚引用。

方法区(元空间)的回收

类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉。准确的来说,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。在元空间的回收过程中没有重定位和压缩等操作。但是元空间内的元数据会进行扫描来确定Java引用。

垃圾收集算法

标记-清除算法

流程:

  1. 标记出所有需要回收的对象
  2. 在标记完成后统一回收所有被标记的对象

不足:

  1. 效率问题

    标记和清除两个过程效率都不高

  2. 空间问题

    标记清除以后会产生大量不连续的空间碎片,无法存储大对象

复制算法

流程:

  1. 将可用内存按容量分为大小相同的两块
  2. 在第一块分配内存,并标记出所有需要回收的对象
  3. 当第一块内存用完了,将所有活着的对象复制到另外一块上
  4. 将第一块所使用过的内存空间一次性清除

好处:解决了空间碎片问题

不足:降低了空间利用率

现在的商业虚拟机都用这种收集算法来回收新生代,因为新生代中的对象98%是“朝生夕死”的,所以不需要1:1划分空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还活着的对象那个一次性复制到另一块Survivor空间上,最后清理掉前两块空间。HotSpot虚拟机默认Eden和Survivor的大小比例为8:1,所以只有10%的内存会被“浪费”,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。

内存的分配担保是指如果放着存活对象的Survivor空间没有足够空间放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

标记-整理算法

适合老年代

流程:

  1. 标记出所有需要回收的对象
  2. 让所有存活的对象都向一端移动
  3. 直接清理掉端边界以外的内存

分代收集算法

将内存划分为老年代和新生代。老年代中存放寿命较长的对象,新生代中存放“朝生夕死”的对象。然后在不同的区域使用不同的垃圾收集算法。

总结

从上面我们了解了对象存活判定算法和垃圾回收算法,但是不同虚拟机的具体实现还是不同的,而且不同的垃圾收集器的内存回收的具体实现也是不同的,我们要因机而议。

参考

  1. 《深入理解Java虚拟机》
  2. JDK8 从永久代到元空间