【JVM】GC垃圾回收

661 阅读4分钟

前言

在Java程序中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自动执行。在Jvm中一个垃圾回收线程,它是低优先级的,在正常的情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足的时候才会触发执行,扫描哪些没有被引用的对象,并将它们添加到要回收的集合中。

GC垃圾回收主要从三个方面去分析

  1. 垃圾回收判断
  2. 垃圾回收算法
  3. GC垃圾收集器

1. 垃圾回收判断

GC是有JVM自动完成的,根据JVM系统环境决定,回收的时机是不确定的。当然我们也可以手动进行垃圾回收system.gc()方法通知JVM进行垃圾回收,但是具体什么时间回收也是无法确定的。

一般的发生垃圾回收的场景:

(1)当Eden区或者S区空间不足的时候 (2)老年代空间不足了 (3)方法区的空间不足 (4)system.gc()方法

1.1 对象的回收

堆中存放几乎所有的对象的实例,那么如何判断对象可以回收?

一般由两种方法可以判断

    1. 引用计数法:为每一个对象创建一个引用计数器,有对象引用的时候计数器+1,引用被释放的时候-1,当计数器为0的时候就可以被回收。缺点不能解决循环引用的问题。
    1. 可达性分析算法:从GC ROOT根开始向下搜索,搜索所走过的路径。让GC ROOTS根没有任务引用链的相连时,则证明此对象可以被回收。

那么什么时GC ROOTS根呢?

  • 虚拟机栈中引用的对象
  • 本地方法栈JNI 中引用的对象
  • 方法区中静态引用的对象
  • 方法区中常量引用的对象

1.2. 类信息回收

需要满足下面三个条件

  1. 该类所有的实例都已经被回收
  2. 加载该类的Class Loader已被回收
  3. 该类的对应的java.lang.class对象没有在任何地方引用

1.3. 常量回收

常量池回收主要回收废弃的常量。那么怎么判断常量是不是已经废弃? 比如常量池中存在字符串"abc",如何没有任何String对象引用该字符串常量的话,也就说明常量“abc”时废弃常量。

2. 垃圾回收算法

image.png

2.1 复制算法

将可用容量大小划分为相等大小的两块,每次只使用其中一块,当这一块内存用完了,触发垃圾回收将还存活的对象复制到另外一块上去,然后在把已经使用过的内存空间清理掉,典型的"空间换时间的思想"。比如我们年轻代的划分Eden,s1和s2区,就是采用的复制算法。

2.2 标记清除算法

算法分为"标记"和"清除"两个阶段,首先标记出所有需要回收的对象,在标记完成之后同一回收被标记的对象。

缺点: 效率问题,标记和清除的效率都不高 空间问题,产生内存碎片

2.3 标记整理算法

标记整理,标记之后将所有的对象都移向一端,然后清理掉边界之外的内存。 优点:连续内存 缺点:移动耗时间

2.4 分代收集算法

分代收集算法目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的区域。 分代收集是一种分治的思想。一般会将Java堆分成新生代和老年代。根据每个年代不同的特点选择合适的垃圾回收算法。

3. 垃圾收集器

image.png

3.1 串行垃圾收集器

只有一个垃圾回收线程执行,用户线程暂停。适用于内存比较小的嵌入式设备。

3.1.1 Serial(young)(标记清理算法)

新生代单线程收集器,标记和清理都是单线程,优点是简单高效

3.1.2 Serial Old(标记整理算法)

老年代单线程收集器,Serial收集器的老年代版本;

3.2 并行垃圾收集器

多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态,适用于科学计算,后台处理等场景。

3.2.1 ParNew(young)

Serial收集器的多线程版本,复制算法,需要stop the word

3.2.2 Paraller Scavenge(young)

新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。

3.2.3 Paraller Old(old)

是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。

3.3 并发垃圾收集器

用户线程和垃圾收集线程同时执行。垃圾收集线程在执行的时候不会停顿用户线程的运行

3.3.1 CMS(old)

image.png

3.3.2 G1收集器