一、判断对象是否还存活的算法
1.引用计数算法
顾名思义,一个对象创建后,在其中建立一个引用计数器,若该对象被引用,就+1,若引用失效,就-1,若引用为0,说明不再被任何地方引用,回收这个对象。
优点:简单,易实现
缺点:很明显两个对象相互引用的情况,会导致彼此都不会被回收,而且计数器本身也比较消耗性能(考虑多线程的话同步、加锁是必不可少的)。
JAVA中是否采用:否
2.可达性分析算法
从GC Roots开始,根据引用关系向下搜索,走过的路径就叫引用链,所有不在引用链上的的对象,就会被判定是可回收的对象。
什么是GC Roots:
- JVM栈中的引用对象(方法入参、局部变量和临时变量)
- 类static属性引用的对象
- 常量,如字符串
- Native方法中引用的对象
- JVM内部的引用,如基本数据类型的Class对象、异常对象、ClassLoader
- 被synchronized持有的对象
- 本地代码缓存、JMXBean、JVMTI中注册的回调
- 其他关联的对象(局部回收时要考虑)
特点:比起引用计数算法,可达性解决了互相调用的对象的判活问题,而且效率和准确度更高。
JAVA中是否采用:是,在OpenJDK的G1,Shenandoah,ZGC以及Azul的PGC,C4收集器中均采用
二、JDK提供的4种对象引用
引用并非简单的内存地址引用,JDK1.2版本后提供了4种引用表述:
- Strongly Reference:强引用,最传统的地址引用关系,类似Object obj = new Object()这样的赋值关系,有强引用关系的对象,GC不会回收(往往OOM来自这里)
- Soft Reference:软引用,在内存就要溢出时,这些对象会被强制回收,若强制回收后仍然内存不足,就会抛出内存溢出异常
- Weak Reference:弱引用,在每次GC时都会被强制回收
- Phantom Reference:虚引用,与对象生命周期毫无关系,仅仅用来在对象即将被回收时收到通知。
三、不可达的对象,是否就是坐等回收了?
并不是,如果对象被判定不可达,系统会检查它的finnalize方法是否被重写过,没有重写过的对象就会被记为死亡;若finnalize()被重写过,系统会把它放在一个F-Queue里,给它们一定时间在finnalize()里再次被引用,过一段时间后,系统会来再次标记F-Queue,有两次标记的,会被记为死亡,等待回收,而重新被引用的,会被移除F-Queue继续存活。
可以看出,即使覆盖了finnalize()方法,对象也不一定会存活。如果它的finnalize执行过慢,或者在队列里还没有轮到它执行,导致在第二次标记来临时还没来得及与外部建立联系,那么它也会被标记为死亡,只要一被标记为死亡,后面就算建立联系也没用了,仍然会被回收掉。
强烈建议永远不使用finnalize,它完全无法保证对象可以再次复活,它的出现主要是JAVA当年为了抢占市场而制作出的一个模仿C语言的功能(JAVA为了抢市场导致的历史遗留问题挺多的,甚至导致现在又启动Valhala项目去解决假泛型的问题),现在已被官方声明不推荐使用。
四、回收方法区(元空间或永久代)
1.回收常量:判断一个常量不再被引用,就可以回收(到底回不回收还是由虚拟机判断)
2.回收类型(需要虚拟机有类卸载能力):满足3个条件:1.类所有实例都被回收
2.类的类加载器已被回收(除非专门自己实现了类加载器否则不可能满足)
3.这个类的Class没有在其他地方被引用、继承,无法通过反射获得这个类
大量使用了反射和动态代理、CGLIB的场景需要虚拟机具备类卸载能力。(JDK11的ZGC就不支持类卸载)