Android Application에서 ‘new’ 를 이용해서 Object를 생성할 경우 이를 위한 Heap allocation이 일어나는 과정을 설명한다. 할당에 실패할 경우를 함께 살펴봄으로써 Out-Of-Memory Exception이 발생하는 과정 또한 알아보도록 하자.
dalvik/vm/mterp/armv5te/OP_NEW_INSTANCE.S
새로운 Java Object/Class를 생성할 경우 interpreter에 의해서 호출되는 ARMv5te용 어셈블리 루틴이다. Class를 초기화 한 이후에 dvmMalloc의 인자로 들어갈 ALLOC_DONT_TRACK flag를 설정하여 아래의 dvmAllocObject를 호출하게 된다. 여기서 말하는 ALLOC_DONT_TRACK flag는 메모리 할당 루틴에서 내부적으로 관리하는 리스트에 굳이 추가하지 않아도 된다는 것을 의미하며 GC 대상에서 제외함을 의미하는 것은 아니다.cmp r1, #CLASS_INITIALIZED @ has class been initialized?
bne .L${opcode}_needinit @ no, init class now
.L${opcode}_initialized: @ r0=class
mov r1, #ALLOC_DONT_TRACK @ flags for alloc call
bl dvmAllocObject @ r0<- new object
- dvmMalloc의 flag
- ALLOC_DEFAULT
- ALLOC_NO_GC
- ALLOC_DONT_TRACK
- ALLOC_FINALIZABLE
Object* dvmAllocObject(ClassObject* clazz, int flags)
이번 Allocation에 대한 설명은 Class Object를 생성하는 경우이므로 이에 대한 몇 가지 특별한 처리가 필요하다.우선 Class가 초기화 되었거나 초기화 중 인지를 검사하는 등의 몇 가지 Sanity Check를 수행한다. 그 후에는 ALLOC_FINALIABLE flag를 추가로 설정하여 다음에 설명할 dvmMalloc을 호출한다. 여기서 설정된 flag는 추후 GC가 수행될 때 반드시 finalize() method가 호출되어야 함을 명시하는 것이다.
할당에 성공했을 경우 Class Object의 메모리 할당을 추적하기 위한 몇 가지 정보를 추가로 기록하게 된다.
newObj = dvmMalloc(clazz->objectSize, flags);
이 함수는 더 이상 Object나 Class 단위의 메모리 할당과는 관련이 없음에 주목하자. 실질적인 메모리 할당이 이뤄지는 과정 만을 놓고 본다면 이 함수에서 호출되는 tryMalloc(size) 함수 (밑에서 설명)를 살펴봐야 하지만 전달 인자 중 flag에 대한 처리는 이 함수 내에서 마무리가 된다.우선 Object가 ALLOC_FINALIZABLE인 경우 GcHeap구조체에 존재하는 Object 목록에 추가해준다. 여기서 hc->data는 tryMalloc을 통해서 반환 받은 메모리를 가리키는 포인터이다.
if ((flags & ALLOC_FINALIZABLE) != 0) {
if (!dvmHeapAddRefToLargeTable(&gcHeap->finalizableRefs,
(Object *)hc->data))
또한 NO_GC가 설정된 경우도 GcHeap의 관련 목록에 추가하게 된다.
ptr = hc->data;
if ((flags & ALLOC_NO_GC) != 0) {
if (!dvmHeapAddToHeapRefTable(&gcHeap->nonCollectableRefs, ptr)) {
마지막으로 내부 관리를 위한 메모리 할당 리스트에 추가한다. 하지만 Class Object의 경우 ALLOC_DONT_TRACK이 켜져 있으므로 실제로는 추가되지 않을 것이다.
if ((flags & (ALLOC_DONT_TRACK | ALLOC_NO_GC)) == 0) {
dvmAddTrackedAlloc(ptr, NULL);
지금까지는 tryMalloc이 성공적으로 수행될 경우이며, 만약 실패한다면 가용 메모리가 없음을 의미하므로 OutOfMemory Exception을 던지며 메모리 할당의 모든 과정이 종료된다.
if (ptr == NULL)
throwOOME();
static DvmHeapChunk *tryMalloc(size_t size)
가정 먼저 하는 일은 요청하는 메모리 크기가 과도하게 큰 경우를 걸러내는 것이다. heapSizeMax는 현재 16MB로 되어 있으며 이 크기가 한번에 할당 가능한 최대 Heap Object의 크기라고 보면 된다. 만약 이 크기를 넘어설 경우 NULL을 반환하여 결국 Out-Of-Memory Exception을 발생시키게 되는데, 그 전에 SoftReference를 모두 회수하는 과정을 수행하고 있다. SoftReference를 수행하는 것은 반드시 수행해야하는 일은 아니지만 Dalvik 개발자는 이 시점이 메모리를 정리할 시기라고 판단한 것으로 보인다.if (size >= gDvm.heapSizeMax) {
LOGW_HEAP("dvmMalloc(%zu/0x%08zx): "
"someone's allocating a huge buffer\n", size, size);
hc = NULL;
goto collect_soft_refs;
}
이제 본격적으로 메모리 할당에 들어간다. 여기서 원래 요청 크기에 DvmHeapChunk의 크기를 더해서 할당을 요청하는데 이는 DvmHeapChunk내의 Profile 정보를 위한 것이며 별도로 설정하지 않는다면 이 부분은 없는 것이 기본이다.
hc = dvmHeapSourceAlloc(size + sizeof(DvmHeapChunk));
if (hc != NULL) {
return hc;
}
dvmHeapSourceAlloc의 내부를 잠시 살펴 보면 다음과 같다. 즉, 현재 할당된 크기에 새로 할당할 크기를 더한 값이 softLimit를 넘지 않는다면 mspace_calloc을 이용해서 메모리를 할당한다. mspace 관련 내용은 앞서 설명했으므로 추가 설명은 하지 않기로 한다.
if (heap->bytesAllocated + n <= hs->softLimit) {
ptr = mspace_calloc(heap->msp, 1, n);
}
만약 softLimit를 넘어선다면 어떻게 될까? 이 경우 GC를 수행하고 같은 방법으로 다시 한번 메모리 할당을 시도한다. gcForMalloc의 인자는 Soft Reference에 대해서 GC를 수행할 것인지를 알려주는 것인데 이번 호출에서는 우선 포함하지 않는 것으로 진행하고 지속적으로 메모리 할당에 실패할 경우에만 Soft Reference를 대상으로 GC를 하게 된다.
gcForMalloc(false);
hc = dvmHeapSourceAlloc(size + sizeof(DvmHeapChunk));
if (hc != NULL) {
return hc;
}
GC를 수행한 이후에도 또 다시 실패한다면 이제는 진정 Heap 크기를 늘려야 할 상황이다.
hc = dvmHeapSourceAllocAndGrow(size + sizeof(DvmHeapChunk));
앞서 수행한 dvmHeapSourceAlloc과의 차이는 다음과 같이 Heap의 크기를 늘리고 나서 할당을 다시 시도한다는 것이다.
max = heap->absoluteMaxSize;
max -= hs->externalBytesAllocated;
mspace_set_max_allowed_footprint(heap->msp, max);
ptr = dvmHeapSourceAlloc(n);
여기까지 수행을 하면 대부분의 경우는 할당에 성공하게 된다. 하지만 이런 노력에도 불구하고 메모리 할당에 실패한다면 Out-Of-Memory Exception을 발생시키기 전에 Soft Reference를 모두 정리하여 메모리 회수에 들어가게 된다. 이는 Java VM 규격에 명시된 바이기도 한다.
gcForMalloc(true);
hc = dvmHeapSourceAllocAndGrow(size + sizeof(DvmHeapChunk));
Add a comment