Java引用类型之软引用(1)
Java使用SoftReference来表示软引用,软引用是用来描述一些“还有用但是非必须”的对象。对于软引用关联着的对象,在JVM应用即将发生内存溢出异常之前,将会把这些软引用关联的对象列进去回收对象范围之中进行第二次回收。如果这次回收之后还是没有足够的内存,才会抛出内存溢出异常。简单来说就是:
- 如果内存空间足够,垃圾回收器就不会回收软引用关联着的对象。
- 如果内存空间不足,垃圾回收器在将要抛出内存溢出异常之前会回收软引用关联着的对象。
后面会详细介绍关于内存空间的计算方式。
下面是软引用类及重要变量和方法的定义:
public class SoftReference<T> extends Reference<T> {
static private long clock;
private long timestamp;
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
类中定义了2个字段:clock和timestamp,这2个字段可以计算内存空间,进而影响到对象是否需要被回收。 clock是个静态变量,每次GC时都会将该字段设置成当前时间;timestamp字段会在调用get()方法时可能更新为当前clock的值。
HotSpot在GC时,通过调用ReferenceProcessor::process_discovered_reflist()方法来查找引用(包括软引用、弱引用、最终引用和幻引用),方法对软引用的处理逻辑如下:
ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor,
GCTimer* gc_timer
){ // ...
_soft_ref_timestamp_clock = java_lang_ref_SoftReference::clock(); // Soft references
size_t soft_count = 0;
{
soft_count = process_discovered_reflist(_discoveredSoftRefs,
_current_soft_ref_policy, true,
is_alive, keep_alive, complete_gc, task_executor);
} update_soft_ref_master_clock(); // 省略对其它引用的处理逻辑
}
调用的java_lang_ref_SoftReference::clock()方法的实现如下:
jlong java_lang_ref_SoftReference::clock() {
InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::SoftReference_klass());
jlong* offset = (jlong*)ik->static_field_addr(static_clock_offset);
return *offset;
}
address InstanceKlass::static_field_addr(int offset) {
return (address)(offset + InstanceMirrorKlass::offset_of_static_fields() + cast_from_oop<intptr_t>(java_mirror()));
}
方法获取java.lang.ref.SoftReference类中的clock属性的值。
调用的ReferenceProcessor::update_soft_ref_master_clock()方法的实现如下:
void ReferenceProcessor::update_soft_ref_master_clock() {
// Update (advance) the soft ref master clock field. This must be done
// after processing the soft ref list.
// We need a monotonically(单调地,无变化地;) non-deccreasing time in ms but
// os::javaTimeMillis() does not guarantee monotonicity.
jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC;
jlong soft_ref_clock = java_lang_ref_SoftReference::clock();
assert(soft_ref_clock == _soft_ref_timestamp_clock, "soft ref clocks out of sync");
// The values of now and _soft_ref_timestamp_clock are set using
// javaTimeNanos(), which is guaranteed to be monotonically
// non-decreasing provided the underlying platform provides such
// a time source (and it is bug free).
// In product mode, however, protect ourselves from non-monotonicty.
if (now > _soft_ref_timestamp_clock) {
_soft_ref_timestamp_clock = now;
java_lang_ref_SoftReference::set_clock(now);
}
// Else leave clock stalled at its old value until time progresses
// past clock value.
}
调用process_discovered_reflist()方法继续处理软引用,方法的实现如下:
size_t ReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[], // refs_lists就是之前提到的DiscoveredList
ReferencePolicy* policy, // 只有处理软引用时才有值,其它引用传递的值为NULL
bool clear_referent, // 软引用和弱引用值为true,最终引用和幻引用值为false
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor
){
bool mt_processing = task_executor != NULL && _processing_is_mt;
// If discovery used MT and a dynamic number of GC threads, then
// the queues must be balanced for correctness if fewer than the
// maximum number of queues were used. The number of queue used
// during discovery may be different than the number to be used
// for processing so don't depend of _num_q < _max_num_q as part
// of the test.
bool must_balance = _discovery_is_mt; if (
(mt_processing && ParallelRefProcBalancingEnabled) ||
must_balance
){
balance_queues(refs_lists);
} size_t total_list_count = total_count(refs_lists); // Phase 1 (soft refs only):
// . Traverse the list and remove any SoftReferences whose
// referents are not alive, but that should be kept alive for
// policy reasons. Keep alive the transitive closure of all
// such referents.
if (policy != NULL) {
if (mt_processing) {
RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);
task_executor->execute(phase1);
} else {
for (uint i = 0; i < _max_num_q; i++) {
process_phase1(refs_lists[i], policy,is_alive, keep_alive, complete_gc);
}
}
} else { // policy == NULL
assert(refs_lists != _discoveredSoftRefs,"Policy must be specified for soft references.");
} // Phase 2:
// . Traverse the list and remove any refs whose referents are alive.
if (mt_processing) {
RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);
task_executor->execute(phase2);
} else {
for (uint i = 0; i < _max_num_q; i++) {
process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
}
} // Phase 3:
// . Traverse the list and process referents as appropriate.
if (mt_processing) {
RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
task_executor->execute(phase3);
} else {
for (uint i = 0; i < _max_num_q; i++) {
process_phase3(refs_lists[i], clear_referent,is_alive, keep_alive, complete_gc);
}
} return total_list_count;
}
分3个阶段处理引用,不过第1个阶段只针对软引用进行处理,因为只有处理软引用时,传递的policy参数的值才不会为NULL。refs_lists中存放了本次GC发现的引用类型(虚引用、软引用、弱引用等),而 process_discovered_reflist方法的作用就是将不需要被回收的对象从 refs_lists移除掉, refs_lists最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给之前提到过的Reference.pending字段。
当mt_processing为true时,3个阶段可以并行执行,阶段之间还是串行执行;否则阶段中的多个任务串行执行。默认mt_processing的值为false,所以我们下面只介绍串行执行的情况。
1、process_phase1()
该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。调用的process_phase1()方法的实现如下:
// NOTE: process_phase*() are largely similar, and at a high level
// merely iterate over the extant list applying a predicate to
// each of its elements and possibly removing that element from the
// list and applying some further closures to that element.
// We should consider the possibility of replacing these
// process_phase*() methods by abstracting them into
// a single general iterator invocation that receives appropriate
// closures that accomplish this work. // (SoftReferences only) Traverse the list and remove any SoftReferences whose
// referents are not alive, but that should be kept alive for policy reasons.
// Keep alive the transitive closure of all such referents.
void ReferenceProcessor::process_phase1(DiscoveredList& refs_list,
ReferencePolicy* policy,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc) {
assert(policy != NULL, "Must have a non-NULL policy");
DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
// Decide which softly reachable refs should be kept alive.
while (iter.has_next()) {
iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));
bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();
if (
referent_is_dead && // 引用的对象referent已经不存活
// 根据相关策略判断,这个不存活的对象还不应该被回收
!policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)
){
// Remove Reference object from list
iter.remove();
// Make the Reference object active again
iter.make_active();
// keep the referent around
iter.make_referent_alive();
iter.move_to_next();
} else {
iter.next();
}
}
// Close the reachable set
complete_gc->do_void();
}
ReferencePolicy一共有4种实现,分别为NeverClearPolicy、AlwaysClearPolicy、LRUCurrentHeapPolicy与LRUMaxHeapPolicy。常用的就是LRUCurrentHeapPolicy和LRUMaxHeapPolicy,这2个类的should_clear_reference()方法的实现相同,如下:
bool LRUMaxHeapPolicy::should_clear_reference(oop p,jlong timestamp_clock) {
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check");
// The interval will be zero if the ref was accessed since the last scavenge/gc.
if(interval <= _max_interval) {
return false;
}
return true;
}
timestamp_clock就是SoftReference的静态字段clock,java_lang_ref_SoftReference::timestamp(p)对应是字段timestamp。如果上次GC后有调用SoftReference类的get()方法, 那么interval值为0,否则为若干次GC之间的时间差。
_max_interval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。
void LRUCurrentHeapPolicy::setup() {
_max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
void LRUMaxHeapPolicy::setup() {
size_t max_heap = MaxHeapSize;
max_heap -= Universe::get_heap_used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
第1个方法中,SoftRefLRUPolicyMSPerMB默认为1000,其实就是1000ms/MB=1s/MB,也就是说上次GC后可用堆大小如果是10MB,那么_max_interval的值就是10s,根据should_clear_reference()方法的判断逻辑,软引用可以至少存活10s的时间。
第2个方法中,根据计算的逻辑可知,对象存储的时间与(堆的最大值大小-上次GC时堆已经使用的大小)有关。
在ReferenceProcessor::process_phase1()方法中,使用DiscoveredListIterator迭代器来遍历DiscoveredList列表,这个迭代器的实现如下:
// Iterator for the list of discovered references.
class DiscoveredListIterator {
private:
DiscoveredList& _refs_list;
HeapWord* _prev_next;
oop _prev;
oop _ref;
HeapWord* _discovered_addr;
oop _next;
HeapWord* _referent_addr;
oop _referent;
OopClosure* _keep_alive;
BoolObjectClosure* _is_alive; public:
inline DiscoveredListIterator(DiscoveredList& refs_list,
OopClosure* keep_alive,
BoolObjectClosure* is_alive):
_refs_list(refs_list),
_prev_next(refs_list.adr_head()), // 前一个的next属性值
_prev(NULL),
_ref(refs_list.head()),
_next(NULL),
_keep_alive(keep_alive),
_is_alive(is_alive)
{ } // Returns true if referent is alive.
inline bool is_referent_alive() const {
return _is_alive->do_object_b(_referent);
} // Loads data for the current reference.
// The "allow_null_referent" argument tells us to allow for the possibility
// of a NULL referent in the discovered Reference object. This typically
// happens in the case of concurrent collectors that may have done the
// discovery concurrently, or interleaved, with mutator execution.
void load_ptrs(DEBUG_ONLY(bool allow_null_referent)); // Move to the next discovered reference.
inline void next() {
_prev_next = _discovered_addr;
_prev = _ref;
move_to_next();
} // Make the referent alive.
inline void make_referent_alive() {
if (UseCompressedOops) {
_keep_alive->do_oop((narrowOop*)_referent_addr);
} else {
_keep_alive->do_oop((oop*)_referent_addr);
}
} inline void move_to_next() {
if (_ref == _next) {
// End of the list.
_ref = NULL;
} else {
_ref = _next;
}
assert(_ref != _first_seen, "cyclic ref_list found");
NOT_PRODUCT(_processed++);
}
};
其它方法的实现如下:
void DiscoveredListIterator::load_ptrs(DEBUG_ONLY(bool allow_null_referent)) {
_discovered_addr = java_lang_ref_Reference::discovered_addr(_ref);
oop discovered = java_lang_ref_Reference::discovered(_ref);
assert(_discovered_addr && discovered->is_oop_or_null(),"discovered field is bad");
_next = discovered;
_referent_addr = java_lang_ref_Reference::referent_addr(_ref);
_referent = java_lang_ref_Reference::referent(_ref);
assert(Universe::heap()->is_in_reserved_or_null(_referent),"Wrong oop found in java.lang.Reference object");
assert(allow_null_referent ?
_referent->is_oop_or_null()
: _referent->is_oop(),
"bad referent");
}
void DiscoveredListIterator::remove() {
assert(_ref->is_oop(), "Dropping a bad reference");
oop_store_raw(_discovered_addr, NULL);
// First _prev_next ref actually points into DiscoveredList (gross).
oop new_next;
if (_next == _ref) {
// At the end of the list, we should make _prev point to itself.
// If _ref is the first ref, then _prev_next will be in the DiscoveredList,
// and _prev will be NULL.
new_next = _prev;
} else {
new_next = _next;
}
if (UseCompressedOops) {
// Remove Reference object from list.
oopDesc::encode_store_heap_oop((narrowOop*)_prev_next, new_next);
} else {
// Remove Reference object from list.
oopDesc::store_heap_oop((oop*)_prev_next, new_next);
}
NOT_PRODUCT(_removed++);
_refs_list.dec_length(1);
}
// Make the Reference object active again.
void DiscoveredListIterator::make_active() {
// For G1 we don't want to use set_next - it
// will dirty the card for the next field of
// the reference object and will fail
// CT verification.
if (UseG1GC) {
BarrierSet* bs = oopDesc::bs();
HeapWord* next_addr = java_lang_ref_Reference::next_addr(_ref);
if (UseCompressedOops) {
bs->write_ref_field_pre((narrowOop*)next_addr, NULL);
} else {
bs->write_ref_field_pre((oop*)next_addr, NULL);
}
java_lang_ref_Reference::set_next_raw(_ref, NULL);
} else {
java_lang_ref_Reference::set_next(_ref, NULL);
}
}
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
28、方法解析
29、klassVtable与klassItable类的介绍
30、计算vtable的大小
31、计算itable的大小
32、解析Class文件之创建InstanceKlass对象
33、字段解析之字段注入
34、类的连接
35、类的连接之验证
36、类的连接之重写(1)
37、类的连接之重写(2)
38、方法的连接
39、初始化vtable
40、初始化itable
41、类的初始化
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!

Java引用类型之软引用(1)的更多相关文章
- Java引用类型之软引用(2)
下面接着上一篇介绍第2阶段和第3阶段的处理逻辑. 2.process_phase2() 第2个阶段移除所有的referent还存活的Reference,也就是从refs_list中移除Referenc ...
- 你不可不知的Java引用类型之——软引用
定义 软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出. 说明 软引用,顾名思义就是比较"软"一点的引用. ...
- Java引用类型之弱引用与幻像引用
这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...
- Java引用类型之最终引用
FinalReference类只有一个子类Finalizer,并且Finalizer由关键字final修饰,所以无法继承扩展.类的定义如下: class FinalReference<T> ...
- Java基础 之软引用、弱引用、虚引用 ·[转载]
Java基础 之软引用.弱引用.虚引用 ·[转载] 2011-11-24 14:43:41 Java基础 之软引用.弱引用.虚引用 浏览(509)|评论(1) 交流分类:Java|笔记分类: Ja ...
- 你不可不知的Java引用类型之——虚引用
定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获 ...
- 你不可不知的Java引用类型之——弱引用
定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型.在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收. 说明 弱 ...
- java中的软引用,弱引用,虚引用
http://zh.wikipedia.org/wiki/%E5%BC%B1%E5%BC%95%E7%94%A8 有些语言包含多种强度的弱引用.例如Java,在java.lang.ref[1]包中定义 ...
- Java中的软引用、弱引用、虚引用的适用场景以及释放机制
Java的强引用,软引用,弱引用,虚引用及其使用场景 从 JDK1.2 版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期.这四种级别由高到低依次为:强引用.软引用.弱引 ...
随机推荐
- Android Studio(Kotlin)之RecyclerView
RecyclerView应该是ListView的增强版. RecyclerView与ListView的区别(我认为的): RecyclerView的性能比ListView高 RecyclerView支 ...
- SQL数据单条转多条(Lateral View)
Lateral View和split,explode等UDTF一起使用,它能够将一行数据拆成多行数据,并在此基础上对拆分后的数据进行聚合. 单个Lateral View语句语法定义如下:lateral ...
- git的分支远程连接和远程分支的拉取推送及冲突处理
目录 备注: 知识点 Feature分支 多人协作 推送分支 远程分支推送建议 克隆(clone)远程仓库 分支的推送和冲突处理 关联本地分支和远程分支 推送时指定分支或设置分支跟踪 拉取分支时文件冲 ...
- Ubuntu安装Docker(官方文档翻译)
翻译自Docker官方文档 https://docs.docker.com/engine/installation/linux/ubuntulinux/ 之前因为看不懂官方文档,卡在某个步骤无法完成安 ...
- 性能测试必备知识(5)- 深入理解“CPU 上下文切换”
做性能测试的必备知识系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html 前言 上一篇文章中,举例了大量进程等待 CP ...
- Android Studio采坑记录
折腾了几个月的Android Studio,终于在今天被我搞定了 ( ̄▽ ̄)~* 开贴记录下,免得下次再次采坑 先说下我之前电脑的环境配置吧,sdk是几年前在网上下载别人整理出来的包,一直没有更新过 ...
- ES模糊查询来对应mysql的like查询
使用ES查询来对应mysql的like查询 建立一个测试索引 PUT /test_like1 { "mappings" : { "properties" : { ...
- 面试题十八:在O(1)的时间内删除链表的节点
方法一:将要删除的·节点的下一个节点的内容复制到该节点上,然后删除下一个节点注意特殊情况:链表只有一个节点时,则删除头节点,则把头节点设置为null, 如果删除的尾节点则需要顺序遍历链表,取得前序节点 ...
- Go语言的跳跃表(SkipList)实现
之所以会有这篇文章,是因为我在学习Go语言跳表代码实现的过程中,产生过一些困惑,但网上的大家都不喜欢写注释- - 我的代码注释一向是写的很全的,所以发出来供后来者学习参考. 本文假设你已经理解了跳表的 ...
- Elasticsearch必知必会的干货知识二:ES索引操作技巧
该系列上一篇文章<Elasticsearch必知必会的干货知识一:ES索引文档的CRUD> 讲了如何进行index的增删改查,本篇则侧重讲解说明如何对index进行创建.更改.迁移.查询配 ...