一、GC 概述

1、GC 主要发生在JVM体系结构图中的亮色部分(Method area、heap)
2、GC 是指分代收集算法

  • 在次数上频繁收集 Young 区的是 Minor GC
  • 在次数上较少收集 Old 区 Full GC
  • 基本不动 Perm 区

3、GC 的回收流程,可以参考上面堆的内容。
1-JVM体系结构概述

  • Minor GC 和 Full GC 的区别
    • 普通GC(Minor GC):只针对新生代区域的GC,指放生在新生代的垃圾收集动作,因为大多数java对象的存活率都不高,所以Minor GC 非常频繁,一般回收速度也比较快。
  • 全局GC(Major GC / Full GC)
    • 只发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比minor GC慢10倍以上。(慢的原因:老年代的空间大)。

4、什么是垃圾
内存中已经不再被使用到的空间就是垃圾。

二、如何判断一个对象是否被回收

两种方法,如下(关注 GC Root)

1、引用计数法

参考后面介绍的4大算法

2、可达性分析

如果我们将一些GC Roots对象作为起始点,从这些节点向下搜索,搜索到的路径为引用链,如果有一些对象没有任何引用链相连,那么这个对象对于GC Roots是不可达的,即使它们之间可能相互产生关联,所以将其判定为可回收对象。
试想一下,如果有两个对象互相引用,比如objA.instance = objB, objB.instance = objA,这个时候两个对象都不能被访问,但是互相引用导致引用计数不为0,这不就无法判定为死亡了吗?我们如果是GC,能允许这种长生不死的存在吗?肯定不。所以引用计数法并没有被采用在目前的JVM垃圾回收器中。所以jvm使用的是可达性分析法算法。

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

image.png

哪些节点可以作为GC ROOT

1、虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。 2、方法区中的类静态属性引用的对象。(一般指被static修饰的对象,加载类的时候就加载到内存中。) 3、方法区中常量引用的对象。 4、本地方法栈中JNI(Native方法)引用的对象

1、java虚拟机栈中的引用的对象
  • 我们知道,每个方法执行的时候,jvm都会创建一个相应的栈帧
  • 栈帧包括(操作数栈、局部变量表、运行时常量池的引用
  • 栈帧中包含这个方法内部使用的所有对象的引用(这就是虚拟机栈中的引用对象)
  • 一旦该方法执行完后,该栈帧就会从虚拟机栈中弹出,这样一来这些局部(临时)对象的引用也就不存在了,或者说没有任何GCRoot指向这些临时对象,所以这些对象在下一次gc时就会被回收掉。

2、方法区中的类静态属性引用的对象(一般指被static修饰的对象,加载类的时候就加载到内存中。)

第一种写法:方法区中的类静态属性引用的对象
1、因为该类是属于test类的全局属性(类属性),所以会存在于方法区中,每个线程共享这种写法一般会用在单例模式的饿汉模式中
2、所以此类对象可以作为GCRoot对象

  1. private static User user = new User()

3、 注意:不止这一种写法,还可以下面那样写
其他单例模式,参考
单例模式

  1. private static User userl
  2. public static synchronized User getuser(){ if (userl == null){
  3. user1 =new User();
  4. return
  5. userl;
  6. }

3、方法区中的常量引用的对象

1、因为该类是属于test类的全局属性(类属性),所以会存在于方法区中。
2、该常量属性被final修饰,所以第一次赋值后便不会在进行变更。

  1. private final User user = new User()

4、d.本地方法栈中的JNI(native方法)引用的对象

即被 native修饰的对象方法

3、注意

即使可达性算法中不可达的对象,也不是一定要马上被回收,还有可能被抢救一下。(网上例子很多)
要真正宣告对象死亡需经过两个过程。
1.可达性分析后没有发现引用链
2.查看对象是否有 finalize 方法,如果有重写且在方法内完成自救【比如再建立引用】,还是可以抢救一下,
注意这边一个类的 finalize 只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。
【如果类重写finalize且还没调用过,会将这个对象放到一个叫做r-Queue的序列里,这边 finalize 不承诺一定会执行,这么做是因为如果里面死循环的话可能会使r-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。

什么是 finalize 方法,参考

7-finalize

三、GC 四大算法

1、引用计数法

有对象引用就+1、没对象用就-1,即有对象被引用就不回收
缺点:每次对象赋值均要维护计数器,且计数器本身也有一定的消耗,较难处理循环引用。
JVM实现不采用这种方式了

2、复制算法(Copying)

年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Copying)
1-JVM体系结构概述
image.png
image.png

原理

  • 从根集合(GC Root)开始,通过Tracing从From中找到存活的对象,拷贝到To中。
  • From、To交换身份,下次内存分类从To开始

    缺点

    需要需要双倍空间(from、to)

    优点

    没有标记清除过程(用 可达性分析),效率高
    没有内存碎片,可以实现快速内存分配

    3、标记清除(Mark-Sweep)

    一般用在old区,标记清除和标记整理 混合实现。
    该算法分为分为标记和清除两个部分,如下
    image.png
    若在程序运行期间,内存被耗尽,GC线程就会触发并将程序暂停,随后将要回收的对象标记一遍,最终统一回收这些对象,完成标记清理工作,接下来继续让程序运行。

    优点

    不需要额外的空间

    缺点

  • 两次扫描(第一次标记,第二次扫描堆内存,清除标记的),耗时严重

  • 会产生内存碎片

    4、标记压缩(Mark-Compact)

    该算法就是比标记清除算法多了一个整理的步骤

一般用在old区,标记清除和标记整理 混合实现。当进行多次GC后才会Compact。
image.png

还有更牛逼的算法吗

有,在jdk8及其以上有G1回收算法
看后续
3-垃圾收集器