【JVM】GC算法与垃圾收集器

时间:2022-09-10 15:14:43

引入

  • java 语言中一个显著的特点就是引入了java回收机制,是c++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员在编写程序的时候不在考虑内存管理。由于有个垃圾回收机制,可以有效的防止内存泄露,有效的使用空闲的内存;

  • 内存泄露:指该内存空间使用完毕后未回收,在不涉及复杂数据结构的一般情况下,java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度

  • Java虚拟机的内存区域中,程序计数器、虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭;栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这三个区域的内存分配和回收都具有确定性。垃圾回收重点关注的是堆和方法区部分的内存。

常用的垃圾回收算法

(1)引用计数算法:

  • 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。

  • 引用计数算法实现简单,效率很高,但是引用计数算法对于对象之间相互循环引用问题难以解决,因此java并没有使用引用计数算法。

【JVM】GC算法与垃圾收集器

(2)可达性分析算法:

  • 通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。

  • 在java语言中,可作为GC Root的对象包括以下几种对象:

    • java虚拟机栈(栈帧中的本地变量表)中的引用的对象。

    • 方法区中的类静态属性引用的对象。

    • 方法区中的常量引用的对象。

    • 本地方法栈中JNI本地方法的引用对象。

【JVM】GC算法与垃圾收集器

(3)回收方法区

  • 方法区在Sun HotSpot虚拟机中被称为永久代,很多人认为该部分的内存是不用回收的,但是方法区中的废弃常量和无用的类还是需要回收以保证永久代不会发生内存溢出。

  • 判断废弃常量的方法:如果常量池中的某个常量没有被任何引用所引用,则该常量是废弃常量。

  • 判断无用的类:

    • 该类的所有实例都已经被回收,即java堆中不存在该类的实例对象。

    • 加载该类的类加载器已经被回收。

    • 该类所对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射机制访问该类的方法。

(4)四种引用

  • 强引用

      使用最普遍的引用。如果一个对象具有强引用,那就 类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

  • 软引用(SoftReference)

      如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

      软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

  • 弱引用(WeakReference)

      如果一个对象只具有弱引用,那就类似于可有可物的生活用品。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

  • 虚引用

      又称为幽灵引用或幻影引用,,虚引用既不会影响对象的生命周期,也无法通过虚引用来获取对象实例,而且它的优先级低,仅用于在发生GC时接收一个系统通知,不知道什么时候调用,因此尽量避免使用finalize方法,可以使用try/catc/finallyh语句块代替。

(5)finalize()方法

因为任何一个对象的finalize方法只会被系统自动调用一次,如果面临下一次回收,它的finalize方法不会被执行,因此尽量避免使用finalize方法。

Java中常用的垃圾收集算法

1.标记-清除算法:

(1)定义:

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。

(2)缺点

  • 效率问题,标记和清除效率都不高。

  • 标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

(3)图解

【JVM】GC算法与垃圾收集器

2.复制算法:

(1)定义:

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

(2)缺点

  • 可使用的内存缩减到原来的一半

  • 当对象存活率高时,就要执行较多的复制操作,效率将会变低

(3)图解

【JVM】GC算法与垃圾收集器

3.标记-整理算法:

(1)定义

改进了标记-清除算法,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

(2)特点

  • 内存被整理以后不会产生大量不连续内存碎片问题。

  • 当对象存活率高时,使用标记-整理算法效率会大大提高。

(3)图解

【JVM】GC算法与垃圾收集器

4.分代收集算法:

(1)定义

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

(2)分类

堆内存被分成新生代和年老代两个部分,整个堆内存使用分代复制垃圾收集算法。

(3)新生代:

  • 新生代使用复制垃圾回收算法,新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行标记-清理回收,两个Survivor区域是轮换的。

  • 对于创建大对象时,如果新生代中无足够的连续内存时,也直接在年老代中分配内存空间。

  • Java虚拟机对新生代的垃圾回收称为Minor GC,次数比较频繁,每次回收时间也比较短。

  • 使用java虚拟机-Xmn参数可以指定新生代内存大小。

(4)老年代:

  • 老年代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在老年代中使用标记-整理垃圾回收算法。

  • Java虚拟机对老年代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

  • 当新生代中无足够空间为对象创建分配内存,年老代中内存回收也无法回收到足够的内存空间,并且新生代和年老代空间无法在扩展时,堆就会产生OutOfMemoryError异常。

  • java虚拟机-Xms参数可以指定最小内存大小,-Xmx参数可以指定最大内存大小,这两个参数分别减去Xmn参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。

(5)永久代

  • 虚拟机内存中的方法区被称为永久代,是线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

  • 永久代垃圾回收比较少,效率也比较低,但是也必须进行垃圾回收,否则会永久代内存不够用时仍然会抛出OutOfMemoryError异常。

  • 永久代也使用标记-整理算法进行垃圾回收,java虚拟机参数-XX:PermSize和-XX:MaxPermSize可以设置永久代的初始大小和最大容量。

(6)图解

【JVM】GC算法与垃圾收集器

5.分区算法

将整个内存分为N个小的独立空间,每个独立空间都可以独立使用,并不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。

几种垃圾收集器的比较

1.垃圾收集器分类图解

【JVM】GC算法与垃圾收集器

2.新生代收集器

(1)Serial收集器

  • Serial是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。采用复制算法,

  • Serial 收集器尽管显得“简陋”并且用户体验不佳,但到1.6为止它仍然是虚拟机运行在Client模式下的默认新生代收集器

  • Serial 收集器实现简单高效,但是会给用户带来停顿。

Serial收集器工作原理图如下

【JVM】GC算法与垃圾收集器

(2)ParNew收集器

  • ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集,其他与serial类似。

  • 只有ParNew收集器和Serial收集器能与CMS收集器配合工作

ParNew收集器工作原理图如下

【JVM】GC算法与垃圾收集器

(3)Parallel Scavenge收集器

  • 并行与并发

    • 并行(Parallel):指多个垃圾收集器线程并行工作,但此时用户线程仍处于等待状态。
    • 并发(Concurrent):指用户线程与回收器线程同时工作
  • 一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是复制算法,

  • 为了达到一个可控的吞吐量

Parallel Scavenge收集器工作原理图如下

【JVM】GC算法与垃圾收集器

3.老年代收集器

(1)Serial Old收集器

  • 一个单线程收集器,采用标记-整理算法

Serial Old收集器工作原理图如下

【JVM】GC算法与垃圾收集器

(2)Parallel Old收集器

  • 一个Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和标记-整理算法。

Parallel Old收集器工作原理图如下

【JVM】GC算法与垃圾收集器

(3)CMS(Current Mark Sweep)收集器

  • 一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是标记-清除算法。

  • 它的运行过程有四个步骤
    ①初始标记(CMS initial mark)
    ②并发标记(CMS concurrenr mark)
    ③重新标记(CMS remark)
    ④并发清除(CMS concurrent sweep)
    其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程(STW)
     

  • 缺点:对CPU资源敏感(影响系统性能)、无法处理浮动垃圾、会产生大量空间碎片

CMS收集器工作原理图如下

【JVM】GC算法与垃圾收集器

4. G1收集器

  • G1(Garbage First)收集器是JDK1.7提供的一个新收集器

  • 并行与并发:充分利用多CPU、多核的硬件优势,缩短STW停顿的时间

  • 空间整合:采用“标记 - 整理”算法,避免产生碎片

  • 可以预测的停顿:通过让使用指定一个参数来控制在一个长度为M的时间片内垃圾回收的时间N

  • 分代收集:之前的收集器是进行收集的范围是整个新生代或 老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域,并且跟踪这些区域的堆积成都,在后台维护一个优先列表,每次根据优 先级从列表中挑选区域进行收集。

  • 它的运行过程有四个步骤
    ①初始标记(CMS initial mark)
    ②并发标记(CMS concurrenr mark)
    ③最终标记(CMS Marking)
    ④筛选回收(CMS Data Counting and Evacuation)

G1收集器工作原理图如下

【JVM】GC算法与垃圾收集器

垃圾收集器参数

【JVM】GC算法与垃圾收集器【JVM】GC算法与垃圾收集器



本人才疏学浅,若有错,请指出,谢谢!
如果你有更好的建议,可以留言我们一起讨论,共同进步!
衷心的感谢您能耐心的读完本篇博文。