对象的三种状态:
可达的
从根节点可以触及到这个对象
可复活的
一旦所有引用被释放,就是可复活状态
因为在finalize()中可能复活该对象
不可达的
在finalize()后,可能会进入不可触及状态
不可达的对象不可能复活
可以回收
引用标记
Java虚拟机提供自动内存管理机制。在GC中,没用的对象,内存是要回收的。如何高效判断对象存活是个重要的问题。
引用计数法
此算法计算对象被引用的次数。引用失效就减一。当对象引用个数为0时表示不被引用。引用计数法,失效简单,效率高是个不错的算法。但Java虚拟机并不使用引用计数器算法作为对象的引用标记。原因是不能处理对象互相引用问题。如A?B,两个对象互相引用。AB对象缺没有被引用,形成引用孤岛。
可达性分析算法
可达性分析算法,是通过一系列被称为"GC Roots"的对象作为起点,从起点开始的引用链都没有被链接,表示对象是不可达的。
Java中GC Roots对象:
- 虚拟机栈中引用的对象
- 方法区类静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈引用的对象
支配树
对象的引用类型
对象分为被引用和不被引用,但这并不利于描述数据的重要级别。对于不同的场景,提供最优的对象引用策略,引用类型细分是有必要的。
对于计算过程的对象,当然在运算结束之前都不应该被垃圾回收。
对于频繁使用数据,希望不必重复读写,作为缓存。尽可能的不要垃圾回收。
对于不重要的对象,希望垃圾回收的时候直接回收,不用关心是否被引用。
Java中所有特殊引用类型的父类。引用类型不会被直接回收(会有特殊策略)。这些引用类型多被用于缓存,缓存的目的是对象有条件的垃圾回收。Reference并不会产生对象。对象被Reference包装,成为引用对象。如果引用包装的对象没被直接使用(即强引用),也不会被GC直接垃圾回收。如果引用的对象在使用,跟普通对象没有确保是不会被垃圾回收的。引用的多种策略是在对象没有被使用的情况下发生的。
应用类型主要有三个方法:
- Get()获取包装对象
- Clear()清空
- Enquene()注册对象被回收回调
强引用(StrongReference)
强引用是普遍引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:
这是一个常见的方法片段。StrongRef是自定义类。Object o=new Object();类型的都是强引用。此时o叫实例,而不能说o是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。sRef指向的对象的作用范围只在这个方法体中。
方法执行完,生命周期结束,所以sRef指向空没有意义,这在虚拟机中会被优化。但非局部变量,则不同。
强引用在ArrayList的实现源代码:
ArrayList中用于保存数据的elementData数组。在调用clea()方法实际上是让数组中每个元素指向空,并不释放内存。对于集合的多次使用,节约内存申请和释放,并且如果元素个数差别不大,也省去了数组动态扩张、数组复制的时间。
?注:在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
-
软引用(SoftReference)
软引用多用在高效缓存,不会轻易被垃圾回收。软引用内存回收发生在,GC完成后内存还不足的情况。保证回收发生在抛出OutOfMemoryError之后。一般来讲,是二次GC。如果内存充足,软引用是不会被垃圾回收的。
-
弱引用(WeakReference)
弱引用对象在垃圾回收时会被回收。
用弱引用实现的API
虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
其它API
ReferenceQueue,一种当 weak, soft, phantom的referent被GC回收后,提供事件回调的接口。需要在实例化三大reference时,通过构造函数传入,phantom reference是强制需要传入的,weak和soft可不传。
回调过程:
GC回收referent后(phantom是在回收前,finalize后),将reference enqueue到RQ中,程序通过调用RQ的remove方法来感知reference被GC回收的事件。
remove方法是阻塞的,当没有referent被回收时(GC未调用enqueue),remove方法会一直挂起线程,当有referent被回收时,该方法返回 referent对应的reference对象。
同样,RQ也提供了一个非阻塞的方法 poll,但这样就做不到实时回调了。