-
Notifications
You must be signed in to change notification settings - Fork 287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java引用类型原理剖析 #10
Comments
以仁兄的水平,必定能够拿到顶级offer的 |
@guochanglin 谢谢,已经拿到阿里的offer~ |
six six six |
写的真好阿 真滴nice |
想问下楼主阿里头条滴滴面试中的出现的算法题,谢谢 |
确切的说应该是Recycle.Handle用的虚引用吧 |
两年就这么牛了, 佩服!! Cleaner 的实现里,没有看到 Cleaner 这个 PhantomReference 调用 clear()方法,将其 referent 置 null. 这个应该是 cleaner 对象本身被从队列换上踢掉了, 没有在引用 cleaner的, cleaner 被回收了,所以 cleaner 指向的对象也被回收了。 |
请教下在不 clear 的情况下, jvm 通过什么参数做到只通知一个 的? 在本地测试没有 clear 的情况下只会有一次通知。 这个和”对象需要被回收时加入 discoverdList" 之间好像缺点什么。 盼复。谢谢~ |
@inthendsun Debug看了下,在Cleaner调用clean方法(即remove自身)之前,其中的referent对象就已经被置null了。 |
加入 discoverdList"之前会判断Reference对象中next是否为null,如果为非null就不会加入到discoverdList。一旦通知过,就会处于非activie状态,即便不调用clear()下次也不会被处理。 |
"对于Final references和 Phantom references,clear_referent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的" PhantomReference 在JDK8里是这样的,JDK10以及后面的版本已经改了。会把referent这个字段清除了再放入队列了。 实际上在JDK8里面,我们是可以让一个PhantomReference引用,入队之后再“复活”这个对象的。
我们可以看到,前后两个Object对象打印出来的hashCode是一样的。其实就是同一个对象。 |
有几点疑惑需要楼主帮忙解答一下: |
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!
The text was updated successfully, but these errors were encountered: