Dalvik虚拟机垃圾收集GC过程分析分解.docx
- 文档编号:7327759
- 上传时间:2023-01-23
- 格式:DOCX
- 页数:63
- 大小:104.97KB
Dalvik虚拟机垃圾收集GC过程分析分解.docx
《Dalvik虚拟机垃圾收集GC过程分析分解.docx》由会员分享,可在线阅读,更多相关《Dalvik虚拟机垃圾收集GC过程分析分解.docx(63页珍藏版)》请在冰豆网上搜索。
Dalvik虚拟机垃圾收集GC过程分析分解
Dalvik虚拟机垃圾收集(GC)过程分析
前面我们分析了Dalvik虚拟机堆的创建过程,以及Java对象在堆上的分配过程。
这些知识都是理解Dalvik虚拟机垃圾收集过程的基础。
垃圾收集是一个复杂的过程,它要将那些不再被引用的对象进行回收。
一方面要求Dalvik虚拟机能够标记出哪些对象是不再被引用的。
另一方面要求Dalvik虚拟机尽快地回收内存,避免应用程序长时间停顿。
本文就将详细分析Dalvik虚拟机是如何解决上述问题完成垃圾收集过程的。
Dalvik虚拟机使用Mark-Sweep算法来进行垃圾收集。
顾名思义,Mark-Sweep算法就是为Mark和Sweep两个阶段进行垃圾回收。
其中,Mark阶段从根集(RootSet)开始,递归地标记出当前所有被引用的对象,而Sweep阶段负责回收那些没有被引用的对象。
在分析Dalvik虚拟机使用的Mark-Sweep算法之前,我们先来了解一下什么情况下会触发GC。
Dalvik虚拟机在三种情况下会触发四种类型的GC。
每一种类型GC使用一个GcSpec结构体来描述,它的定义如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
structGcSpec{
/*Iftrue,onlytheapplicationheapisthreatened.*/
boolisPartial;
/*Iftrue,thetraceisrunconcurrentlywiththemutator.*/
boolisConcurrent;
/*Togglesforthesoftreferenceclearingpolicy.*/
booldoPreserve;
/*Anameforthisgarbagecollectionmode.*/
constchar*reason;
};
这个结构体定义在文件dalvik/vm/alloc/Heap.h中。
GcSpec结构体的各个成员变量的含义如下所示:
isPartial:
为true时,表示仅仅回收Active堆的垃圾;为false时,表示同时回收Active堆和Zygote堆的垃圾。
isConcurrent:
为true时,表示执行并行GC;为false时,表示执行非并行GC。
doPreserve:
为true时,表示在执行GC的过程中,不回收软引用引用的对象;为false时,表示在执行GC的过程中,回收软引用引用的对象。
reason:
一个描述性的字符串。
Davlik虚拟机定义了四种类的GC,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
/*Notenoughspaceforan"ordinary"Objecttobeallocated.*/
externconstGcSpec*GC_FOR_MALLOC;
/*AutomaticGCtriggeredbyexceedingaheapoccupancythreshold.*/
externconstGcSpec*GC_CONCURRENT;
/*ExplicitGCviaRuntime.gc(),VMRuntime.gc(),orSIGUSR1.*/
externconstGcSpec*GC_EXPLICIT;
/*FinalattempttoreclaimmemorybeforethrowinganOOM.*/
externconstGcSpec*GC_BEFORE_OOM;
这四个全局变量声明在文件dalvik/vm/alloc/Heap.h中。
它们的含义如下所示:
GC_FOR_MALLOC:
表示是在堆上分配对象时内存不足触发的GC。
GC_CONCURRENT:
表示是在已分配内存达到一定量之后触发的GC。
GC_EXPLICIT:
表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
GC_BEFORE_OOM:
表示是在准备抛OOM异常之前进行的最后努力而触发的GC。
实际上,GC_FOR_MALLOC、GC_CONCURRENT和GC_BEFORE_OOM三种类型的GC都是在分配对象的过程触发的。
在前面一文,我们提到,Dalvik虚拟机在Java堆上分配对象的时候,在碰到分配失败的情况,会尝试调用函数gcForMalloc进行垃圾回收。
函数gcForMalloc的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticvoidgcForMalloc(boolclearSoftReferences)
{
......
constGcSpec*spec=clearSoftReferences?
GC_BEFORE_OOM:
GC_FOR_MALLOC;
dvmCollectGarbageInternal(spec);
}
这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。
参数clearSOftRefereces表示是否要对软引用引用的对象进行回收。
如果要对软引用引用的对象进行回收,那么就表明当前内存是非常紧张的了,因此,这时候执行的就是GC_BEFORE_OOM类型的GC。
否则的话,执行的就是GC_FOR_MALLOC类型的GC。
它们都是通过调用函数dvmCollectGarbageInternal来执行的。
在前面一文,我们也提到,当Dalvik虚拟机成功地在堆上分配一个对象之后,会检查一下当前分配的内存是否超出一个阀值,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
void*dvmHeapSourceAlloc(size_tn)
{
......
HeapSource*hs=gHs;
Heap*heap=hs2heap(hs);
if(heap->bytesAllocated+n>hs->softLimit){
......
returnNULL;
}
void*ptr;
if(gDvm.lowMemoryMode){
......
ptr=mspace_malloc(heap->msp,n);
......
}else{
ptr=mspace_calloc(heap->msp,1,n);
......
}
countAllocation(heap,ptr);
......
if(heap->bytesAllocated>heap->concurrentStartBytes){
......
dvmSignalCond(&gHs->gcThreadCond);
}
returnptr;
}
这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。
函数dvmHeapSourceAlloc成功地在Active堆上分配到一个对象之后,就会检查Active堆当前已经分配的内存(heap->bytesAllocated)是否大于预设的阀值(heap->concurrentStartBytes)。
如果大于,那么就会通过条件变量gHs->gcThreadCond唤醒GC线程进行垃圾回收。
预设的阀值(heap->concurrentStartBytes)是一个比指定的堆最小空闲内存小128K的数值。
也就是说,当堆的空闲内不足时,就会触发GC_CONCURRENT类型的GC。
GC线程是Dalvik虚拟机启动的过程中创建的,它的执行体函数是gcDaemonThread,实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticvoid*gcDaemonThread(void*arg)
{
dvmChangeStatus(NULL,THREAD_VMWAIT);
dvmLockMutex(&gHs->gcThreadMutex);
while(gHs->gcThreadShutdown!
=true){
booltrim=false;
if(gHs->gcThreadTrimNeeded){
intresult=dvmRelativeCondWait(&gHs->gcThreadCond,&gHs->gcThreadMutex,
HEAP_TRIM_IDLE_TIME_MS,0);
if(result==ETIMEDOUT){
/*TimedoutwaitingforaGCrequest,scheduleaheaptrim.*/
trim=true;
}
}else{
dvmWaitCond(&gHs->gcThreadCond,&gHs->gcThreadMutex);
}
......
dvmLockHeap();
if(!
gDvm.gcHeap->gcRunning){
dvmChangeStatus(NULL,THREAD_RUNNING);
if(trim){
trimHeaps();
gHs->gcThreadTrimNeeded=false;
}else{
dvmCollectGarbageInternal(GC_CONCURRENT);
gHs->gcThreadTrimNeeded=true;
}
dvmChangeStatus(NULL,THREAD_VMWAIT);
}
dvmUnlockHeap();
}
dvmChangeStatus(NULL,THREAD_RUNNING);
returnNULL;
}
这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。
GC线程平时没事的时候,就在条件变量gHs->gcThreadCond上进行等待HEAP_TRIM_IDLE_TIME_MS毫秒(5000毫秒)。
如果在HEAP_TRIM_IDLE_TIME_MS毫秒内,都没有得到执行GC的通知,那么它就调用函数trimHeaps对Java堆进行裁剪,以便可以将堆上的一些没有使用到的内存交还给内核。
函数trimHeaps的实现可以参考前面一文。
否则的话,就会调用函数dvmCollectGarbageInternal进行类型为GC_CONCURRENT的GC。
注意,函数gcDaemonThread在调用函数dvmCollectGarbageInternal进行类型为GC_CONCURRENT的GC之前,会先调用函数dvmLockHeap来锁定堆的。
等到GC执行完毕,再调用函数dvmUnlockHeap来解除对堆的锁定。
这与函数gcForMalloc调用函数dvmCollectGarbageInternal进行类型为GC_FOR_MALLOC和GC_CONCURRENT的GC是一样的。
只不过,对堆进行锁定和解锁的操作是在调用堆栈上的函数dvmMalloc进行的,具体可以参考前面一文。
当应用程序调用System.gc、VMRuntime.gc接口,或者接收到SIGUSR1信号时,最终会调用到函数dvmCollectGarbage,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voiddvmCollectGarbage()
{
if(gDvm.disableExplicitGc){
return;
}
dvmLockHeap();
dvmWaitForConcurrentGcToComplete();
dvmCollectGarbageInternal(GC_EXPLICIT);
dvmUnlockHeap();
}
这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中。
如果Davik虚拟机在启动的时候,通过-XX:
+DisableExplicitGC选项禁用了显式GC,那么函数dvmCollectGarbage什么也不做就返回了。
这意味着Dalvik虚拟机可能会不支持应用程序显式的GC请求。
一旦Dalvik虚拟机支持显式GC,那么函数dvmCollectGarbage就会先锁定堆,并且等待有可能正在执行的GC_CONCURRENT类型的GC完成之后,再调用函数dvmCollectGarbageInternal进行类型为GC_EXPLICIT的GC。
以上就是三种情况下会触发四种类型的GC。
有了这个背景知识之后,我们接下来就开始分析Dalvik虚拟机使用的Mark-Sweep算法,整个过程如图1所示:
图1Dalvik虚拟机垃圾收集过程
Dalvik虚拟机支持非并行和并行两种GC。
在图1中,左边是非并行GC的执行过程,而右边是并行GC的执行过程。
它们的总体流程是相似的,主要差别在于前者在执行的过程中一直是挂起非GC线程的,而后者是有条件地挂起非GC线程。
由前面的分析可以知道,无论是并行GC,还是非并行GC,它们都是通过函数dvmCollectGarbageInternal来执行的。
函数dvmCollectGarbageInternal的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voiddvmCollectGarbageInternal(constGcSpec*spec)
{
......
if(gcHeap->gcRunning){
......
return;
}
......
gcHeap->gcRunning=true;
......
dvmSuspendAllThreads(SUSPEND_FOR_GC);
......
if(!
dvmHeapBeginMarkStep(spec->isPartial)){
......
dvmAbort();
}
dvmHeapMarkRootSet();
......
if(spec->isConcurrent){
......
dvmClearCardTable();
dvmUnlockHeap();
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
dvmHeapScanMarkedObjects();
if(spec->isConcurrent){
......
dvmLockHeap();
......
dvmSuspendAllThreads(SUSPEND_FOR_GC);
......
dvmHeapReMarkRootSet();
......
dvmHeapReScanMarkedObjects();
}
dvmHeapProcessReferences(&gcHeap->softReferences,
spec->doPreserve==false,
&gcHeap->weakReferences,
&gcHeap->finalizerReferences,
&gcHeap->phantomReferences);
......
dvmHeapSweepSystemWeaks();
......
dvmHeapSourceSwapBitmaps();
.......
if(spec->isConcurrent){
dvmUnlockHeap();
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
dvmHeapSweepUnmarkedObjects(spec->isPartial,spec->isConcurrent,
&numObjectsFreed,&numBytesFreed);
......
dvmHeapFinishMarkStep();
if(spec->isConcurrent){
dvmLockHeap();
}
......
dvmHeapSourceGrowForUtilization();
......
gcHeap->gcRunning=false;
......
if(spec->isConcurrent){
......
dvmBroadcastCond(&gDvm.gcHeapCond);
}
if(!
spec->isConcurrent){
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
.......
dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);
......
}
这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。
前面提到,在调用函数dvmCollectGarbageInternal之前,堆是已经被锁定了的,因此这时候任何需要堆上分配对象的线程都会被挂起。
注意,这不会影响到那些不需要在堆上分配对象的线程。
在图1中显示的GC流程中,除了第一步的LockHeap和最后一步的UnlockHeap,都对应于函数dvmCollectGarbageInternal的实现,它的执行逻辑如下所示:
第1步到第3步用于并行和非并行GC:
1.调用函数dvmSuspendAllThreads挂起所有的线程,以免它们干扰GC。
2.调用函数dvmHeapBeginMarkStep初始化MarkStack,并且设定好GC范围。
关于MarkStack的介绍,可以参考前面一文。
3.调用函数dvmHeapMarkRootSet标记根集对象。
第4到第6步用于并行GC:
4.调用函数dvmClearCardTable清理CardTable。
关于CardTable的介绍,可以参考前面一文。
因为接下来我们将会唤醒第1步挂起的线程。
并且使用这个CardTable来记录那些在GC过程中被修改的对象。
5.调用函数dvmUnlock解锁堆。
这个是针对调用函数dvmCollectGarbageInternal执行GC前的堆锁定操作。
6.调用函数dvmResumeAllThreads唤醒第1步挂起的线程。
第7步用于并行和非并行GC:
7.调用函数dvmHeapScanMarkedObjects从第3步获得的根集对象开始,归递标记所有被根集对象引用的对象。
第8步到第11步用于并行GC:
8.调用函数dvmLockHeap重新锁定堆。
这个是针对前面第5步的操作。
9.调用函数dvmSuspendAllThreads重新挂起所有的线程。
这个是针对前面第6步的操作。
10.调用函数dvmHeapReMarkRootSet更新根集对象。
因为有可能在第4步到第6步的执行过程中,有线程创建了新的根集对象。
11.调用函数dvmHeapReScanMarkedObjects归递标记那些在第4步到第6步的执行过程中被修改的对象。
这些对象记录在CardTable中。
第12步到第14步用于并行和非并行GC:
12.调用函数dvmHeapProcessReferences处理那些被软引用(SoftReference)、弱引用(WeakReference)和影子引用(PhantomReference)引用的对象,以及重写了finalize方法的对象。
这些对象都是需要特殊处理的。
13.调用函数dvmHeapSweepSystemWeaks回收系统内部使用的那些被弱引用引用的对象。
14.调用函数dvmHeapSourceSwapBitmaps交换LiveBitmap和MarkBitmap。
执行了前面的13步之后,所有还被引用的对象在MarkBitmap中的bit都被设置为1。
而LiveBitmap记录的是当前GC前还被引用着的对象。
通过交换这两个Bitmap,就可以使得当前GC完成之后,使得LiveBitmap记录的是下次GC前还被引用着的对象。
第15步和第16步用于并行GC:
15.调用函数dvmUnlock解锁堆。
这个是针对前面第8步的操作。
16.调用函数dvmResumeAllThreads唤醒第9步挂起的线程。
第17步和第18步用于并行和非并行GC:
17.调用函数dvmHeapSweepUnmarkedObjects回收那些没有被引用的对象。
没有被引用的对象就是那些在执行第14步之前,在LiveBitmap中的bit设置为1,但是在MarkBitmap中的bit设置为0的对象。
18.调用函数dvmHeapFinishMarkStep重置MarkBitmap以及MarkStack。
这个是针对前面第2步的操作。
第19步用于并行GC:
19.调用函数dvmLockHeap重新锁定堆。
这个是针对前面第15步的操作。
第20步用于并行和非并行GC:
20.调用函数dvmHeapSourceGrowForUtilization根据设置的堆目标利用率调整堆的大小。
第21步用于并行GC:
21.调用函数dvmBroadcastCond唤醒那些等待GC执行完成再在堆上分配对象的线程。
第22步用于非并行GC:
22.调用函数dvmResumeAllThreads唤醒第1步挂起的线程。
第23步用到并行和非并行GC:
23.调用函数dvmEnqueueClearedReferences将那些目标对象已经被回收了的引用对象增加到相应的Java队列中去,以便应用程序可以知道哪些引用引用的对象已经被回收了。
以上就是并行和非并行GC的执行总体流程,它们的主要区别在于,前者在GC过程中,有条件地挂起和唤醒非GC线程,而后者在执行GC的过程中,一直都是挂起非GC线程的。
并行GC通过有条件地挂起和唤醒非GC线程,就可以使得应用程序获得更好的响应性。
但是我们也应该看到,并行GC需要多执行一次标记根集对象以及递归标记那些在GC过程被访问
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Dalvik 虚拟机 垃圾 收集 GC 过程 分析 分解
![提示](https://static.bdocx.com/images/bang_tan.gif)