JVM(4)垃圾回收

    • 判断对象存活算法
      • 引用计数算法
      • 根搜索算法
    • 垃圾回收算法
      • 标记-清除算法
      • 复制算法
      • 标记整理压缩算法
      • 分代回收算法

Java堆中存放着大量的Java对象实例,在垃圾收集器回收内存前,第一件事情就是确定哪些对象是“活着的”,哪些是可以回收的。

1. 判断对象存活算法

引用计数算法

引用计数算法是判断对象是否存活的基本算法:给每个对象添加一个引用计数器,没当一个地方引用它的时候,计数器值加1;当引用失效后,计数器值减1。但是这种方法有一个致命的缺陷,当两个对象相互引用时会导致这两个都无法被回收。

根搜索算法

在主流的商用语言中(Java、C#…)都是使用根搜索算法来判断对象是否存活。对于程序来说,根对象总是可以访问的。从这些根对象开始,任何可以被触及的对象都被认为是”活着的”的对象。无法触及的对象被认为是垃圾,需要被回收。

Java虚拟机的根对象集合根据实现不同而不同,但是总会包含以下几个方面:

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

区分活动对象和垃圾的两个基本方法是引用计数和根搜索。 引用计数是通过为堆中每个对象保存一个计数来区分活动对象和垃圾。根搜索算法实际上是追踪从根结点开始的引用图。

2. 垃圾回收算法

标记-清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记的方法使用根搜索算法。标记所有从根结点开始的可达对象。
缺点是会造成内存空间不连续,不连续的内存空间的工作效率低于连续的内存空间,不容易分配内存
(效率问题,标记和清除的效率都不高。
空间问题,标记清除后会产生大量不连续的内存碎片。)

复制算法

将内存空间分成两块,每次将正在使用的内存中存活对象复制到未使用的内存块中,之后清除正在使用的内存块。算法效率高,但是代价是系统内存折半。适用于新生代(存活对象少,垃圾对象多)

标记–整理(压缩)算法

标记-清除的改进,清除未标记的对象时还将所有的存活对象压缩到内存的一端,之后,清理边界所有空间既避免碎片产生,又不需要两块同样大小的内存快,性价比高。适用于老年代。

分代回收算法

在JVM中不同的对象拥有不同的生命周期,因此对于不同生命周期的对象也可以采用不同的垃圾回收方法,以提高效率,这就是分代回收算法的核心思想。

在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费的时间相对会长。同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

JVM中的共划分为三个代:新生代(Young Generation)、老年代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。

  • 新生代:所有新生成的对象首先都是放在新生代的,新生代采用复制回收算法。新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

  • 老年代:在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中,老年代采用标记整理回收算法。因此,可以认为老年代中存放的都是一些生命周期较长的对象。

  • 持久代:用于存放静态文件,如final常量,static常量,常量池等。持久代对垃圾回收没有显著影响。

参考链接:https://github.com/hadyang/interview/blob/master/java/jvm-gc.md

你可能感兴趣的:(#,jvm)