COW策略
一开始的时候(未复制Zygote进程的地址空间的时候),应用程序进程和Zygote进程共享了同一个用来分配对象的堆。当Zygote进程或者应用程序进程对该堆进行写操作时,内核就会执行真正的拷贝操作,使得Zygote进程和应用程序进程分别拥有自己的一份拷贝,这就是所谓的COW。因为copy是十分耗时的,所以必须尽量避免copy或者尽量少的copy。
和GC有关的一些指标
在启动Dalvik虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimit三个选项来指定上述三个值,以上三个值分别表示表示:
- Starting Size: Dalvik虚拟机启动的时候,会先分配一块初始的堆内存给虚拟机使用。
- Growth Limit:是系统给每一个程序的最大堆上限,超过这个上限,程序就会OOM
- Maximum Size:不受控情况下的最大堆内存大小,起始就是我们在用largeheap属性的时候,可以从系统获取的最大堆大小
同时除了上面的这个三个指标外,还有几个指标也是值得我们关注的,那就是堆最小空闲值(Min Free)、堆最大空闲值(Max Free)和堆目标利用率(Target Utilization)。假设在某一次GC之后,存活对象占用内存的大小为LiveSize,那么这时候堆的理想大小应该为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree),每次GC后垃圾回收器都会尽量让堆的利用率往目标利用率靠拢。所以当我们尝试手动去生成一些几百K的对象,试图去扩大可用堆大小的时候,反而会导致频繁的GC,因为这些对象的分配会导致GC,而GC后会让堆内存回到合适的比例,而我们使用的局部变量很快会被回收理论上存活对象还是那么多,我们的堆大小也会缩减回来无法达到扩充的目的。 与此同时这也是产生CONCURRENT GC的一个因素。
GC的类型
- GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。
- GC_CONCURRENT: 当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。
- GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
- GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。
GC类型 | isPartial | isConcurrent | doPreserve |
---|---|---|---|
GC_FOR_MALLOC | Active | 非并行 | 不回收软引 |
GC_CONCURRENT | Active | 并行 | 不回收软引 |
GC_EXPLICIT | Active&Zygote | 并行 | 不回收软引 |
GC_BEFORE_OOM | Active&Zygote | 非并行 | 回收软引 |
对象的分配和GC触发时机
在对象的分配中会导致GC,第一次分配对象失败我们会触发GC但是不回收Soft的引用,如果再次分配还是失败我们就会将Soft的内存也给回收,前者触发的GC是GC_FOR_MALLOC类型的GC,后者是GC_BEFORE_OOM类型的GC。而当内存分配成功后,我们会判断当前的内存占用是否是达到了GC_CONCURRENT的阀值,如果达到了那么又会触发GC_CONCURRENT。
Java堆创建过程分析
- Alloc.cpp: dvmGcStartup
bool dvmGcStartup()
{
dvmInitMutex(&gDvm.gcHeapLock);
pthread_cond_init(&gDvm.gcHeapCond, NULL);
return dvmHeapStartup();
}
- Heap.cpp: dvmHeapStartup
bool dvmHeapStartup()
{
GcHeap *gcHeap;
gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize,
gDvm.heapMaximumSize,
gDvm.heapGrowthLimit);
if (gcHeap == NULL) {
return false;
}
gDvm.gcHeap = gcHeap;
if (!dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit)) {
return false;
}
return true;
}
创建对象分配内存的过程
- Alloc.cpp: dvmAllocObject
Object* dvmAllocObject(ClassObject* clazz, int flags)
{
Object* newObj;
newObj = (Object*)dvmMalloc(clazz->objectSize, flags);
if (newObj != NULL) {
DVM_OBJECT_INIT(newObj, clazz);
dvmTrackAllocation(clazz, clazz->objectSize); // notify DDMS
}
return newObj;
}
- Heap.cpp: dvmMalloc
void* dvmMalloc(size_t size, int flags)
{
dvmLockHeap();
ptr = tryMalloc(size);
dvmUnlockHeap();
if (ptr == NULL) throwOOME();
return ptr;
}
- Heap.cpp: tryMalloc
static void *tryMalloc(size_t size)
{
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) return ptr;
if (gDvm.gcHeap->gcRunning) {
dvmWaitForConcurrentGcToComplete();
} else {
gcForMalloc(false);
}
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) return ptr;
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) return ptr;
gcForMalloc(true);
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) return ptr;
LOGE_HEAP("Out of memory on a %zd-byte allocation.", size);
dvmDumpThread(dvmThreadSelf(), false);
return NULL;
}
GC过程
- GC_BEFORE_OOM 、GC_FOR_MALLOC
// Heap.cpp: gcForMalloc
static void gcForMalloc(bool clearSoftReferences)
{
const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
dvmCollectGarbageInternal(spec);
}
// Heap.cpp: dvmCollectGarbageInternal
void dvmCollectGarbageInternal(const GcSpec* spec)
{
gcHeap->gcRunning = true;
dvmSuspendAllThreads(SUSPEND_FOR_GC);
// 初始化Mark Stack,并且设定好GC范围
if (!dvmHeapBeginMarkStep(spec->isPartial)) {
dvmAbort();
}
dvmHeapMarkRootSet();
if (spec->isConcurrent) {
dvmResumeAllThreads(SUSPEND_FOR_GC);
}
dvmHeapScanMarkedObjects();
if (spec->isConcurrent) {
dvmSuspendAllThreads(SUSPEND_FOR_GC);
dvmHeapReMarkRootSet();
dvmHeapReScanMarkedObjects();
}
if (spec->isConcurrent) {
dvmResumeAllThreads(SUSPEND_FOR_GC);
}
// 根据设置的堆目标利用率调整堆的大小
dvmHeapSourceGrowForUtilization();
gcHeap->gcRunning = false;
if (!spec->isConcurrent) {
dvmResumeAllThreads(SUSPEND_FOR_GC);
}
}
- GC_CONCURRENT
// HeapSource.cpp: dvmHeapSourceAlloc
void* dvmHeapSourceAlloc(size_t n)
{
void* ptr = mspace_calloc(heap->msp, 1, n);
if (ptr == NULL) {
return NULL;
}
countAllocation(heap, ptr);
if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
return ptr;
}
if (heap->bytesAllocated > heap->concurrentStartBytes) {
// 超过阈值,触发GC --> gcDaemonThread
dvmSignalCond(&gHs->gcThreadCond);
}
return ptr;
}
static void *gcDaemonThread(void* arg)
{
while (gHs->gcThreadShutdown != true) {
bool trim = false;
if (gHs->gcThreadTrimNeeded) {
int result = dvmRelativeCondWait(&gHs->gcThreadCond, &gHs->gcThreadMutex,
HEAP_TRIM_IDLE_TIME_MS, 0);
if (result == ETIMEDOUT) {
/* Timed out waiting for a GC request, schedule a heap trim. */
trim = true;
}
} else {
dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex);
}
if (!gDvm.gcHeap->gcRunning) {
dvmChangeStatus(NULL, THREAD_RUNNING);
if (trim) {
trimHeaps();
gHs->gcThreadTrimNeeded = false;
} else {
dvmCollectGarbageInternal(GC_CONCURRENT);
gHs->gcThreadTrimNeeded = true;
}
}
}
return NULL;
}
GC线程平时没事的时候,就在条件变量gHs->gcThreadCond上进行等待HEAP_TRIM_IDLE_TIME_MS毫秒(5000毫秒)。如果在HEAP_TRIM_IDLE_TIME_MS毫秒内,都没有得到执行GC的通知,那么它就调用函数trimHeaps对Java堆进行裁剪,以便可以将堆上的一些没有使用到的内存交还给内核。
- GC_EXPLICIT
// Alloc.app: dvmCollectGarbage
void dvmCollectGarbage()
{
if (gDvm.disableExplicitGc) {
return;
}
dvmLockHeap();
dvmWaitForConcurrentGcToComplete();
dvmCollectGarbageInternal(GC_EXPLICIT);
dvmUnlockHeap();
}
相关资料
- Android GC 原理探究
- 老罗讲Dalvik虚拟机