本文介紹garbageCollectForReal方法中的第二步–markNonRootObjects.
該方法的功能是掃描堆中存活對象,將其所引用的對象標記爲存活.
該方法使用了尾遞歸的方式掃描整個堆.同時爲了防止遞歸次數過多,設置了閥值.最多遞歸次數爲MAX_GC_DEPTH(4).這樣,不管對象多少,則一定會處理完畢的.
markNonRootObjects的代碼如下:
static void
markNonRootObjects(void) {
/* Scan the entire heap, looking for badly formed headers */
cell* scanner;
cell* endScanPoint = CurrentHeapEnd;
int scans = 0;
do {
WeakPointers = NULL;
WeakReferences = NULL;
initializeDeferredObjectTable(); // 初始化table
for (scanner = CurrentHeap;
scanner < endScanPoint;
scanner += SIZE(*scanner) + HEADERSIZE) {
if (ISMARKED(*scanner)) {// 如果該對象是存活的
cell *object = scanner + 1; // 獲得對象指針
/* See markChildren() for comments on the arguments */
markChildren(object, object, MAX_GC_DEPTH);
}
}
if (ENABLEPROFILING) {
scans++;
}
} while (deferredObjectTableOverflow); // 如果存在溢出現象,則重新進行掃描
#if ENABLEPROFILING
GarbageCollectionRescans += (scans - 1);
#endif
}
其中initializeDeferredObjectTable方法如下:
#define DEFERRED_OBJECT_TABLE_SIZE 40
static cell *deferredObjectTable[DEFERRED_OBJECT_TABLE_SIZE]; // 表的長度爲40
#define endDeferredObjectTable (deferredObjectTable + DEFERRED_OBJECT_TABLE_SIZE)
static cell **startDeferredObjects, **endDeferredObjects;
static int deferredObjectCount;
static int deferredObjectTableOverflow;
static void initializeDeferredObjectTable(void) {
startDeferredObjects = endDeferredObjects = deferredObjectTable; // 重置表
deferredObjectCount = 0; // 表中對象的個數
deferredObjectTableOverflow = FALSE; // 是否存在表溢出
}
markChildren使用了尾遞歸,此處使用瞭如下技巧:
- 它只遞歸地標記堆中位置小於“limit”值的對象。它標記但不在“超過限制”的對象上遞歸,因爲marknonrootobjects()最終將到達這些對象
- 變量nextobject是在我們認爲或希望只需要遵循當前節點的一個子節點的情況下設置的。
- 只允許迭代達到有限的深度即MAX_GC_DEPTH。如果深度超過了這個值,我們將子對象保存在deferredObjectTable中,然後在迭代次數爲MAX_GC_DEPTH - 1時,處理deferredObjectTable。
- 如果deferredObjectTable已滿,則重新進行掃描。(即回到markNonRootObjects中,重新進行掃描)
其代碼如下:
static void
markChildren(cell* object, cell* limit, int remainingDepth)
{
cell *heapSpace = CurrentHeap;
cell *heapSpaceEnd = CurrentHeapEnd;
#define MARK_AND_RECURSE(child) \
if (inHeapSpaceFast(child)) { \
cell _tmp_ = OBJECT_HEADER(child); \
if (!ISKEPT(_tmp_)) { \
OBJECT_HEADER(child) = _tmp_ | MARKBIT; \
if ((cell*)child < limit) { \
RECURSE((cell *)child); \
} \
} \
}
#define MARK_AND_TAIL_RECURSE(child) \
if (inHeapSpaceFast(child)) { \
cell _tmp_ = OBJECT_HEADER(child); \
if (!ISKEPT(_tmp_)) { \
OBJECT_HEADER(child) = _tmp_ | MARKBIT; \
if ((cell*)child < limit) { \
if (nextObject != NULL) { \
RECURSE(nextObject); \
} \
nextObject = (cell *)(child); \
} \
} \
}
#define MARK_AND_TAIL_RECURSEX(child) \
if (inHeapSpaceFast(child)) { \
cell _tmp_ = OBJECT_HEADER(child); \
if (!ISKEPT(_tmp_)) { \
OBJECT_HEADER(child) = _tmp_ | MARKBIT; \
if ((cell*)child < limit) { \
nextObject = (cell *)(child); \
} \
} \
}
#define RECURSE(child) \
if (remainingDepth < 0) { \
putDeferredObject(child); \
} else { \
markChildren(child, limit, remainingDepth); \
}
/*
* 如果非空,那麼它將保存對象的值,以便在循環中進行下一次迭代。用於實現尾部遞歸。
*/
cell *nextObject = NULL;
remainingDepth -= 1; /* remaining depth for any subcalls */
for (;;) {
cell *header = object - HEADERSIZE; // 獲得該對象的對象頭
GCT_ObjectType gctype = TYPE(*header);
#if INCLUDEDEBUGCODE
if (tracegarbagecollectionverbose) {
fprintf(stdout, "GC Mark: ");
printObject(object);
}
#endif
switch (gctype) {
int length;
cell **ptr;
case GCT_INSTANCE: {
/* The object is a Java object instance. Mark pointer fields */
INSTANCE instance = (INSTANCE)object;
INSTANCE_CLASS clazz = instance->ofClass;
/* Mark the possible monitor object alive 標記該對象的monitor*/
checkMonitorAndMark((OBJECT)instance);
while (clazz) {
/* This is the tough part: walk through all the fields */
/* of the object and see if they contain pointers 遍歷字段,只處理指針*/
FOR_EACH_FIELD(thisField, clazz->fieldTable)
/* Is this a non-static pointer field? */
if ((thisField->accessFlags & (ACC_POINTER | ACC_STATIC))
== ACC_POINTER) {
int offset = thisField->u.offset;
cell* subobject = instance->data[offset].cellp;
MARK_AND_TAIL_RECURSE(subobject);
}
END_FOR_EACH_FIELD
clazz = clazz->superClass;
}
break;
}
case GCT_ARRAY:
/* The object is a Java array with primitive values. */
checkMonitorAndMark((OBJECT)object);
break;
case GCT_POINTERLIST: {
POINTERLIST list = (POINTERLIST)object;
length = list->length;
ptr = &list->data[0].cellp;
goto markArray;
}
case GCT_WEAKPOINTERLIST:
// 加入到WeakPointers鏈表中,在garbageCollectForReal 中的第3步處理
((WEAKPOINTERLIST)object)->gcReserved = WeakPointers;
WeakPointers = (WEAKPOINTERLIST)object;
break;
case GCT_OBJECTARRAY:
/* The object is a Java array with object references. */
checkMonitorAndMark((OBJECT)object);
length = ((ARRAY)object)->length;
ptr = &((ARRAY)object)->data[0].cellp;
/* FALL THROUGH */
markArray:
/* Keep objects in the array alive. */
while (--length >= 0) {
cell *subobject = *ptr++;
MARK_AND_TAIL_RECURSE(subobject);
}
break;
/* Added for java.lang.ref.WeakReference support in CLDC 1.1 */
case GCT_WEAKREFERENCE: {
/* Mark the possible monitor object alive */
checkMonitorAndMark((OBJECT)object);
// 將當前對應所引用的對應 加入到WeakReferences鏈表中,並在在garbageCollectForReal 中的第4步處理
((WEAKREFERENCE)object)->gcReserved = WeakReferences;
WeakReferences = (WEAKREFERENCE)object;
break;
}
case GCT_METHODTABLE:
// 處理方法表,此類型只在使用static區域時才生效
FOR_EACH_METHOD(thisMethod, ((METHODTABLE)object))
if ((thisMethod->accessFlags & ACC_NATIVE) == 0) {
MARK_OBJECT(thisMethod->u.java.code);
MARK_OBJECT_IF_NON_NULL(thisMethod->u.java.handlers);
}
END_FOR_EACH_METHOD
break;
case GCT_MONITOR:
// 此類型對象,已經在其他地方處理過了
break;
case GCT_THREAD:
// 此類型對象,在garbageCollectForReal中的第1步處理過了
break;
case GCT_NOPOINTERS:
// 此類型對象不含有任何指針,不需要處理
break;
case GCT_EXECSTACK:
// 此類型對象,在garbageCollectForReal中的第1步處理過了
break;
default:
// 其他類型,則拋出異常
fatalVMError(KVM_MSG_BAD_DYNAMIC_HEAP_OBJECTS_FOUND);
} /* End of switch statement */
if (nextObject != NULL) {
/* 此處進行尾遞歸 */
object = nextObject;
nextObject = NULL;
/* continue */
} else if (remainingDepth == MAX_GC_DEPTH - 1
&& deferredObjectCount > 0) {
object = getDeferredObject();
/* continue */
} else {
break; /* finish "for" loop. */
}
} /* end of for(ever) loop */
}
在該方法內部,定義了多個宏,但是在該方法中,只使用了MARK_AND_TAIL_RECURSE,RECURSE.
此處有個問題: 如果迭代次數過多,則會在markNonRootObjects中重新掃描整個堆,不會存在重複標記的現象嗎?
答案是:在markChildren方法中,標記的時候,已經判斷了對象是否標記過,如果不標記,才進行標記.不存在重複標記的現象.
通過看這個代碼,可以看到,此算法是比較耗時的.但是由於kvm是運行在小內存設備上,最大支持到64M,因此也就不是特別慢(該算法用在jdk上,可以說是很慢了…)。