GC是如何回收SoftReference对象的
看Fresco的代码中,有这样的一个类:
/**
* To eliminate the possibility of some of our objects causing an OutOfMemoryError when they are
* not used, we reference them via SoftReferences.
* What is a SoftReference?
* <a href="http://developer.android.com/reference/java/lang/ref/SoftReference.html"></a>
* <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/ref/SoftReference.html"></a>
* A Soft Reference is a reference that is cleared when its referent is not strongly reachable and
* there is memory pressure. SoftReferences as implemented by Dalvik blindly treat every second
* SoftReference as a WeakReference every time a garbage collection happens, - i.e. clear it unless
* there is something else referring to it:
* <a href="https://goo.gl/Pe6aS7">dalvik</a>
* <a href="https://goo.gl/BYaUZE">art</a>
* It will however clear every SoftReference if we don't have enough memory to satisfy an
* allocation after a garbage collection.
* <p>
* This means that as long as one of the soft references stays alive, they all stay alive. If we
* have two SoftReferences next to each other on the heap, both pointing to the same object, then
* we are guaranteed that neither will be cleared until we otherwise would have thrown an
* OutOfMemoryError. Since we can't strictly guarantee the location of objects on the heap, we use
* 3 just to be on the safe side.
* TLDR: It's a reference that's cleared if and only if we otherwise would have encountered an OOM.
*/
public class OOMSoftReference<T> {
SoftReference<T> softRef1;
SoftReference<T> softRef2;
SoftReference<T> softRef3;
public OOMSoftReference() {
softRef1 = null;
softRef2 = null;
softRef3 = null;
}
public void set(@Nonnull T hardReference) {
softRef1 = new SoftReference<T>(hardReference);
softRef2 = new SoftReference<T>(hardReference);
softRef3 = new SoftReference<T>(hardReference);
}
@Nullable
public T get() {
return (softRef1 == null ? null : softRef1.get());
}
public void clear() {
if (softRef1 != null) {
softRef1.clear();
softRef1 = null;
}
if (softRef2 != null) {
softRef2.clear();
softRef2 = null;
}
if (softRef3 != null) {
softRef3.clear();
softRef3 = null;
}
}
}
当看到类的名字的时候就感觉奇怪,因为大家听到OOM都感觉害怕,为什么还起这样的一个名字?
带着疑问,看了下代码,三个SoftReference引用同一个对象,甚是出奇。WHY?
然后看注释,注释一大段,其实就只有一个关键点:什么是SoftReference?有何特点?
SoftReference
在android developer官方文档上对SoftReference是这样描述的:
Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand.
Suppose that the garbage collector determines at a certain point in time that an object is softly reachable. At that time it may choose to clear atomically all soft references to that object and all soft references to any other softly-reachable objects from which that object is reachable through a chain of strong references. At the same time or at some later time it will enqueue those newly-cleared soft references that are registered with reference queues.
All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError. Otherwise no constraints are placed upon the time at which a soft reference will be cleared or the order in which a set of such references to different objects will be cleared. Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.
注意红色段落的描述:所有的SoftReference必定会在OOM发生前被回收,但是,究竟虚拟机要回收哪个SoftReference或者回收的顺序是怎么样是没有限制的。虚拟机的实现一般倾向于清掉最新创建或者最新被使用的SoftReference。
看到这里,有一个想法就是,三个SoftReference的作用应该和GC对SoftReference的回收策略有关,至于有何相关依然很难确定。恰巧,上面的代码注释中,有两个链接:https://goo.gl/Pe6aS7和https://goo.gl/BYaUZE,链接到的页面就是dalvik和art虚拟机的源代码,这就印证了我们的想法。那么我们就看看虚拟机对SoftReference的回收策略是如何的?
Virtual Machine GC
循着注释的链接,我们去看一下虚拟机GC对SoftReference的回收策略。选择dalvik虚拟部分去看:
/*
* Walks the reference list marking any references subject to the
* reference clearing policy. References with a black referent are
* removed from the list. References with white referents biased
* toward saving are blackened and also removed from the list.
*/
static void preserveSomeSoftReferences(Object **list)
{
assert(list != NULL);
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
Object *clear = NULL;
size_t counter = 0;
while (*list != NULL) {
//从list中取出head
Object *ref = dequeuePendingReference(list);
//取出reference指向的对象
Object *referent = dvmGetFieldObject(ref, referentOffset);
if (referent == NULL) {
/* Referent was cleared by the user during marking. */
continue;
}
//判断对象是否被marked
bool marked = isMarked(referent, ctx);
//如果没有被marked,而且该对象是处于list的偶数位置,则为ture
if (!marked && ((++counter) & 1)) {
/* Referent is white and biased toward saving, mark it. */
//mark对象
markObject(referent, ctx);
marked = true;
}
//依然没有mark的对象插入到clear list头中
if (!marked) {
/* Referent is white, queue it for clearing. */
enqueuePendingReference(ref, &clear);
}
}
//clear list复制给list
*list = clear;
/*
* Restart the mark with the newly black references added to the
* root set.
*/
processMarkStack(ctx);
}
MarkSweep.cpp
总所周知,dalvik虚拟机的GC算法是Mark-Sweep,即回收过程主要分两部分,Mark阶段是对对象进行标记,Sweep阶段是对没有被标记的对象进行回收。
大概的分析可以知道上面的函数是主要作用是:遍历所有的SoftReference,位置是偶数位的进行Mark,奇数位的对象进入清除队列。
究竟是不是这样,我们看调用该函数的地方:
/*
* Process reference class instances and schedule finalizations.
*/
void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
Object **weakReferences,
Object **finalizerReferences,
Object **phantomReferences)
{
assert(softReferences != NULL);
assert(weakReferences != NULL);
assert(finalizerReferences != NULL);
assert(phantomReferences != NULL);
/*
* Unless we are in the zygote or required to clear soft
* references with white references, preserve some white
* referents.
*/
/*
* 如果当前不是zygote进程,而且没有设置clearSoftRefs为true,则调用preserveSomeSoftReferences
* 去mark 偶数位的SoftReference引用的对象
*/
if (!gDvm.zygote && !clearSoftRefs) {
preserveSomeSoftReferences(softReferences);
}
/*
* Clear all remaining soft and weak references with white
* referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
* Preserve all white objects with finalize methods and schedule
* them for finalization.
*/
enqueueFinalizerReferences(finalizerReferences);
/*
* Clear all f-reachable soft and weak references with white
* referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
* Clear all phantom references with white referents.
*/
clearWhiteReferences(phantomReferences);
/*
* At this point all reference lists should be empty.
*/
assert(*softReferences == NULL);
assert(*weakReferences == NULL);
assert(*finalizerReferences == NULL);
assert(*phantomReferences == NULL);
}
再看clearWhiteReferences方法:
/*
* Unlink the reference list clearing references objects with white
* referents. Cleared references registered to a reference queue are
* scheduled for appending by the heap worker thread.
*/
static void clearWhiteReferences(Object **list)
{
assert(list != NULL);
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
while (*list != NULL) {
Object *ref = dequeuePendingReference(list);
Object *referent = dvmGetFieldObject(ref, referentOffset);
//没有被mark的对象,会被回收掉
if (referent != NULL && !isMarked(referent, ctx)) {
/* Referent is white, clear it. */
clearReference(ref);
if (isEnqueuable(ref)) {
enqueueReference(ref);
}
}
}
assert(*list == NULL);
}
从上面的代码可以知道,preserveSomeSoftReferences函数的作用其实就是保留一部分的SoftReference引用的对象,另外一部分就会被垃圾回收掉,而这个策略就是位置的奇偶性。
然后我们回到,那段注释
This means that as long as one of the soft references stays alive, they all stay alive. If we
have two SoftReferences next to each other on the heap, both pointing to the same object, then
we are guaranteed that neither will be cleared until we otherwise would have thrown an
OutOfMemoryError. Since we can't strictly guarantee the location of objects on the heap, we use
3 just to be on the safe side.
感觉有点恍然大悟,注释的意思就是说:如果两个SoftReference相邻(一奇一偶),那么这两个SoftReference引用的对象就不会被GC回收掉,但是,SoftReference的位置是不能够确定的,所以,为了“安全起见”,使用三个SoftReference(为什么不是10个)去引用对象,尽可能地防止被GC回收。
到此,我们基本明白这个OOMSoftReference为什么改这个名字了,目的就是防止对象被GC回收掉,那么,如果这样做不就真的容易引起OOM的发生吗?其实不然。原因就是前面提到的,“所有的SoftReference必定会在OOM发生前被回收”。
GC_BEFORE_OOM
继续追根究底,所有的真相都在代码中,下面这段代码是当需要分配任何一个对象内存时,都会调用的:
/* Try as hard as possible to allocate some memory.
*/
static void *tryMalloc(size_t size)
{
void *ptr;
//TODO: figure out better heuristics
// There will be a lot of churn if someone allocates a bunch of
// big objects in a row, and we hit the frag case each time.
// A full GC for each.
// Maybe we grow the heap in bigger leaps
// Maybe we skip the GC if the size is large and we did one recently
// (number of allocations ago) (watch for thread effects)
// DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other
// (or, at least, there are only 0-5 objects swept each time)
//尝试分配内存,分配成功则返回
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/*
* The allocation failed. If the GC is running, block until it
* completes and retry.
*/
//GC进行中,等待
if (gDvm.gcHeap->gcRunning) {
/*
* The GC is concurrently tracing the heap. Release the heap
* lock, wait for the GC to complete, and retrying allocating.
*/
dvmWaitForConcurrentGcToComplete();
} else {
/*
* Try a foreground GC since a concurrent GC is not currently running.
*/
//注意这里的参数是false
gcForMalloc(false);
}
//尝试分配内存,分配成功则返回
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/* Even that didn't work; this is an exceptional state.
* Try harder, growing the heap if necessary.
*/
//尝试分配内存,不够分配Heap就自增,尝试分配,分配成功则返回
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
size_t newHeapSize;
newHeapSize = dvmHeapSourceGetIdealFootprint();
//TODO: may want to grow a little bit more so that the amount of free
// space is equal to the old free space + the utilization slop for
// the new allocation.
LOGI_HEAP("Grow heap (frag case) to "
"%zu.%03zuMB for %zu-byte allocation",
FRACTIONAL_MB(newHeapSize), size);
return ptr;
}
/* Most allocations should have succeeded by now, so the heap
* is really full, really fragmented, or the requested size is
* really big. Do another GC, collecting SoftReferences this
* time. The VM spec requires that all SoftReferences have
* been collected and cleared before throwing an OOME.
*/
//TODO: wait for the finalizers from the previous GC to finish
LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation",
size);
//注意这里的参数是true
gcForMalloc(true);
//尝试分配内存,不够分配Heap就自增,尝试分配,分配成功则返回
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
//TODO: maybe wait for finalizers and try one last time
LOGE_HEAP("Out of memory on a %zd-byte allocation.", size);
//TODO: tell the HeapSource to dump its state
dvmDumpThread(dvmThreadSelf(), false);
return NULL;
}
看看gcForMalloc函数:
/* Do a full garbage collection, which may grow the
* heap as a side-effect if the live set is large.
*/
static void gcForMalloc(bool clearSoftReferences)
{
if (gDvm.allocProf.enabled) {
Thread* self = dvmThreadSelf();
gDvm.allocProf.gcCount++;
if (self != NULL) {
self->allocProf.gcCount++;
}
}
/* This may adjust the soft limit as a side-effect.
*/
//clearSoftReferences为true,则GC类似为GC_BEFORE_OOM,否则为GC_FOR_MALLOC
const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
dvmCollectGarbageInternal(spec);
}
/*
* Initiate garbage collection.
*
* NOTES:
* - If we don't hold gDvm.threadListLock, it's possible for a thread to
* be added to the thread list while we work. The thread should NOT
* start executing, so this is only interesting when we start chasing
* thread stacks. (Before we do so, grab the lock.)
*
* We are not allowed to GC when the debugger has suspended the VM, which
* is awkward because debugger requests can cause allocations. The easiest
* way to enforce this is to refuse to GC on an allocation made by the
* JDWP thread -- we have to expand the heap or fail.
*/
void dvmCollectGarbageInternal(const GcSpec* spec)
{
GcHeap *gcHeap = gDvm.gcHeap;
u4 gcEnd = 0;
u4 rootStart = 0 , rootEnd = 0;
u4 dirtyStart = 0, dirtyEnd = 0;
size_t numObjectsFreed, numBytesFreed;
size_t currAllocated, currFootprint;
size_t percentFree;
int oldThreadPriority = INT_MAX;
/* The heap lock must be held.
*/
if (gcHeap->gcRunning) {
LOGW_HEAP("Attempted recursive GC");
return;
}
// Trace the beginning of the top-level GC.
if (spec == GC_FOR_MALLOC) {
ATRACE_BEGIN("GC (alloc)");
} else if (spec == GC_CONCURRENT) {
ATRACE_BEGIN("GC (concurrent)");
} else if (spec == GC_EXPLICIT) {
ATRACE_BEGIN("GC (explicit)");
} else if (spec == GC_BEFORE_OOM) {
ATRACE_BEGIN("GC (before OOM)");
} else {
ATRACE_BEGIN("GC (unknown)");
}
.............................
...................................
/*
* All strongly-reachable objects have now been marked. Process
* weakly-reachable objects discovered while tracing.
*/
dvmHeapProcessReferences(&gcHeap->softReferences,
spec->doPreserve == false,
&gcHeap->weakReferences,
&gcHeap->finalizerReferences,
&gcHeap->phantomReferences);
#if defined(WITH_JIT)
/*
* Patching a chaining cell is very cheap as it only updates 4 words. It's
* the overhead of stopping all threads and synchronizing the I/D cache
* that makes it expensive.
*
* Therefore we batch those work orders in a queue and go through them
* when threads are suspended for GC.
*/
dvmCompilerPerformSafePointChecks();
#endif
LOGD_HEAP("Sweeping...");
dvmHeapSweepSystemWeaks();
/*
* Live objects have a bit set in the mark bitmap, swap the mark
* and live bitmaps. The sweep can proceed concurrently viewing
* the new live bitmap as the old mark bitmap, and vice versa.
*/
dvmHeapSourceSwapBitmaps();
.............................
..................................
}
再看看GC_BEFORE_OOM:
static const GcSpec kGcBeforeOomSpec = {
false, /* isPartial */
false, /* isConcurrent */
false, /* doPreserve 为false*/
"GC_BEFORE_OOM"
};
const GcSpec *GC_BEFORE_OOM = &kGcBeforeOomSpec;
看完上面代码,基本了解了为什么说“所有的SoftReference必定会在OOM发生前被回收”。
原因是:当进程不断申请内存,如果一直申请不到(尝试了多次,Heap大小已经不能再增长),那么dalvik虚拟机会触发GC_BEFORE_OOM类似的回收方式,触发这种类型GC,会保证所有SoftReference引用的对象,都会被回收掉。
Conclusions
至此,三个SoftReference的谜团终于解开,至于为什么Fresco这样做,个人猜想是,Fresco希望尽量自己管理内存的分配和释放,所以要防止对象被回收掉,避免重新分配内存,起到缓存池的作用。那为什么不使用strong reference,因为在自己管理的同时可以保证在系统内存资源紧张时,能够依赖GC,释放掉SoftReference引用对象的内存,避免真的发生OOM。
对于Art部分的回收机制,这里就不在深入,基本差不多,有兴趣的自行深究。
https://zhuanlan.zhihu.com/p/24720906
GC是如何回收SoftReference对象的的更多相关文章
- JVM学习(4)——全面总结Java的GC算法和回收机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...
- JVM学习(4)——全面总结Java的GC算法和回收机制---转载自http://www.cnblogs.com/kubixuesheng/p/5208647.html
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...
- GC(垃圾回收)
Java程序的内存分配和回收都是由JRE在后台自动进行的.JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收GC.通常JRE会提供一条超级线程来进行检测和控制,一般都是在CPU空闲或内存不足时 ...
- Java虚拟机笔记(二):GC垃圾回收和对象的引用
为什么要了解GC 我们都知道Java开发者在开发过程中是不需要关心对象的回收的,因为Java虚拟机的原因,它会自动回收那些失效的垃圾对象.那我们为什么还要去了解GC和内存分配呢? 答案很简单:当我们需 ...
- GC是如何判断一个对象为"垃圾"的?被GC判断为"垃圾"的对象一定会被回收吗?
一.GC如何判断一个对象为”垃圾”的java堆内存中存放着几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”.那么GC具体通过什么手段来 ...
- GC之二--GC是如何回收时的判断依据、shallow(浅) size、retained(保留) size、Deep(深)size
回到问题“为何会内存溢出?”. 要回答这个问题又要引出另外一个话题,既什么样的对象GC才会回收? 一.对象存活方式判断方法 在上一篇文章<GC之一--GC 的算法分析.垃圾收集器.内存分配策略介 ...
- java: system.gc()和垃圾回收机制finalize
System.gc()和垃圾回收机制前的收尾方法:finalize(收尾机制) 程序退出时,为每个对象调用一次finalize方法,垃圾回收前的收尾方法 System.gc() 垃圾回收方法 clas ...
- 关于GC进行垃圾回收的时机
前言 今天查看一个同事的代码,发现代码中多处地方使用了GC.Collect()方法,我问他为什么这么做,他说感觉程序中定义了好多变量,怕GC回收不及时,用GC.Collect()可以手动掌控GC进行垃 ...
- [翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化
将长生命周期对象和大对象池化 请记住最开始说的原则:对象要么立即回收要么一直存在.它们要么在0代被回收,要么在2代里一直存在.有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束.其它对 ...
随机推荐
- Codeforces Round #394 (Div. 2) 题解
无需吟唱,直接传送 problem A 题目大意 已知有n个偶数,m个奇数,问这些数有没有可能组成一个严格递增1的序列 题解 判断abs(n,m) <= 1即可,注意n,m均为0的情况. Cod ...
- 数据结构与算法(3)----->队列和栈
1. 栈和队列的基本性质 栈是先进后出;(像是子弹夹,后进先打出) 队列是先进先出;(像是平时排队买冰淇淋,按顺序轮流) 栈和队列在实现的结构上可以有数组和链表两种形式; (1)数组结构实现容易; ( ...
- poj3662Telephone Lines——二分+最短路
题目:http://poj.org/problem?id=3662 二分答案找出符合条件的最小长度: 假设了每个长度后,以这个为标准对每条边赋值,0为小于等于,1为大于,然后按这个值来跑最短路,在看看 ...
- SQL 优化总结(二) 索引
索引 1.索引的建立 缺省情况下建立的索引是非群集索引,但有时它并不是最佳的:合理的索引设计要建立在对各种查询的分析和预测上. 一般来说: (1) 有大量重复值.且经常有范围查询(between, ...
- 兼容ie6,ie7,ie8,firefox,chrome浏览器的代码片段
hack实现方式和原理 #hacker{ color:red; *color:white; /*for ie6,ie7*/ *+color:blue; /*for ie7*/ _color:gray; ...
- 【转】Pro Android学习笔记(九):了解Content Provider(下下)
Content provider作为信息的读出,比较常见的还有文件的读写,最基础的就是二进制文件的的读写,例如img文件,音频文件的读写.在数据库中存放了该文件的路径,我们可以通过ContentPro ...
- Django admin有用的自定义功能
引用园友 无名小妖 的博客 https://www.cnblogs.com/wumingxiaoyao/p/6928297.html 写的很好,但是博客园不能转载,不过我已经点赞了~
- 梯度算子(普通的+Robert + sobel + Laplace)
1.水平垂直差分法: 2.Robert 算子梯度 3.sobel算子 4.拉普拉斯算子
- linux下libphenom的测试代码
使用说明:测试使用libphenom库的字符串追加函数,效率是strcat的60多倍.所以在进行大量的字符串累加的时候可以考虑使用libphenom库 依赖库: ck-.tar.gz cmake-. ...
- 25.ProfileService实现(调试)
上一节课拿到的AccessToken和IdToken 实现ProfileService类 在服务端 添加ProfileService类 需要继承IProfileServiuce 用到的画图工具 Ipr ...