java 垃圾回收机制

垃圾收集GC(Garbage Collection)是Java语言的核心技术之一, 在Java中,程序员不须要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。java

一. jvm的内存结构程序员

垃圾回收都是基于内存去回收的,所以,先要对内存结构有一个大概的了解算法

 

 

 Java内存运行时区域大概分了三部分多线程

  • 其中PC寄存器、java虚拟机栈、本地方法栈3个区域是全部线程独有的一块区域,随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着入栈和出栈操做。每个栈帧中分配多少内存基本上是在类结构肯定下来时就已知的,所以这几个区域的内存分配和回收都具有肯定性,在这几个区域内就不须要过多考虑回收的问题,由于方法结束或者线程结束,内存天然就跟随着回收了。
  • 而Java堆和方法区则不同,一个接口中的多个实现类须要的内存可能不同,一个方法中的多个实现类须要的内存可能不同,一个方法中的多个分支须要的内存也可能不同,只有在程序处于运行期间时才能知道会建立哪些对象,这部份内存的分配和回收是动态的,垃圾收集关注的是这部分的内存。

. 内存分配

在解垃圾回收以前,得先了解JVM是怎么分配内存的,而后识别哪些内存是垃圾须要回收,最后才是用什么方式回收。并发

Java的内存分配原理与C/C++不一样,C/C++每次申请内存时都要malloc进行系统调用,而系统调用发生在内核空间,每次都要中断进行切换,这须要必定的开销,而Java虚拟机是先一次性分配一块较大的空间,而后每次new时都在该空间上进行分配和释放,减小了系统调用的次数,节省了必定的开销,这有点相似于内存池的概念;二是有了这块空间事后,如何进行分配和回收就跟GC机制有关了。jvm

三. 垃圾检测(标记)算法

什么样的对象才是垃圾?

若是一个对象没有被其余对象所引用该对象就是无用的,此对象就被称为垃圾,其占用的内存也就要被销毁。那么天然而然的就引出了咱们的第二个问题,判断对象为垃圾的算法都有哪些?ide

标记垃圾的算法

Java中标记垃圾的算法主要有两种, 引用计数法可达性分析算法。咱们首先来介绍引用计数法。spa

  • 引用计数法

  引用计数法就是给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任什么时候候计数器为 0 的对象就是不可能再被使用的,能够当作垃圾收集。这种方法实现起来很简单并且优缺点都很明显。线程

  

    •   优势 执行效率高,程序执行受影响较小3d

    •   缺点 没法检测出循环引用的状况,致使内存泄露

  什么是循环引用呢?

  假设有A和B两个对象之间互相引用,也就是说A对象中的一个属性是B,B中的一个属性时A,这种状况下因为他们的相互引用

  咱们举一个简单的例子。

public class MyObject {
    public MyObject childNode;
}

public class ReferenceCounterProblem {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
        object1.childNode = object2;
        object2.childNode = object1;
    }
}
  • 可达性分析

可达性分析基本思路是把全部引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出全部链接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。不能到达的则被可回收对象。

下面这张图就是可达性分析的描述:

 

 

 咱们发现,GC Roots 自己是一个出发点,也就是说咱们每次进行可达性分析的时候都要从这个初始点出发。换句话说,初始点咱们必定是可达的。那么,Java 里有哪些对象能够做为GC Roots呢?主要有如下四种:

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

总之,JVM在作垃圾回收的时候,会检查堆中的全部对象是否会被这些根集对象引用,不可以被引用的对象就会被垃圾收集器回收。

四. 垃圾收集(回收)算法

已经可以肯定那些对象能够被视为垃圾了。下面咱们能够分析一下,如何去回收这些垃圾,一样的,有一系列算法。首先咱们定义一个规则肯定那些是垃圾、存活对象、空白空间

 

 

 

垃圾收集算法有四种:

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

一、标记-清理

第一步(标记),利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。第二步(清理),咱们再遍历一遍,把全部“垃圾”对象所占的空间直接 清空 便可。

结果以下:

 

 

 

特色:

  • 简单方便
  • 容易产生内存碎片

二、标记-整理

上面的方法咱们发现会产生内存碎片,所以在这个方法中一样为两步:

第一步(标记):利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。

第二步(整理):把全部存活对象堆到同一个地方,这样就没有内存碎片了。

结果以下:

 

 

 

特色:

  • 适合存活对象多,垃圾少的状况
  • 须要整理的过程

三、复制

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

过程以下:

 

 

 

特色:

  • 简单
  • 不会产生碎片
  • 内存利用率过低,只用了一半

四、分代收集算法

java中的垃圾回收大体在两部分,第一个就是堆、第二个就是方法区。为此先看方法区是如何进行垃圾回收的。

一、方法区的垃圾回收

方法区又叫作永久代。永久代的垃圾回收主要有两部分:废弃常量和无用的类。

  • 废弃常量

    第一步:断定一个常量是不是废弃常量:没有任何一个地方对这个常量进行引用就表示是废弃常量。

    第二步:垃圾回收

  • 无用的类

    第一步:断定一个类是不是“无用的类”:须要知足下面三个条件:

      • Java堆中不存在该类的任何实例,也就是该类的全部实例都被回收
      • 加载该类的ClassLoader已经被回收
      • 该类对应的Class对象在任何地方没有引用了,也不能经过反射访问该类的方法。

    第二步:知足上面三个条件就能够回收了,但不是强制的。

注意:《java虚拟机规范》里面曾经说到过,不要求虚拟机对方法区进行垃圾回收。并且方法区进行垃圾回收性价比比较低

二、Java 堆的垃圾回收:

先来看一下 Java 堆的结构。

 

 

 Java 堆空间分红了三部分,这三部分用来存储三类数据:

  • 刚刚建立的对象。
  • 存活了一段时间的对象。
  • 永久存在的对象。

也就是说,常规的 Java 堆至少包括了 新生代 和 老年代 两块内存区域,并且这两块区域有很明显的特征:

  • 新生代:存活对象少、垃圾多
  • 老年代:存活对象多、垃圾少

针对这种特色,咱们有如下两种方案;

(1)新生代-复制 回收机制

对于新生代区域,因为每次 GC 都会有大量新对象死去,只有少许存活。所以采用 复制 回收算法,GC 时把少许的存活对象复制过去便可。可是从上面咱们能够看到,新生代也划分了三个部分比例:Eden:S1:S2=8:1:1。

其中 Eden 意为伊甸园,形容有不少新生对象在里面建立;S1和S2中的S表示Survivor,为幸存者,即经历 GC 后仍然存活下来的对象。

工做原理以下:

  1. 首先,Eden对外提供堆内存。当 Eden区快要满了,触发垃圾回收机制,把存活对象放入 Survivor A 区,清空 Eden 区;

  2. Eden区被清空后,继续对外提供堆内存;

  3. 当 Eden 区再次被填满,对 Eden区和 Survivor A 区同时进行垃圾回收,把存活对象放入 Survivor B区,同时清空 Eden 区和Survivor A 区;

  4. 当某个 Survivor区被填满,把多余对象放到Old 区;

  5. 当 Old 区也被填满时,进行 下一阶段的垃圾回收。

(2)老年代-标记整理 回收机制

老年代的特色是:存活对象多、垃圾少。所以,根据老年代的特色,这里仅仅经过少许地移动对象就能清理垃圾,并且不存在内存碎片化。也就是标记整理的回收机制。既然是标记整理算法,并且老年代内部也不存在着内存划分,因此只须要根据标记整理的具体步骤进行垃圾回收就行了。

五. 垃圾回收器

若是说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

在了解 垃圾回收器以前,首先得了解一下垃圾回收器的几个名词。

1. 吞吐量

CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值。好比说虚拟机总运行了 100 分钟,用户代码时间 99 分钟,垃圾回收 时间 1 分钟,那么吞吐量就是 99%。

2. 停顿时间

停顿时间 指垃圾回收器正在运行时,应用程序 的 暂停时间。

3. GC的名词

新生代GC:Minor GC

老年代GC:Major GC

4. 并发与并行

(1)串行(Parallel)

垃圾回收线程 进行垃圾回收工做,但此时 用户线程 仍然处于 等待状态。

(2)并发(Concurrent)

这里的并发指 用户线程 与 垃圾回收线程 交替执行。

(3)并行(Parallel)

这里的并行指 用户线程 和多条 垃圾回收线程 分别在不一样 CPU 上同时工做。

下面其中垃圾回收器是基于HotSpot虚拟机。先给一张图看一下

 

 在 JVM 中,具体实现有 Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old、G1 等。在上图中,你能够看到 不一样垃圾回收器 适合于 不一样的内存区域,若是两个垃圾回收器之间 存在连线,那么表示二者能够 配合使用。

下面对这其中垃圾回收器有一个了解。

第一种:Serial(单线程)

Serial 回收器是最基本的 新生代垃圾回收器,是单线程的垃圾回收器。采用的是 复制算法。垃圾清理时,Serial回收器不存在线程间的切换,所以,在单 CPU` 的环境下,垃圾清除效率比较高。

第二种:Serial Old(单线程)

Serial Old回收器是 Serial回收器的老生代版本,单线程回收器,使用 标记-整理算法。在 JDK1.5 及其之前,它常与Parallel Scavenge回收器配合使用,达到较好的吞吐量,另外它也是 CMS 回收器在Concurrent Mode Failure时的后备方案。

第三种:ParNew(多线程)

ParNew回收器是在Serial回收器的基础上演化而来的,属于Serial回收器的多线程版本,采用复制算法。运行在新生代区域。在实现上,二者共用不少代码。在不一样运行环境下,根据CPU核数,开启不一样的线程数,从而达到最优的垃圾回收效果。

 

 

第四种:Parallel Scavenge(多线程)

Parallel Scavenge回收器也是运行在新生代区域,属于多线程的回收器,采用复制算法。与ParNew不一样的是,ParNew回收器是经过控制垃圾回收的线程数来进行参数调整,而Parallel Scavenge回收器更关心的是程序运行的吞吐量。即一段时间内用户代码运行时间占总运行时间的百分比。

第五种:Parallel Old(多线程)

Parallel Old回收器是Parallel Scavenge回收器的老生代版本,属于多线程回收器,采用标记-整理算法。Parallel Old回收器和Parallel Scavenge回收器一样考虑了吞吐量优先这一指标,很是适合那些注重吞吐量和CPU资源敏感的场合。

 

 

第六种:CMS(多线程回收)

CMS回收器是在最短回收停顿时间为前提的回收器,属于多线程回收器,采用标记-清除算法。

 

 

第七种:G1回收器

G1是 JDK 1.7中正式投入使用的用于取代CMS的压缩回收器。它虽然没有在物理上隔断新生代与老生代,可是仍然属于分代垃圾回收器。G1仍然会区分年轻代与老年代,年轻代依然分有Eden区与Survivor区。

G1首先将堆分为大小相等的 Region,避免全区域的垃圾回收。G1的分区示例以下图所示:

 

 

这种使用区域划份内存空间以及有优先级的区域回收方式,保证G1回收器在有限的时间内能够得到尽量高的回收效率。

下面对这几种垃圾回收机制进行一个总结:

 

 

参考:https://baijiahao.baidu.com/s?id=1636852721632353675&wfr=spider&for=pc

相关文章
相关标签/搜索