您的位置:首页 > 编程语言

从一段奇怪代码开始说

2016-12-29 01:26 288 查看
看Fresco的代码中,有这样的一个类:

/**
*ToeliminatethepossibilityofsomeofourobjectscausinganOutOfMemoryErrorwhentheyare
*notused,wereferencethemviaSoftReferences.
*WhatisaSoftReference?
*<ahref="http://developer.android.com/reference/java/lang/ref/SoftReference.html"></a>
*<ahref="http://docs.oracle.com/javase/7/docs/api/java/lang/ref/SoftReference.html"></a>
*ASoftReferenceisareferencethatisclearedwhenitsreferentisnotstronglyreachableand
*thereismemorypressure.SoftReferencesasimplementedbyDalvikblindlytreateverysecond
*SoftReferenceasaWeakReferenceeverytimeagarbagecollectionhappens,-i.e.clearitunless
*thereissomethingelsereferringtoit:
*<ahref="https://goo.gl/Pe6aS7">dalvik</a>
*<ahref="https://goo.gl/BYaUZE">art</a>
*ItwillhowevercleareverySoftReferenceifwedon'thaveenoughmemorytosatisfyan
*allocationafteragarbagecollection.
*<p>
*Thismeansthataslongasoneofthesoftreferencesstaysalive,theyallstayalive.Ifwe
*havetwoSoftReferencesnexttoeachotherontheheap,bothpointingtothesameobject,then
*weareguaranteedthatneitherwillbecleareduntilweotherwisewouldhavethrownan
*OutOfMemoryError.Sincewecan'tstrictlyguaranteethelocationofobjectsontheheap,weuse
*3justtobeonthesafeside.
*TLDR:It'sareferencethat'sclearedifandonlyifweotherwisewouldhaveencounteredanOOM.
*/
publicclassOOMSoftReference<T>{
SoftReference<T>softRef1;
SoftReference<T>softRef2;
SoftReference<T>softRef3;
publicOOMSoftReference(){
softRef1=null;
softRef2=null;
softRef3=null;
}
publicvoidset(@NonnullThardReference){
softRef1=newSoftReference<T>(hardReference);
softRef2=newSoftReference<T>(hardReference);
softRef3=newSoftReference<T>(hardReference);
}
@Nullable
publicTget(){
return(softRef1==null?null:softRef1.get());
}
publicvoidclear(){
if(softRef1!=null){
softRef1.clear();
softRef1=null;
}
if(softRef2!=null){
softRef2.clear();
softRef2=null;
}
if(softRef3!=null){
softRef3.clear();
softRef3=null;
}
}
}
[/code]

当看到类的名字的时候就感觉奇怪,因为大家听到OOM都感觉害怕,为什么还起这样的一个名字?

带着疑问,看了下代码,三个SoftReference引用同一个对象,甚是出奇。WHY?

然后看注释,注释一大段,其实就只有一个关键点:什么是SoftReference?有何特点?

SoftReference



在androiddeveloper官方文档上对SoftReference是这样描述的:

Softreferenceobjects,whichareclearedatthediscretionofthegarbagecollectorinresponsetomemorydemand.

Supposethatthegarbagecollectordeterminesatacertainpointintimethatanobjectissoftly
reachable.Atthattimeitmaychoosetoclearatomicallyallsoftreferencestothatobjectandallsoftreferencestoanyothersoftly-reachableobjectsfromwhichthatobjectisreachablethroughachainofstrong
references.Atthesametimeoratsomelatertimeitwillenqueuethosenewly-clearedsoftreferencesthatareregisteredwithreferencequeues.

Allsoftreferencestosoftly-reachableobjectsareguaranteedtohavebeenclearedbeforethevirtualmachinethrowsan
OutOfMemoryError
.
Otherwisenoconstraintsareplaceduponthetimeatwhichasoftreferencewillbeclearedortheorderinwhichasetofsuchreferencestodifferentobjectswillbecleared.Virtualmachineimplementationsare,however,encouragedtobiasagainstclearing
recently-createdorrecently-usedsoftreferences.

注意红色段落的描述:所有的SoftReference必定会在OOM发生前被回收,但是,究竟虚拟机要回收哪个SoftReference或者回收的顺序是怎么样是没有限制的。虚拟机的实现一般倾向于清掉最新创建或者最新被使用的SoftReference。

看到这里,有一个想法就是,三个SoftReference的作用应该和GC对SoftReference的回收策略有关,至于有何相关依然很难确定。恰巧,上面的代码注释中,有两个链接:https://goo.gl/Pe6aS7和https://goo.gl/BYaUZE,链接到的页面就是dalvik和art虚拟机的源代码,这就印证了我们的想法。那么我们就看看虚拟机对SoftReference的回收策略是如何的?

VirtualMachineGC



循着注释的链接,我们去看一下虚拟机GC对SoftReference的回收策略。选择dalvik虚拟部分去看:

/*
*Walksthereferencelistmarkinganyreferencessubjecttothe
*referenceclearingpolicy.Referenceswithablackreferentare
*removedfromthelist.Referenceswithwhitereferentsbiased
*towardsavingareblackenedandalsoremovedfromthelist.
*/
staticvoidpreserveSomeSoftReferences(Object**list)
{
assert(list!=NULL);
GcMarkContext*ctx=&gDvm.gcHeap->markContext;
size_treferentOffset=gDvm.offJavaLangRefReference_referent;
Object*clear=NULL;
size_tcounter=0;
while(*list!=NULL){
//从list中取出head
Object*ref=dequeuePendingReference(list);
//取出reference指向的对象
Object*referent=dvmGetFieldObject(ref,referentOffset);
if(referent==NULL){
/*Referentwasclearedbytheuserduringmarking.*/
continue;
}
//判断对象是否被marked
boolmarked=isMarked(referent,ctx);
//如果没有被marked,而且该对象是处于list的偶数位置,则为ture
if(!marked&&((++counter)&1)){
/*Referentiswhiteandbiasedtowardsaving,markit.*/
//mark对象
markObject(referent,ctx);
marked=true;
}
//依然没有mark的对象插入到clearlist头中
if(!marked){
/*Referentiswhite,queueitforclearing.*/
enqueuePendingReference(ref,&clear);
}
}
//clearlist复制给list
*list=clear;
/*
*Restartthemarkwiththenewlyblackreferencesaddedtothe
*rootset.
*/
processMarkStack(ctx);
}
[/code]

MarkSweep.cpp

总所周知,dalvik虚拟机的GC算法是Mark-Sweep,即回收过程主要分两部分,Mark阶段是对对象进行标记,Sweep阶段是对没有被标记的对象进行回收。

大概的分析可以知道上面的函数是主要作用是:遍历所有的SoftReference,位置是偶数位的进行Mark,奇数位的对象进入清除队列。

究竟是不是这样,我们看调用该函数的地方:

/*
*Processreferenceclassinstancesandschedulefinalizations.
*/
voiddvmHeapProcessReferences(Object**softReferences,boolclearSoftRefs,
Object**weakReferences,
Object**finalizerReferences,
Object**phantomReferences)
{
assert(softReferences!=NULL);
assert(weakReferences!=NULL);
assert(finalizerReferences!=NULL);
assert(phantomReferences!=NULL);
/*
*Unlessweareinthezygoteorrequiredtoclearsoft
*referenceswithwhitereferences,preservesomewhite
*referents.
*/
/*
*
如果当前不是zygote进程,而且没有设置
clearSoftRefs为true,则调用preserveSomeSoftReferences*去mark偶数位的SoftReference引用的对象*/
if(!gDvm.zygote&&!clearSoftRefs){
preserveSomeSoftReferences(softReferences);
}
/*
*Clearallremainingsoftandweakreferenceswithwhite
*referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
*Preserveallwhiteobjectswithfinalizemethodsandschedule
*themforfinalization.
*/
enqueueFinalizerReferences(finalizerReferences);
/*
*Clearallf-reachablesoftandweakreferenceswithwhite
*referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
*Clearallphantomreferenceswithwhitereferents.
*/
clearWhiteReferences(phantomReferences);
/*
*Atthispointallreferencelistsshouldbeempty.
*/
assert(*softReferences==NULL);
assert(*weakReferences==NULL);
assert(*finalizerReferences==NULL);
assert(*phantomReferences==NULL);
}
[/code]

再看clearWhiteReferences方法:

/*
*Unlinkthereferencelistclearingreferencesobjectswithwhite
*referents.Clearedreferencesregisteredtoareferencequeueare
*scheduledforappendingbytheheapworkerthread.
*/
staticvoidclearWhiteReferences(Object**list)
{
assert(list!=NULL);
GcMarkContext*ctx=&gDvm.gcHeap->markContext;
size_treferentOffset=gDvm.offJavaLangRefReference_referent;
while(*list!=NULL){
Object*ref=dequeuePendingReference(list);
Object*referent=dvmGetFieldObject(ref,referentOffset);
//没有被mark的对象,会被回收掉
if(referent!=NULL&&!isMarked(referent,ctx)){
/*Referentiswhite,clearit.*/
clearReference(ref);
if(isEnqueuable(ref)){
enqueueReference(ref);
}
}
}
assert(*list==NULL);
}
[/code]

从上面的代码可以知道,preserveSomeSoftReferences函数的作用其实就是保留一部分的SoftReference引用的对象,另外一部分就会被垃圾回收掉,而这个策略就是位置的奇偶性。

然后我们回到,那段注释

Thismeansthataslongasoneofthesoftreferencesstaysalive,theyallstayalive.Ifwe
havetwoSoftReferencesnexttoeachotherontheheap,bothpointingtothesameobject,then
weareguaranteedthatneitherwillbecleareduntilweotherwisewouldhavethrownan
OutOfMemoryError.Sincewecan'tstrictlyguaranteethelocationofobjectsontheheap,weuse
3justtobeonthesafeside.
[/code]

感觉有点恍然大悟,注释的意思就是说:如果两个SoftReference相邻(一奇一偶),那么这两个SoftReference引用的对象就不会被GC回收掉,但是,SoftReference的位置是不能够确定的,所以,为了“安全起见”,使用三个SoftReference(为什么不是10个)去引用对象,尽可能地防止被GC回收。

到此,我们基本明白这个OOMSoftReference为什么改这个名字了,目的就是防止对象被GC回收掉,那么,如果这样做不就真的容易引起OOM的发生吗?其实不然。原因就是前面提到的,“所有的SoftReference必定会在OOM发生前被回收”。

GC_BEFORE_OOM

继续追根究底,所有的真相都在代码中,下面这段代码是当需要分配任何一个对象内存时,都会调用的:

/*Tryashardaspossibletoallocatesomememory.
*/
staticvoid*tryMalloc(size_tsize)
{
void*ptr;
//TODO:figureoutbetterheuristics
//Therewillbealotofchurnifsomeoneallocatesabunchof
//bigobjectsinarow,andwehitthefragcaseeachtime.
//AfullGCforeach.
//Maybewegrowtheheapinbiggerleaps
//MaybeweskiptheGCifthesizeislargeandwedidonerecently
//(numberofallocationsago)(watchforthreadeffects)
//DeflateTestallocsabunchof~128kbuffersw/in0-5allocsofeachother
//(or,atleast,thereareonly0-5objectsswepteachtime)
//尝试分配内存,分配成功则返回
ptr=dvmHeapSourceAlloc(size);
if(ptr!=NULL){
returnptr;
}
/*
*Theallocationfailed.IftheGCisrunning,blockuntilit
*completesandretry.
*/
//GC进行中,等待
if(gDvm.gcHeap->gcRunning){
/*
*TheGCisconcurrentlytracingtheheap.Releasetheheap
*lock,waitfortheGCtocomplete,andretryingallocating.
*/
dvmWaitForConcurrentGcToComplete();
}else{
/*
*TryaforegroundGCsinceaconcurrentGCisnotcurrentlyrunning.
*/
//注意这里的参数是false
gcForMalloc(false);
}
//尝试分配内存,分配成功则返回
ptr=dvmHeapSourceAlloc(size);
if(ptr!=NULL){
returnptr;
}
/*Eventhatdidn'twork;thisisanexceptionalstate.
*Tryharder,growingtheheapifnecessary.
*/
//尝试分配内存,不够分配Heap就自增,尝试分配,分配成功则返回
ptr=dvmHeapSourceAllocAndGrow(size);
if(ptr!=NULL){
size_tnewHeapSize;
newHeapSize=dvmHeapSourceGetIdealFootprint();
//TODO:maywanttogrowalittlebitmoresothattheamountoffree
//spaceisequaltotheoldfreespace+theutilizationslopfor
//thenewallocation.
LOGI_HEAP("Growheap(fragcase)to"
"%zu.%03zuMBfor%zu-byteallocation",
FRACTIONAL_MB(newHeapSize),size);
returnptr;
}
/*Mostallocationsshouldhavesucceededbynow,sotheheap
*isreallyfull,reallyfragmented,ortherequestedsizeis
*reallybig.DoanotherGC,collectingSoftReferencesthis
*time.TheVMspecrequiresthatallSoftReferenceshave
*beencollectedandclearedbeforethrowinganOOME.
*/
//TODO:waitforthefinalizersfromthepreviousGCtofinish
LOGI_HEAP("ForcingcollectionofSoftReferencesfor%zu-byteallocation",
size);
//注意这里的参数是true
gcForMalloc(true);
//尝试分配内存,不够分配Heap就自增,尝试分配,分配成功则返回
ptr=dvmHeapSourceAllocAndGrow(size);
if(ptr!=NULL){
returnptr;
}
//TODO:maybewaitforfinalizersandtryonelasttime
LOGE_HEAP("Outofmemoryona%zd-byteallocation.",size);
//TODO:telltheHeapSourcetodumpitsstate
dvmDumpThread(dvmThreadSelf(),false);
returnNULL;
}
[/code]

看看gcForMalloc函数:

/*Doafullgarbagecollection,whichmaygrowthe
*heapasaside-effectifthelivesetislarge.
*/
staticvoidgcForMalloc(boolclearSoftReferences)
{
if(gDvm.allocProf.enabled){
Thread*self=dvmThreadSelf();
gDvm.allocProf.gcCount++;
if(self!=NULL){
self->allocProf.gcCount++;
}
}
/*Thismayadjustthesoftlimitasaside-effect.
*/
//
clearSoftReferences为true,则GC类似为GC_BEFORE_OOM,否则为GC_FOR_MALLOC
constGcSpec*spec=clearSoftReferences?GC_BEFORE_OOM:GC_FOR_MALLOC;
dvmCollectGarbageInternal(spec);
}
[/code]

/*
*Initiategarbagecollection.
*
*NOTES:
*-Ifwedon'tholdgDvm.threadListLock,it'spossibleforathreadto
*beaddedtothethreadlistwhilewework.ThethreadshouldNOT
*startexecuting,sothisisonlyinterestingwhenwestartchasing
*threadstacks.(Beforewedoso,grabthelock.)
*
*WearenotallowedtoGCwhenthedebuggerhassuspendedtheVM,which
*isawkwardbecausedebuggerrequestscancauseallocations.Theeasiest
*waytoenforcethisistorefusetoGConanallocationmadebythe
*JDWPthread--wehavetoexpandtheheaporfail.
*/
voiddvmCollectGarbageInternal(constGcSpec*spec)
{
GcHeap*gcHeap=gDvm.gcHeap;
u4gcEnd=0;
u4rootStart=0,rootEnd=0;
u4dirtyStart=0,dirtyEnd=0;
size_tnumObjectsFreed,numBytesFreed;
size_tcurrAllocated,currFootprint;
size_tpercentFree;
intoldThreadPriority=INT_MAX;
/*Theheaplockmustbeheld.
*/
if(gcHeap->gcRunning){
LOGW_HEAP("AttemptedrecursiveGC");
return;
}
//Tracethebeginningofthetop-levelGC.
if(spec==GC_FOR_MALLOC){
ATRACE_BEGIN("GC(alloc)");
}elseif(spec==GC_CONCURRENT){
ATRACE_BEGIN("GC(concurrent)");
}elseif(spec==GC_EXPLICIT){
ATRACE_BEGIN("GC(explicit)");
}elseif(spec==GC_BEFORE_OOM){
ATRACE_BEGIN("GC(beforeOOM)");
}else{
ATRACE_BEGIN("GC(unknown)");
}
.............................
...................................
/*
*Allstrongly-reachableobjectshavenowbeenmarked.Process
*weakly-reachableobjectsdiscoveredwhiletracing.
*/
dvmHeapProcessReferences(&gcHeap->softReferences,
spec->doPreserve==false,
&gcHeap->weakReferences,
&gcHeap->finalizerReferences,
&gcHeap->phantomReferences);
#ifdefined(WITH_JIT)
/*
*Patchingachainingcellisverycheapasitonlyupdates4words.It's
*theoverheadofstoppingallthreadsandsynchronizingtheI/Dcache
*thatmakesitexpensive.
*
*Thereforewebatchthoseworkordersinaqueueandgothroughthem
*whenthreadsaresuspendedforGC.
*/
dvmCompilerPerformSafePointChecks();
#endif
LOGD_HEAP("Sweeping...");
dvmHeapSweepSystemWeaks();
/*
*Liveobjectshaveabitsetinthemarkbitmap,swapthemark
*andlivebitmaps.Thesweepcanproceedconcurrentlyviewing
*thenewlivebitmapastheoldmarkbitmap,andviceversa.
*/
dvmHeapSourceSwapBitmaps();
.............................
..................................
}
[/code]

再看看GC_BEFORE_OOM:

staticconstGcSpeckGcBeforeOomSpec={
false,/*isPartial*/
false,/*isConcurrent*/
false,/*doPreserve为false*/
"GC_BEFORE_OOM"
};
constGcSpec*GC_BEFORE_OOM=&kGcBeforeOomSpec;
[/code]

看完上面代码,基本了解了为什么说“所有的SoftReference必定会在OOM发生前被回收”。

原因是:当进程不断申请内存,如果一直申请不到(尝试了多次,Heap大小已经不能再增长),那么dalvik虚拟机会触发GC_BEFORE_OOM类似的回收方式,触发这种类型GC,会保证所有SoftReference引用的对象,都会被回收掉。





Conclusions



至此,三个SoftReference的谜团终于解开,至于为什么Fresco这样做,个人猜想是,Fresco希望尽量自己管理内存的分配和释放,所以要防止对象被回收掉,避免重新分配内存,起到缓存池的作用。那为什么不使用strongreference,因为在自己管理的同时可以保证在系统内存资源紧张时,能够依赖GC,释放掉SoftReference引用对象的内存,避免真的发生OOM。

对于Art部分的回收机制,这里就不在深入,基本差不多,有兴趣的自行深究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 内存 fresco