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就是请求操作系统释放一些物理内存。

6 GC的详细过程

在这里插入图片描述
上图就是所谓的标记清除机制,分别沿着并行gc和串行gc的两条路径。

主要实现的功能有三点
1 标记引用对象,回收内存
2 执行 强引用,弱引用,影子引用的回收,在异步线程执行重写了finalizer方法同时要被回收对象的finalizer方法
3 根据目标利用率调整堆大小,和设置heap->concurrentStartBytes阈值

这里还有一点需要注意
整个过程中 Lock Heap 后由于heap被锁定,其他要分配内存的线程不能获取该锁,就不能分配内存,所以在某些阶段是不能在堆上创建新对象的。
Suspend AllThread 的目的是不允许线程改变栈贞,虽然Lock heap锁定了内存分配,但是栈贞的变化还是会影响根对象集。 当要挂起一个线程的时候会给该线程的suspendCount计数+1,线程运行过程中会在关键点检查自己的计数是否>0,如果大于0则挂起自己。
实际上就是碰到IF指令、GOTO指令、SWITCH指令、RETURN指令和THROW指令等时。

所以gc过程中线程挂起是有两种原因的 1 等锁 2 检查点挂起

对于软引用,弱引用,影子引用和finalizer 对象的收集,是放在各自的堆队列中

static void delayReferenceReferent(Object *obj, GcMarkContext *ctx)
{
    ......
    GcHeap *gcHeap = gDvm.gcHeap;
    size_t pendingNextOffset = gDvm.offJavaLangRefReference_pendingNext;
    size_t referentOffset = gDvm.offJavaLangRefReference_referent;
    Object *pending = dvmGetFieldObject(obj, pendingNextOffset);
    Object *referent = dvmGetFieldObject(obj, referentOffset);
    if (pending == NULL && referent != NULL && !isMarked(referent, ctx)) {
        Object **list = NULL;
        if (isSoftReference(obj)) {
            list = &gcHeap->softReferences;
        } else if (isWeakReference(obj)) {
            list = &gcHeap->weakReferences;
        } else if (isFinalizerReference(obj)) {
            list = &gcHeap->finalizerReferences;
        } else if (isPhantomReference(obj)) {
            list = &gcHeap->phantomReferences;
        }
        ......
        enqueuePendingReference(obj, list);
    }
}

引用对象回收的过程

void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
                              Object **weakReferences,
                              Object **finalizerReferences,
                              Object **phantomReferences)
{
    assert(softReferences != NULL);
    assert(weakReferences != NULL);
    assert(finalizerReferences != NULL);
    assert(phantomReferences != NULL);
    /*
     * Unless we are in the zygote or required to clear soft
     * references with white references, preserve some white
     * referents.
     */
    if (!gDvm.zygote && !clearSoftRefs) {
        preserveSomeSoftReferences(softReferences);
    }
    /*
     * Clear all remaining soft and weak references with white
     * referents.
     */
    clearWhiteReferences(softReferences);
    clearWhiteReferences(weakReferences);
    /*
     * Preserve all white objects with finalize methods and schedule
     * them for finalization.
     */
    enqueueFinalizerReferences(finalizerReferences);
    /*
     * Clear all f-reachable soft and weak references with white
     * referents.
     */
    clearWhiteReferences(softReferences);
    clearWhiteReferences(weakReferences);
    /*
     * Clear all phantom references with white referents.
     */
    clearWhiteReferences(phantomReferences);
    /*
     * At this point all reference lists should be empty.
     */
    assert(*softReferences == NULL);
    assert(*weakReferences == NULL);
    assert(*finalizerReferences == NULL);
    assert(*phantomReferences == NULL);
}

preserveSomeSoftReferences 函数值得注意的是,软引用并非全部要到内存紧张的时候回收,preserveSomeSoftReferences会把一般的软引用救活,所以每次gc都会回收一半。 这才是软引用的语义。

最后设置目标利用率的函数

/*
 * Given the size of a live set, returns the ideal heap size given
 * the current target utilization and MIN/MAX values.
 */
static size_t getUtilizationTarget(const HeapSource* hs, size_t liveSize)
{
    /* Use the current target utilization ratio to determine the
     * ideal heap size based on the size of the live set.
     */
    size_t targetSize = (liveSize / hs->targetUtilization) * HEAP_UTILIZATION_MAX;

    /* Cap the amount of free space, though, so we don't end up
     * with, e.g., 8MB of free space when the live set size hits 8MB.
     */
    if (targetSize > liveSize + hs->maxFree) {
        targetSize = liveSize + hs->maxFree;
    } else if (targetSize < liveSize + hs->minFree) {
        targetSize = liveSize + hs->minFree;
    }
    return targetSize;
}

和我们所说的两个虚拟机启动参数是一样的,设置的是申请内存时候的软限制

在回头看tryMalloc(size_t size)函数,每次调用dvmHeapSourceAlloc的时候都首先判断

 if (heap->bytesAllocated + n > hs->softLimit) {
        ......
        return NULL;
    }

这样当快要达到软限制的时候先执行一次gc,再进行内存分配,这样实在不能分配后,才突破软限制分配对象。 这就有效的控制内存增长,和提前进行gc。

接下来就是heap->concurrentStartBytes 阈值的设计

/*
 * Given the current contents of the active heap, increase the allowed
 * heap footprint to match the target utilization ratio.  This
 * should only be called immediately after a full mark/sweep.
 */
void dvmHeapSourceGrowForUtilization()
{
    HS_BOILERPLATE();

    HeapSource *hs = gHs;
    Heap* heap = hs2heap(hs);

    /* Use the current target utilization ratio to determine the
     * ideal heap size based on the size of the live set.
     * Note that only the active heap plays any part in this.
     *
     * Avoid letting the old heaps influence the target free size,
     * because they may be full of objects that aren't actually
     * in the working set.  Just look at the allocated size of
     * the current heap.
     */
    size_t currentHeapUsed = heap->bytesAllocated;
    size_t targetHeapSize = getUtilizationTarget(hs, currentHeapUsed);

    /* The ideal size includes the old heaps; add overhead so that
     * it can be immediately subtracted again in setIdealFootprint().
     * If the target heap size would exceed the max, setIdealFootprint()
     * will clamp it to a legal value.
     */
    size_t overhead = getSoftFootprint(false);
    setIdealFootprint(targetHeapSize + overhead);

    size_t freeBytes = getAllocLimit(hs);
    if (freeBytes < CONCURRENT_MIN_FREE) {
        /* Not enough free memory to allow a concurrent GC. */
        heap->concurrentStartBytes = SIZE_MAX;
    } else {
        heap->concurrentStartBytes = freeBytes - CONCURRENT_START;
    }

    /* Mark that we need to run finalizers and update the native watermarks
     * next time we attempt to register a native allocation.
     */
    gHs->nativeNeedToRunFinalization = true;
}
/* Start a concurrent collection when free memory falls under this
 * many bytes.
 */
#define CONCURRENT_START (128 << 10)

/* The next GC will not be concurrent when free memory after a GC is
 * under this many bytes.
 */
#define CONCURRENT_MIN_FREE (CONCURRENT_START + (128 << 10))

getAllocLimit 实际上返回的是softLImit,
当空闲内存<CONCURRENT_MIN_FREE(256k) 的时候禁止并行gc,也就是堆还很小的时候, 当堆距离软限制<CONCURRENT_START(128k)的时候开始执行并行gc 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值