看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/Pe6aS7https://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对象的的更多相关文章

  1. JVM学习(4)——全面总结Java的GC算法和回收机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  2. JVM学习(4)——全面总结Java的GC算法和回收机制---转载自http://www.cnblogs.com/kubixuesheng/p/5208647.html

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  3. GC(垃圾回收)

    Java程序的内存分配和回收都是由JRE在后台自动进行的.JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收GC.通常JRE会提供一条超级线程来进行检测和控制,一般都是在CPU空闲或内存不足时 ...

  4. Java虚拟机笔记(二):GC垃圾回收和对象的引用

    为什么要了解GC 我们都知道Java开发者在开发过程中是不需要关心对象的回收的,因为Java虚拟机的原因,它会自动回收那些失效的垃圾对象.那我们为什么还要去了解GC和内存分配呢? 答案很简单:当我们需 ...

  5. GC是如何判断一个对象为"垃圾"的?被GC判断为"垃圾"的对象一定会被回收吗?

    一.GC如何判断一个对象为”垃圾”的java堆内存中存放着几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”.那么GC具体通过什么手段来 ...

  6. GC之二--GC是如何回收时的判断依据、shallow(浅) size、retained(保留) size、Deep(深)size

    回到问题“为何会内存溢出?”. 要回答这个问题又要引出另外一个话题,既什么样的对象GC才会回收? 一.对象存活方式判断方法 在上一篇文章<GC之一--GC 的算法分析.垃圾收集器.内存分配策略介 ...

  7. java: system.gc()和垃圾回收机制finalize

    System.gc()和垃圾回收机制前的收尾方法:finalize(收尾机制) 程序退出时,为每个对象调用一次finalize方法,垃圾回收前的收尾方法 System.gc() 垃圾回收方法 clas ...

  8. 关于GC进行垃圾回收的时机

    前言 今天查看一个同事的代码,发现代码中多处地方使用了GC.Collect()方法,我问他为什么这么做,他说感觉程序中定义了好多变量,怕GC回收不及时,用GC.Collect()可以手动掌控GC进行垃 ...

  9. [翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化

    将长生命周期对象和大对象池化 请记住最开始说的原则:对象要么立即回收要么一直存在.它们要么在0代被回收,要么在2代里一直存在.有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束.其它对 ...

随机推荐

  1. XAML 编码规范 (思考)

    1.尽量和Blend统一 2.兄弟元素之间需要空行 4.父子元素之间不需要空格 3.每行尽量单个属性 5.Grid的Row和Column定义不需要空行 6.Style里的Setter中不需要单行一个属 ...

  2. 【242】◀▶IEW-Unit07

    Unit 7 Education: Schools I.句子基本结构在写作中的运用 主谓宾 主系表 主谓 主谓宾宾 主谓宾补 1.主语: 1)位于句首 2)名词 例句:应该建立相关法律 Laws an ...

  3. fastIO模板

    freadIO整理 namespace fastIO{ #define BUF_SIZE 100000 ; inline char nc() { static char buf[BUF_SIZE],* ...

  4. Programming With Objective-C---- Introduction ---- Objective-C 学习(一)

    About Objective-C Objective-C is the primary programming language you use when writing software for ...

  5. codeblocks 汉字乱码

    网上有很多方法,不过目测是不同的机子和环境要用不同的设置来应对这种情况 电脑情况: win8 64-bit 装的是codeblocks v12.11 然后在我电脑上正确的配置是setting-edit ...

  6. linux下的函数dirname()和basename()使用

    总览 #include <libgen.h> char *dirname(char *path); char *basename(char *path); 说明 函数以 '/' 为分隔符 ...

  7. Jdk 1.7*安装并配置

    jdk 1.7的下载,见http://www.cnblogs.com/lchzls/p/6281448.html 新建JAVA_HOME指明JDK安装路径,就是刚才安装时所选择的路径C:\Progra ...

  8. GitHub上最火爆!码代码不得不知的所有定律法则

    目录 介绍 定律 阿姆达尔定律 (Amdahl's Law) 布鲁克斯法则 (Brooks's Law) 康威定律 (Conway's Law) 侯世达定律 (Hofstadter's Law) 技术 ...

  9. Automake使用说明

    说明 从零开始编写automake工程非常复杂也没有必要,我们只要能看懂开源项目的automake即可,然后根据自己实际情况进行修改即可,下面给出两个比较好的参考项目,其中spice-gtk涵盖了使用 ...

  10. css 实现垂直水平居中常用方法

    html <div class="outer"> <div class="inner"></div> </div> ...