Java Dalvik简要学习总结

1概要

在这里插入图片描述
Dalivik虚拟机有两个堆,包括zygote堆和Active 堆,前者是不太变化的,是从父进程公用的相同物理内存的堆,因为很少发生写操作在这个堆上,所以写时复制机制不会发生在这个堆上,这也是android运行时比较巧妙的地方。

Live Heap Bitmap 和 Mark Heap Bitmap 这两个位图是是java垃圾回收机制的关键,用来标记java对象是否被引用的, 可以映射最大堆大小的所有对象。 live heap bitmap标记表示上次垃圾回收后存活的对象,Mark heap bitmap 标记本次垃圾回被引用的对象。有了这两个信息我们就知道哪些对象是需要回收的,即在live bitmap标记为1,但在mark bitmap标记为0的对象。 (为什么需要两个bitmap呢,因为mark bitmap被标记为0的对象有可能是不存在的对象,也有可能是要回收的对象,有了live bitmap就可以进行区分)

Mark Stack就是标记过程中对象的引用关系是错综复杂的,也会有循环引用,Mark stack通过栈的方式解决这个问题。(思路就是保证优先检查地址小的对象)

如何标记对象: 首先java虚拟机使用标记清除算法,而不是引用计数方法。 这里引入根级对象概念, 根级对象是指在gc的瞬间,被全局变量,栈变量和寄存器引用的对象,试想 顺着这些对象标记下去的对象就是全部被使用的对象,不被根级对象引用的对象其实就是没有的。 这里补充下引用计数存在的问题。 比如A,B两个对象互相引用,他们的引用计数都是1,但是不被根级对象引用,这样他们俩也是无用的对象,如存在引用计数。

Card Table : 在并行gc的过程中使用, 并行gc的过程分为3步
1 挂起所有java线程,gc线程确定根级别对象。
2 唤醒java线程,同时间gc线程标记被根级对象引用的对象,但是在这个过程中java线程有可能创建或者修改对象关系,这个变化的对象地址要记录在card table中
3 gc线程标记完所有对象的时候,挂起所有java进程,再从card table中找到在过程2中发生变化的对象(少数对象)进行重新标记
所以 Card Tab对象的作用就是用于记录并行gc中发生变化的对象地址

2 Dalvik 虚拟机相关启动参数和含义

-xms: 虚拟机堆的起始大小,启动时申请的物理内存
-xms: 虚拟机堆使用的虚拟内存最大大小
-XX:HeapGrowthLimit: 减少内存碎片的方式
-XX:HeapMinFree 堆最小空闲值,空闲值小于该值时应该扩容堆(调整软限制)
-XX:HeapMaxFree 堆最大空闲值,大于该值时应该堆缩容(调整软限制)
-XX:HeapTargetUtilization 目标利用率,扩容和缩容的根据目标利用率找到合适的目标堆大小(调整软限制)
-XX:+DisableExplicitGC 禁止显式gc

堆最小空闲值(Min Free)、堆最大空闲值(Max Free)和堆目标利用率(Target Utilization)。这三个值可以分别通过Dalvik虚拟机的启动选项-XX:HeapMinFree、-XX:HeapMaxFree和-XX:HeapTargetUtilization来指定。它们用来确保每次GC之后,Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的次数。举个例子说,堆的利用率为U,最小空闲值为MinFree字节,最大空闲值为MaxFree字节。假设在某一次GC之后,存活对象占用内存的大小为LiveSize。那么这时候堆的理想大小应该为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree)。

3 创建对象的内存申请

在这里插入图片描述

static void *tryMalloc(size_t size)
{
    void *ptr;
    ......

    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;
    }
   
    ......

    return NULL;
}
    1. 调用函数dvmHeapSourceAlloc在Java堆上分配指定大小的内存。如果分配成功,那么就将分配得到的地址直接返回给调用者了。函数dvmHeapSourceAlloc在不改变Java堆当前大小的前提下进行内存分配,这是属于轻量级的内存分配动作。

    2. 如果上一步内存分配失败,这时候就需要执行一次GC了。不过如果GC线程已经在运行中,即gDvm.gcHeap->gcRunning的值等于true,那么就直接调用函数dvmWaitForConcurrentGcToComplete等到GC执行完成就是了。否则的话,就需要调用函数gcForMalloc来执行一次GC了,参数false表示不要回收软引用对象引用的对象。

    3. GC执行完毕后,再次调用函数dvmHeapSourceAlloc尝试轻量级的内存分配操作。如果分配成功,那么就将分配得到的地址直接返回给调用者了。

    4. 如果上一步内存分配失败,这时候就得考虑先将Java堆的当前大小设置为Dalvik虚拟机启动时指定的Java堆最大值,再进行内存分配了。这是通过调用函数dvmHeapSourceAllocAndGrow来实现的。 

    5. 如果调用函数dvmHeapSourceAllocAndGrow分配内存成功,则直接将分配得到的地址直接返回给调用者了。

    6. 如果上一步内存分配还是失败,这时候就得出狠招了。再次调用函数gcForMalloc来执行GC。参数true表示要回收软引用对象引用的对象。

    7. GC执行完毕,再次调用函数dvmHeapSourceAllocAndGrow进行内存分配。这是最后一次努力了,成功与事都到此为止。

4 GC的类型

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都是在分配对象的过程触发的。

GC_FOR_MALLOC | GC_BEFORE_OOM 就是在我们前面看到的申请对象过程中gcForMalloc(bool soft) 函数触发的gc

static void gcForMalloc(bool clearSoftReferences)
{
    const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
    dvmCollectGarbageInternal(spec);
}

GC_CONCURRENT 在每次对象申请成功后都会做检查,代码如下

if (heap->bytesAllocated > heap->concurrentStartBytes) {
        dvmSignalCond(&gHs->gcThreadCond);
    }

GC_EXPLICIT 是明确的要进行gc

void dvmCollectGarbage()
{
    if (gDvm.disableExplicitGc) {
        return;
    }
    dvmLockHeap();
    dvmWaitForConcurrentGcToComplete();
    dvmCollectGarbageInternal(GC_EXPLICIT);
    dvmUnlockHeap();
}

从函数可以看到,GC_EXPLICIT并不一定会被执行,可以通过-XX:+DisableExplicitGC参数设置disableExplicitGc变量禁止显方gc
另外还要等待并行gc完成,所以调用System.gc 执行gc的时机是不确定的

5 GC的主体流程

static void *gcDaemonThread(void* arg)
{
    dvmChangeStatus(NULL, THREAD_VMWAIT);
    dvmLockMutex(&gHs->gcThreadMutex);
    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);
        }

        ......

        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);
    return NULL;
}

该函数运行在gc进程中
执行完并行gc后会设置gcThreadTrimNeeded 为真,那么过HEAP_TRIM_IDLE_TIME_MS 后会执行trimHeaps 然后再设置gcThreadTrimNeeded为flase后才能再次执行并行gc,所以并行gc和trimHeaps是交替执行的, trimHeaps就是请求操作系统释放一些物理内存。

猜你喜欢

转载自blog.csdn.net/woai110120130/article/details/84176000