首页 JVM对象回收机制(上)
文章
取消

JVM对象回收机制(上)

我们知道Java实例对象存储在堆中,等不用的时候,GC会回收它,但是GC是不受程序控制的,它会在满足条件时自动触发。那我们接下来就聊一聊这个条件。

在发生GC时,一个对象,JVM总能找到引用它的祖先,最后发现这个祖先已经被回收,那么它们就都会被清理掉,而能够躲过垃圾回收的那些祖先,我们管它叫GC Roots。

引用计数法

该方法是在对象头里面维护了一个计数器,每当该对象被引用1次,计数器+1,引用失效,则计数器-1,当计数器为0时,就会被认为无效。到那时这种方法有一个硬伤,就是针对循环引用,就处理不了了,所以现在主流的JVM都不采用这种方法了。

可达性分析法

这个算法的基本思路就是通过一系列被称为GC Roots的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径被称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,即从GC Roots到这个对象不可达,则证明此对象是不可用的。

image-20220822153735606

图中object4、object5、object6由于不能和GC Roots产生关系,所以发生GC时就会被回收。

GC Roots对象

在Java语言中,常常包含这几类对象:

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI (即所谓的Native方法)引用的对象

引用级别

如果说满足了上面的条件,即可以找到引用链的对象,就一定会存活吗?接下来要说的就是,其实判断对象的存活还与引用有关,Java对引用的概念进行了扩充,做了更细致的划分:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种。

强引用

强引用在我们代码中很常见,类似:String s = new String("s")这类的引用都是强引用,只要引用还存在,垃圾收集器就永远不会回收被引用的对象。所以当内存不足时,JVM就会抛出OutOfMemoryError的错误。

软引用

软引用时用来描述一些还有用但是并非必需的对象,在内存足够的时候,软引用对象不会被回收,只有在内存不足的时候,系统则会回收这些引用对象,如果回收了仍然没有足够的内存,才会抛出内存溢出。

从上面可以看出,软引用非常适合做缓存技术。

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

1
2
3
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
String str = new String("yuxingxin");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

我们有时候调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM根据自己的状态决定的,就算扫描到软引用对象也不一定回收它,只有内存不够的时候才回收。

当内存不足时,JVM首先会将软引用的对象置为null,然后通知垃圾回收机制进行回收。

也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用软引用对象。对那些刚构建的或刚使用过的“较新的”软引用对象会被虚拟机尽可能保留,这就是引入引用队列ReferenceQueue的原因。

弱引用

弱引用也是用来描述非必需对象的,但是它的强度会更弱一些,生命周期也更短。即当JVM进行垃圾回收时,无论内存是否足够,都会回收弱引用对象,它的应用场景和软引用类似,可以在一些对内存更加敏感的系统里采用,不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。

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

1
2
3
4
String str = new String("yuxingxin");
WeakReference<String> weakReference = new WeakReference<>(str);
// 弱引用转强引用
String strongReference = weakReference.get();

虚引用

顾名思义,形同虚设,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。它常用来跟踪对象被垃圾回收器回收的活动。

另外,它必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

1
2
3
4
String str = new String("yuxingxin");
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

总结

引用类型被垃圾回收时间用途场景生命周期
强引用从来不会对象的一般状态JVM停止运行时终止
软引用当内存不足时对象缓存内存不足时终止
弱引用正常垃圾回收时对象缓存垃圾回收后终止
虚引用正常垃圾回收时跟踪对象的垃圾回收垃圾回收后终止

OOM发生区域对比:

区域是否线程私有是否会发生OOM
程序计数器
Java虚拟机栈
本地方法栈
方法区
直接内存
本文由作者按照 CC BY 4.0 进行授权

JVM类加载机制及类加载器

JVM对象回收机制(下)