起因
看guaua cache的时候看到源码里面有用到softReference、weakReference等作为key和value,突然想要深入理解一下引用的原理,看了源码和不少文章,终于有点感觉了,主要是gc控制了一些参数的设置,所以感觉有点难理解。关于基本的知识,我就不赘述了,反正我也是看的大家的文章?
源码和流程
大部分网上的文章都是差不多贴的源码注释:
/* A Reference instance is in one of four possible internal states:
*
* Active: Subject to special treatment by the garbage collector. Some
* time after the collector detects that the reachability of the
* referent has changed to the appropriate state, it changes the
* instance's state to either Pending or Inactive, depending upon
* whether or not the instance was registered with a queue when it was
* created. In the former case it also adds the instance to the
* pending-Reference list. Newly-created instances are Active.
*
* Pending: An element of the pending-Reference list, waiting to be
* enqueued by the Reference-handler thread. Unregistered instances
* are never in this state.
*
* Enqueued: An element of the queue with which the instance was
* registered when it was created. When an instance is removed from
* its ReferenceQueue, it is made Inactive. Unregistered instances are
* never in this state.
*
* Inactive: Nothing more to do. Once an instance becomes Inactive its
* state will never change again.
*
* The state is encoded in the queue and next fields as follows:
*
* Active: queue = ReferenceQueue with which instance is registered, or
* ReferenceQueue.NULL if it was not registered with a queue; next =
* null.
*
* Pending: queue = ReferenceQueue with which instance is registered;
* next = this
*
* Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
* in queue, or this if at end of list.
*
* Inactive: queue = ReferenceQueue.NULL; next = this.
*
* With this scheme the collector need only examine the next field in order
* to determine whether a Reference instance requires special treatment: If
* the next field is null then the instance is active; if it is non-null,
* then the collector should treat the instance normally.
*
* To ensure that a concurrent collector can discover active Reference
* objects without interfering with application threads that may apply
* the enqueue() method to those objects, collectors should link
* discovered objects through the discovered field. The discovered
* field is also used for linking Reference objects in the pending list.
*/
注释讲的算比较清楚了,虽然是英文,大家应该可以看懂啦!有个概念就行。网上有个状态转换图,比较清晰:
上面的图大概描述了一个对象从创建被回收的过程,看了这些好像有点了解了,但是还是有点蒙,我们再看看Reference的成员变量:
referent:被封装的对象
queue:注册的引用队列,只有注册了队列的Reference,才会有pending和enqueued的状态,引用队列的使用可以让我们知道对象什么时候被gc,gc的时候引用会被enqueue到队列里面,我们可以从队列中取出来,或者做一些监控(资源清除等),没有注册队列的reference,我们就一脸懵逼了。
next:在引用队列里面形成一个链表,也就是指向下一个被gc的引用对象,既然next指向引用队列的gc对象,那注释里面说”next != null,说明就是被gc了,收集器应该处理掉他了”就不难理解了。
discovered:这个我觉得是待加入到引用队列里面的一个对象,JVM帮我们设置的,和pending对象联合使用形成一个链表,相当于pending队列的next吧。
pending:static类型的,说明只有一个,也就是pending链表的head了。
ReferenceHandler:一个Thead,而且在static初始化代码里面设置了Thread.MAX_PRIORITY最高优先级,run方法里面就是个死循环
public void run() {
while (true) {
tryHandlePending(true);
}
}
tryHandlePending这个方法里面主要就是不断取出pending列表里面的引用,并且加入到ReferenceQueue里面去,也就是上图中从pending队列放到了ReferenceQueue队列中了。
reference里面的pending队列和referenceQueue队列都有lock进行控制,不会出现并发问题。
分析完成员变量,不知道大家理解一点了没有,我的表达水平有限,所以想多写写,练练手?。上面两张图还是画的很清楚的?,大家可以结合源码再看看,有什么问题可以交流,帮助我打怪升级。
具体的引用
软引用和弱引用:
软引用是在内存不足的情况下进行回收,那什么时候算内存不足呢?具体的jvm源码也不贴了,大致的计算方式就是:
interval > get_heap_free_at_last_gc * SoftRefLRUPolicyMSPerMB
SoftRefLRUPolicyMSPerMB:每1M空闲空间可保持的SoftReference对象生存的时长(单位毫秒),默认值是1000
interval= timestamp - clock(软引用的两个变量)
意思就是当这个引用被get(访问)的时间足够长,就可以被回收了,而get_heap_free_at_last_gc越大(内存越大,越不会内存不足),或者SoftRefLRUPolicyMSPerMB越大,软引用存活的时间就越长。
软引用会有一定的问题,由于要在内存不足的时候被回收,那如果一直不回收,就会被放到老年代,而老年代会越来越大,导致不停地full gc。
相对软引用,弱引用用的就比较多了,只要gc就会被回收,对JVM没有副作用,对于一般的缓存还挺有效的。
幻引用主要的实现就是Cleaner了,实现资源的回收和释放。
今天就这样了。以后有新发现再说。
参考:
https://www.jianshu.com/p/439a8f738153
https://coldwalker.com/2019/02//gc_intro/