JVM是如何和“垃圾”发生关系的

时间:2022-03-16 06:11:52

JVM是如何和“垃圾”发生关系的

在开始之前,我们先回顾一下堆是个什么玩意,大家可能都知道,我们每天创建的Java对象几乎都存放在堆上面,所以说堆是一个巨大的对象池一点都不过分,在这个对象池里面管理者数据巨大的对象实例。

在对象池中对象的引用层次,有的是很深的。比如一个调用非常频繁的接口,生产对象的速度是非常可观的。对象之间的关系,可以形容成一张网。虽然Java总是给人一种有使不完的内存的感觉,但是对象也不能一直增加不减少啊,所以就必须有垃圾回收这个操作。

那么JVM如何发现垃圾的呢?

 

"垃圾回收"本文中简称 GC

你还记得电视剧中的“诛九族""?

比如小憨批打了皇帝老儿一巴掌,把皇帝老儿打的鼻青脸肿滴,皇帝老儿非常生气,他要下令诛小憨批的九族,以平心头只恨。

哈哈哈嗝~ 小憨批完了~

那么我们看看在古代这个诛九族是具体操作的呢?首先需要追溯到共同的祖先(也就是小憨批家族的大哥大),再往下逐一细数和小憨批有关系的(小憨批真坑啊)。

其实发生在堆上的垃圾回收和这个“诛九族“的是相同思路,那么我们下面具体分析一下JVM是如何进行GC的呢?

关于JVM的GC是不受程序控制的,当满足一定条件的时候就会主动触发。

当发生GC的时候,对于一个对象来说,JVM总能够找到引用它的祖先,当找到最后的时候,JVM发现这家伙的有些祖先已经玩完了,那么它们就会被JVM给干掉。

为什么还有没有被干掉的祖先呢?因为这些躲过GC的祖先们,它们是GC Roots ,长得比较特殊嘛(下面介绍它们的样子)。

当从GC Roots 向下追溯、搜索,就会产生一个引用链。当碰到有对象没有任何一个GC Roots 产生关系的话,这个对象就会被无情的干掉。(一根绳上的蚂蚱嘛)

来,我们画个图瞅瞅咋回事,如下图所示,Object5、Object6、Object7,由于不能和 GC Root 产生关联,发生 GC 时,就会被摧毁。

JVM是如何和“垃圾”发生关系的

其实所谓的垃圾回收就是围绕着GC Roots 来的,但是同时,GC Roots 也存在着很多内存泄漏的根源,因为其他引用小弟压根没有这个权利。

你可能会产生疑问,那么什么样的对象才会是GC Roots 呢?

这个不在于它是什么样的对象,关键是它所处的位置(仔细品~)。

GC Roots 是什么

 

首先,GC Roots必须是一组必须活跃的引用。简单的讲,就是程序接下来通过直接引用或间接引用,能够被访问到的潜在被使用的对象(咋感觉还是有点绕呢)。

GC Roots 是这样子滴:

  1. Java线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等等。也就是与我们栈帧相关的各种引用。
  2. 所有当前被加载的Java类。
  3. Java类的引用类型静态变量。
  4. 运行时常量池里的引用类型常量。
  5. JVM内部数据结构的一些引用,比如sun.jvm.hotspot.memory.Univers类。
  6. 用于同步的监控对象。比如调用了对象的wait()方法。
  7. JNI handles,包括global handles 和 local handles。

以上GC Roots大致可以分为一下三大类。

  1. 活动线程相关的各种引用。
  2. 类的静态变量的引用。
  3. JNI引用。

最后我们需要注意的是,我们这里说的是活跃的引用,而不是对象,对象是不能作为GC Roots的。

整个GC过程中是找到那些活对象,并把剩余的空间都认得为“无用”。而不是找到所有死掉的对象,并回收它们占用的空间。所有说,哪怕JVM的堆非常大,基于tracing的GC方式,回收速度也是跟快的。

总结

 

GC Roots 就是可达性分析法。还有一种叫作引用计数法的方式。下面我们简单介绍一下。

引用计数法:在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为一个对象添加一个引用时,引用计数器就加1;为对象删除一个引用时,引用计数器就减1;如果一个对象的引用计数为0,则说明该对象没有被引用,可以回收。

优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题。

因为存在循环引用这个致命的硬伤,没有一个主流JVM是采用引用计数法来实现 GC 的,所以你现在完全忘记引用计数这种方式了。

原文链接:https://mp.weixin.qq.com/s/t4q4lO79FSQMK0WllOcujg