Open farmerjohngit opened 6 years ago
以仁兄的水平,必定能够拿到顶级offer的
@guochanglin 谢谢,已经拿到阿里的offer~
six six six
写的真好阿 真滴nice
想问下楼主阿里头条滴滴面试中的出现的算法题,谢谢
确切的说应该是Recycle.Handle用的虚引用吧
两年就这么牛了, 佩服!!
Cleaner 的实现里,没有看到 Cleaner 这个 PhantomReference 调用 clear()方法,将其 referent 置 null. 这个应该是 cleaner 对象本身被从队列换上踢掉了, 没有在引用 cleaner的, cleaner 被回收了,所以 cleaner 指向的对象也被回收了。
请教下在不 clear 的情况下, jvm 通过什么参数做到只通知一个 的? 在本地测试没有 clear 的情况下只会有一次通知。 这个和”对象需要被回收时加入 discoverdList" 之间好像缺点什么。
盼复。谢谢~
两年就这么牛了, 佩服!!
Cleaner 的实现里,没有看到 Cleaner 这个 PhantomReference 调用 clear()方法,将其 referent 置 null. 这个应该是 cleaner 对象本身被从队列换上踢掉了, 没有在引用 cleaner的, cleaner 被回收了,所以 cleaner 指向的对象也被回收了。
@inthendsun Debug看了下,在Cleaner调用clean方法(即remove自身)之前,其中的referent对象就已经被置null了。
请教下在不 clear 的情况下, jvm 通过什么参数做到只通知一个 的? 在本地测试没有 clear 的情况下只会有一次通知。 这个和”对象需要被回收时加入 discoverdList" 之间好像缺点什么。
盼复。谢谢~
加入 discoverdList"之前会判断Reference对象中next是否为null,如果为非null就不会加入到discoverdList。一旦通知过,就会处于非activie状态,即便不调用clear()下次也不会被处理。 /* When active: NULL
"对于Final references和 Phantom references,clear_referent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的"
PhantomReference 在JDK8里是这样的,JDK10以及后面的版本已经改了。会把referent这个字段清除了再放入队列了。
实际上在JDK8里面,我们是可以让一个PhantomReference引用,入队之后再“复活”这个对象的。
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
Object obj = new Object();
System.out.println(obj.hashCode());
ReferenceQueue<Object> refQueue =new ReferenceQueue<>();
PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue);
Object objg = phanRef.get();
//这里拿到的是null
System.out.println(objg);
//让obj变成垃圾
obj=null;
System.gc();
Thread.sleep(1000);
//gc后会将phanRef加入到refQueue中
Reference<? extends Object> phanRefP = refQueue.remove();
//这里输出true
System.out.println(phanRefP==phanRef);
Field referent = Reference.class.getDeclaredField("referent");
referent.setAccessible(true);
Object o = referent.get(phanRef);
System.out.println(o.hashCode());
}
我们可以看到,前后两个Object对象打印出来的hashCode是一样的。其实就是同一个对象。
有几点疑惑需要楼主帮忙解答一下: 1.Reference对象的生命周期那个图中,对象被回收,加入到引用队列,为什么下面还有一步:从引用队列中移除 2.在虚引用的列子中,说虚引用不会被回收,但是不回收就不会入队,为什么refQueue.remove()后有对象引用,那不就说明被回收了么?前后矛盾了 3.弱引用,说是内存不足就会被回收,回收的是什么?即使可达、对象存活也会被回收么?可是在 process_phase2 :移除所有指向对象还存活的引用,说明内存不足时,回收的是不存活对象啊!!!又矛盾了
Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是我们经常使用的
Object a = new Object();
这样的形式,在Java中并没有对应的Reference类。本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。
更多文章见个人博客:https://github.com/farmerjohngit/myblog
问题
在分析前,先抛几个问题?
1.网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
2.网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
3.虚引用在Jdk中有哪些场景下用到了呢?
Reference
我们先看下
Reference.java
中的几个字段一个Reference对象的生命周期如下:
主要分为Native层和Java层两个部分。
Native层在GC时将需要被回收的Reference对象加入到DiscoveredList中(代码在
referenceProcessor.cpp
中process_discovered_references
方法),然后将DiscoveredList的元素移动到PendingList中(代码在referenceProcessor.cpp
中enqueue_discovered_ref_helper
方法),PendingList的队首就是Reference类中的pending对象。 具体代码就不分析了,有兴趣的同学可以看看这篇文章。看看Java层的代码
流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。
看完了Reference的实现,再看看几个实现类里,各自有什么不同。
SoftReference
软引用的实现很简单,就多了两个字段:
clock
和timestamp
。clock
是个静态变量,每次GC时都会将该字段设置成当前时间。timestamp
字段则会在每次调用get方法时将其赋值为clock
(如果不相等且对象没被回收)。那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?
这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。
refs_lists
中存放了本次GC发现的某种引用类型(虚引用、软引用、弱引用等),而process_discovered_reflist
方法的作用就是将不需要被回收的对象从refs_lists
移除掉,refs_lists
最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给上文提到过的Reference.java#pending
字段。ReferencePolicy一共有4种实现:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永远返回false,代表永远不回收SoftReference,在JVM中该类没有被使用,AlwaysClearPolicy则永远返回true,在
referenceProcessor.hpp#setup
方法中中可以设置policy为AlwaysClearPolicy,至于什么时候会用到AlwaysClearPolicy,大家有兴趣可以自行研究。LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法则是完全相同:
timestamp_clock
就是SoftReference的静态字段clock
,java_lang_ref_SoftReference::timestamp(p)
对应是字段timestamp
。如果上次GC后有调用SoftReference#get
,interval
值为0,否则为若干次GC之间的时间差。_max_interval
则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。其中
SoftRefLRUPolicyMSPerMB
默认为1000,前者的计算方法和上次GC后可用堆大小有关,后者计算方法和(堆大小-上次gc时堆使用大小)有关。看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。
WeakReference
可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的
process_discovered_reflist
方法:不管是弱引用还是其他引用类型,将字段referent置null的操作都发生在
process_phase3
中,而具体行为是由clear_referent
的值决定的。而clear_referent
的值则和引用类型相关。可以看到,对于Soft references和Weak references
clear_referent
字段传入的都是true,这也符合我们的预期:对象不可达后,引用字段就会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在Phase 1,相关的引用就会从refs_list中被移除,到Phase 3时refs_list为空集合)。但对于Final references和 Phantom references,
clear_referent
字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。PhantomReference
可以看到虚引用的get方法永远返回null,我们看个demo。
从以上代码中可以看到,虚引用能够在指向对象不可达时得到一个'通知'(其实所有继承References的类都有这个功能),需要注意的是GC完成后,phanRef.referent依然指向之前创建Object,也就是说Object对象一直没被回收!
而造成这一现象的原因在上一小节末尾已经说了:
对于Final references和 Phantom references,
clear_referent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。
对于虚引用来说,从
refQueue.remove();
得到引用对象后,可以调用clear
方法强行解除引用和对象之间的关系,使得对象下次可以GC时可以被回收掉。End
针对文章开头提出的几个问题,看完分析,我们已经能给出回答:
1.我们经常在网上看到软引用的介绍是:在内存不足的时候才会回收,那内存不足是怎么定义的?为什么才叫内存不足?
软引用会在内存不足时被回收,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系,计算公式在上文中也已经给出。
2.网上对于虚引用的介绍是:形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
严格的说,虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),需要调用
clear
方法解除PhantomReference和其引用对象的引用关系。3.虚引用在Jdk中有哪些场景下用到了呢?
DirectByteBuffer中是用虚引用的子类
Cleaner.java
来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。Ps: 最近一直在找工作,所以半个多月没写文章,本来是想简单写下Java引用的几个点的,但写的时候才发现不把牵连到的知识点说清楚不行,所以又写了这么多。 希望自己能拿到一个满意的offer!