我们知道Java实例对象存储在堆中,等不用的时候,GC会回收它,但是GC是不受程序控制的,它会在满足条件时自动触发。那我们接下来就聊一聊这个条件。
在发生GC时,一个对象,JVM总能找到引用它的祖先,最后发现这个祖先已经被回收,那么它们就都会被清理掉,而能够躲过垃圾回收的那些祖先,我们管它叫GC Roots。
引用计数法
该方法是在对象头里面维护了一个计数器,每当该对象被引用1次,计数器+1,引用失效,则计数器-1,当计数器为0时,就会被认为无效。到那时这种方法有一个硬伤,就是针对循环引用,就处理不了了,所以现在主流的JVM都不采用这种方法了。
可达性分析法
这个算法的基本思路就是通过一系列被称为GC Roots的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径被称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,即从GC Roots到这个对象不可达,则证明此对象是不可用的。
图中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虚拟机栈 | 是 | 是 |
本地方法栈 | 是 | 是 |
方法区 | 否 | 是 |
堆 | 否 | 是 |
直接内存 | 否 | 是 |