来自JVM的灵魂拷问:你是什么垃圾?

807 阅读7分钟

本文已授权公众号 「码个蛋」,后续再完善。 转载请指明出处,谢谢~

0. 写在前面

魔都才开始垃圾分类,有些人还是傻傻分不清垃圾应该怎么分类,Java真是伟大的语言(傲娇脸)早就实现垃圾分类回收了! 还不懂就out了!

什么?你说现实里的垃圾分类有图文说明,看看就明白?Java里的垃圾回收是算法写的,看不懂? 拜托!要是看看就明白,怎么还会流传“猪能吃的是湿垃圾,猪不吃的是有害垃圾……”

但是Java世界的回收划分很简单,只分可回收还是不可回收~

所以计算机的世界其实更简单。

用Java需要了解GC吗?

虽然Java开发同学不用像C和C++的同学,自己申请了内存,要惦记着不用的时候释放掉,申请了就不管了,但是不用关心不代表它不重要,有些看不见的不代表不会影响你。(好绕呀)

JVM负责内存的分配和回收,这是它的优点,但同时也是它的缺点 —— 不够灵活。

那到底什么是GC?

GC是一种自动的存储管理机制。当一些被占用的内存不再需要时,就应该予以释放。 这种存储资源管理,称为垃圾回收。

就和平时你清理桌面,整理东西一样,很多人都见过听过一个理念“断舍离”吧,不会使用到的物品可以捐赠给其他人或者作为可回收物品被回收掉,很多时候我们存着一些物品,但是好几年都没有用到它们,只是想着可能什么时候会用到,这样一直占据着现实的“内存”。

1. 回收哪些对象 —— To be or not to be?

马路上很常见的垃圾桶分为:干垃圾和可回收垃圾,像纸箱、金属、塑料瓶,都是属于可回收的。

在0-1的世界里,所谓“垃圾回收”,就是指收回那些不可能再被任何途径使用的对象所占的内存空间,释放了这些内存可以给需要的对象使用。

那么JVM里是怎么来分的?或者说哪些对象是需要被回收的?

1.1 还“活着”吗?

要回收,先要判断是不是可以回收,就是这个对象还“活着”吗?

主要有两种算法来判断:引用计数法和可达性分析法。

算法 思想 优点 缺点
引用计数法 给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。 判定效率很高 很难解决对象之间相互引用的情况; 开销较大、频繁且大量的引用变化,带来大量的额外运算
可达性分析法 通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,当GC Roots到某个对象不可达时,这个对象就是可回收的。 更加精确和严谨,可以分析出循环数据结构相互引用的情况 实现比较复杂;需要分析大量数据,消耗大量时间

1.1.1 引用计数法

最大的问题是很难解决对象之间互相引用的情况。

  • a. 互相引用

这是两个对象互相引用的情况,除此之外,这两个对象再无引用,但因为它们的引用计数不为0,所以引用计数器无法通知GC收集器回收它们。

  • b. 循环引用

这是循环引用的情况,没有外部引用指向它们,但它们的引用计数不为0,就无法进行回收了。

这个要怎么理解好呢?

比如一把椅子的靠背坏了,椅子的轮子还是好的,但这轮子不能拆下来挪威他用,所以椅子的靠背和它的轮子是互相引用的,但是除了它们自己,没有外界引用它们了,这样的情况用“引用计数法”来判断的话,它们还是不用被回收的,但实际它们都没有使用作用了。

所以主流的jvm都不使用引用计数法来管理内存,而是采用下面的可达性分析法,下图是它的基本思路示意图。

1.1.2 可达性分析法

由图可知,object5、object6和object7都没有到GCRoots对象的引用链,它们都会被回收。

按照上面的例子,坏了的椅背和没坏的轮子都属于椅子对象(object5),但是椅子本身不被使用,就没有引用到它们的引用链了。

可做为GC Roots的对象

整理下:

是不是“非死不可”

哪怕对象没有到GC Roots的引用链了,它也不是“非死不可”的,它只是被判了“缓刑”,真正要宣判,要经过两次标记,

  • 第一次:对象可达性分析之后,发现没有与GCRoots相连接,此时会被第一次标记并筛选。
  • 第二次:对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,此时会被认定为没必要执行。

Stop the world?

2. 四种引用类型

前面说的对象有没有被引用,那什么是“引用”?有哪些类型?

对于GC来说,对象是否被应该回收的取决于该对象是否被引用。 为了控制对象的生存周期,提供了四种引用方式,引用的强度从强到弱分别为:

强引用、软引用、弱引用、虚引用。

引用的作用:

  • 方便Jvm进行垃圾回收
  • 方便开发人员使用
类型 描述对象 使用情况
强引用 StrongReference 类似"Object obj = new Object()"这类的引用 如果一个对象具有强引用,那垃圾回收器绝不会回收它。
软引用 SoftReference 用来描述还有用但并非必需的对象 内存空间不够时(抛出OutOfMemoryError之前),才会被垃圾回收
弱引用 WeakReference 非必需对象 只能生存到下一次垃圾回收之前,无论内存是否足够 (只能活到下一集)
虚引用 PhantomReference 幽灵引用或幻影引用 唯一目的就是能在这个对象被回收时收到一个系统通知

几种类型的继承关系:

3. 怎么回收?有垃圾车吗?

JVM有自己的算法啦~

3.1 标记清除法

这是GC算法的思想基础。 将垃圾分为两个阶段:

  • 标记阶段。

    通过根节点,标记所有从根节点开始的可达对象,未标记过的对象就是未被引用的垃圾对象。

  • 清除阶段。

    清除所有未被标记的对象。

3.2 复制算法

为了解决效率问题而出现的。

将原有的内存空间分为两块,每次只使用其中一块。

在垃圾回收时,将正在使用的内存中存活对象复制到未使用的内存块,然后清除使用的内存块中所有的对象。

3.3 标记整理算法

是前两种GC方法的综合体。

过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。

算法 优点 缺点
标记清除法 思路简单且实现方便 1.效率不太高。
2. 回收后的状态里,可用内存不连续。当后续需要分配大对象而无法找到连续足够的空间,会提前触发下一次GC
复制算法 回收和分配时不用考虑碎片问题,提升效率 代价是永远只能使用一半的内存
标记整理算法 解决了内存碎片问题,又提高了内存空间的使用率 牺牲了性能,需要遍历heap至少两次。

3.4 分代收集算法

现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。其实是上面几种算法的结合。

垃圾回收主要是在Young(年轻代)和 Old(老年代)工作.

堆内存空间主要是这样分配的:

分代回收的过程是这样滴

堆设置相关参数

堆内存分配与相关参数

ref

  1. 图解 Java 垃圾回收机制
  2. 简述Java垃圾回收
  3. 【深入理解JVM】GC
  4. Java垃圾回收(GC)机制详解
  5. jvm优化 - 图解GC
  6. 深入理解Java的分级引用模型