Android Dalvik虚拟机 堆内存管理 增长&释放

前言

本篇继续介绍安卓dalvik虚拟机系列,介绍Dalvik虚拟机的堆内存管理,即堆是如何增长和释放的。

Dalvik堆大小的两种限制

Dalvik中的softlimit和mspace的footprint机制:

  • softlimit:是Dalvik设定的机制,表示应用可以从Active堆中分配的内存最大值;
  • Java堆的Soft Limit开始的时候设置为最大允许的整数值。但是每一次GC之后,Dalvik虚拟机会根据Active堆已经分配的内存字节数、设定的堆目标利用率和Zygote堆的大小,重新计算Soft Limit,以及别外一个称为理想大小(Ideal Size)的值。如果此时只有一个堆,即只有Active堆没有Zygote堆,那么Soft Limit就等于Ideal Size。如果此时有两个堆,那么Ideal Size就等于Zygote堆的大小再加上Soft Limit值,其中Soft Limit值就是此时Active堆的大小,它是根据Active堆已经分配的内存字节数和设定的堆目标利用率计算得到的。
  • 这个Soft Limit值到底有什么用呢?它主要是用来限制Active堆无节制地增长到最大值的,而是要根据预先设定的堆目标利用率来控制Active有节奏地增长到最大值。这样可以更有效地使用堆内存。想象一下,如果我们一开始Active堆的大小设置为最大值,那么就很有可能造成已分配的内存分布在一个很大的范围。这样随着Dalvik虚拟机不断地运行,Active堆的内存碎片就会越来越来重。相反,如果我们施加一个Soft Limit,那可以尽量地控制已分配的内存都位于较紧凑的范围内。这样就可以有效地减少碎片。
  • footprint机制:mspace是ashmem匿名共享内存的一个封装,Dalvik是通过mspace_malloc和mspace_bulk_free来管理Java堆的。mspace的大小也就是footprint,Dalvik在启动时并不会将footprint设置为允许的最大值,而是给定的一个startingSize,之后在分配的时候逐渐增大这个值。
  • mspace_footprint_limit返回系统允许分配的最大值
  • mspace_footprint返回系统已经分配的内存值。这个值比所有通过dlmalloc、dlrealloc等函数分配的内存之和要大。
  • footprint机制在整个堆管理中是设置的zygote堆和active堆之和的大小,称为idealSize,通过setIdealFootprint函数进行设置。

堆分配时的内存增长dvmHeapSourceAllocAndGrow

从堆创建流程我们知道,虚拟机启动时会创建一个heapStartingSize大小的堆,在后续分配对象过程中进行增大。
从前面的对象创建流程我们知道,分配不出来对象会走到dvmHeapSourceAllocAndGrow,会进行增长堆大小。

函数流程如下:

  • 如果Active堆设置有Soft Limit,那么函数isSoftLimited的返回值等于true。在这种情况下,先将Soft Limit去掉,再调用函数dvmHeapSourceAlloc来分配大小为n的内存。如果分配成功,那么在将分配得到的地址返回给调用者之前,需要调用函数snapIdealFootprint来修改Active堆的大小。也就是说,在去掉Active堆的Soft Limit之后,可以成功地分配到大小为n的内存,这时候就需要相应的增加Soft Limit的大小。
  • 如果Active堆没有设置Soft Limit,或者去掉Soft Limit之后,仍然不能成功地在Active堆上分配在大小为n的内存,那么这时候就得出大招了,它会调用函数heapAllocAndGrow将Java堆的大小设置为允许的最大值,然后再在Active堆上分配大小为n的内存。
  • 最后,如果能成功分配到大小为n的内存,那么就调用函数snapIdealFootprint来重新设置Active堆的当前大小。否则的话,就调用函数setIdealFootprint来恢复之前Active堆的大小。这是因为虽然分配失败,但是前面仍然做了修改Active堆大小的操作。
void* dvmHeapSourceAllocAndGrow(size_t n)
{
    
    
    HeapSource *hs = gHs;
    Heap* heap = hs2heap(hs);
    void* ptr = dvmHeapSourceAlloc(n);
    if (ptr != NULL) {
    
    
        return ptr;
    }

    size_t oldIdealSize = hs->idealSize;

    // 移除softLimited再分配一次
    if (isSoftLimited(hs)) {
    
    
        // We're soft-limited.  Try removing the soft limit to see if we can allocate without actually growing.
        hs->softLimit = SIZE_MAX;
        ptr = dvmHeapSourceAlloc(n);
        if (ptr != NULL) {
    
    
            // Removing the soft limit worked;  fix things up to reflect the new effective ideal size.
            snapIdealFootprint();
            return ptr;
        }
        // softLimit intentionally left at SIZE_MAX.
    }

    // We're not soft-limited.  Grow the heap to satisfy the request. If this call fails, no footprints will have changed.
    ptr = heapAllocAndGrow(hs, heap, n);
    if (ptr != NULL) {
    
    
        // The allocation succeeded.  Fix up the ideal size to reflect any footprint modifications that had to happen.
        snapIdealFootprint();
    } else {
    
    
        // We just couldn't do it.  Restore the original ideal size, fixing up softLimit if necessary.
        setIdealFootprint(oldIdealSize);
    }
    return ptr;
}

isSoftLimited

  • softLimit描述的是Active堆的大小,而idealSize描述的是Zygote堆和Active堆的大小之和。
    当只有一个堆时,即只有Active堆时,如果设置了Soft Limit,那么它的大小总是等于Active堆的大小,即这时候hs->softLimit总是等于hs->idealSize。
    如果没有设置Soft Limit,那么它的值会被设置为SIZE_MAX值,这会就会保证hs->softLimit大于hs->idealSize。也就是说,当只有一个堆时,函数isSoftLimited能正确的反映Active堆是否设置有Soft Limit。
  • 当有两个堆时,即Zygote堆和Active堆同时存在,
    那么如果设置有Soft Limit,那么它的值就总是等于Active堆的大小。由于hs->idealSize描述的是Zygote堆和Active堆的大小之和,因此就一定可以保证hs->softLimit小于等于hs->idealSize。
    如果没有设置Soft Limit,即hs->softLimit的值等于SIZE_MAX,那么就一定可以保证hs->softLimit的值大于hs->idealSize的值。也就是说,当有两个堆时,函数isSoftLimited也能正确的反映Active堆是否设置有Soft Limit。
// Returns true iff a soft limit is in effect for the active heap.
static bool isSoftLimited(const HeapSource *hs)
{
    
    
    /* softLimit will be either SIZE_MAX or the limit for the active mspace. 
     *  idealSize can be greater than softLimit if there is more than one heap.  
     * If there is only one heap, a non-SIZE_MAX softLimit should always be the same as idealSize.
     */
    return hs->softLimit <= hs->idealSize;
}

heapAllocAndGrow

函数heapAllocAndGrow使用最激进的办法来在参数heap描述的堆上分配内存,参数heap描述的就是Active堆上。

  • 它首先将Active堆的大小设置为允许的最大值,接着再调用函数dvmHeapSourceAlloc在上面分配大小为n的内存。
  • 接着再通过函数mspace_footprint获得分配了n个字节之后Active堆的大小,并且将该值设置为Active堆的当前大小限制。这就相当于是将Active堆的当前大小限制值从允许设置的最大值减少为一个刚刚合适的值。
// 增长堆,放大footprint_limit后缩小
static void* heapAllocAndGrow(HeapSource *hs, Heap *heap, size_t n)
{
    
    
    // Grow as much as possible, but don't let the real footprint go over the absolute max.
    size_t max = heap->maximumSize;
    
    mspace_set_footprint_limit(heap->msp, max);
    void* ptr = dvmHeapSourceAlloc(n);

    // Shrink back down as small as possible.  Our caller may readjust max_allowed to a more appropriate value.
    mspace_set_footprint_limit(heap->msp,
                               mspace_footprint(heap->msp));
    return ptr;
}

snapIdealFootprint

  • 函数snapIdealFootprint通过调用前面分析的函数getSoftFootprint和setIdealFootprint来调整Active堆的大小以及Soft Limit值。
    函数dvmHeapSourceAlloc调用snapIdealFootprint的情景,分别是在修改了Active堆的Soft Limit或者将Active堆的大小设置为允许的最大值,并且成功在Active堆分配了指定大小的内存之后进行的。
    这样就需要调用函数snapIdealFootprint将Active堆的大小设置为实际使用的大小(而不是允许的最大值),以及重新设置Soft Limit值。
// Make the ideal footprint equal to the current footprint.
static void snapIdealFootprint() {
    
    
    // 扩大idal
    setIdealFootprint(getSoftFootprint(true));
}

setIdealFootprint

  • overhead = hs->heaps[0].brk - hs->heaps[0].base:
    brk:描述当前堆所分配的最大内存值。
    base:描述堆所使用的内存块的起始地址。
    即overhead表示该堆正在用的大小

  • 函数setIdealFootprint的作用是要将Zygote堆和Active堆的大小之和设置为max。
    在设置之前,先检查max值是否大于Java堆允许的最大值maximum。如果大于的话,那么就属于将max的值修改为maximum。
    接下为是以参数false来调用函数getSoftFootprint来获得Zygote堆的大小overhead。如果max的值大于Zygote堆的大小overhead,那么从max中减去overhead,就可以得到Active堆的大小activeMax。
    如果max的值小于等于Zygote堆的大小overhead,那么就说明要将Active堆的大小activeMax设置为0。
    最后,函数setIdealFootprint调用函数setSoftLimit设置Active堆的当前大小,并且将Zygote堆和Active堆的大小之和记录在hs->idealSize中。

// 堆大小设置到内核允许的最大值,即footlimit允许的最大值
// Sets the maximum number of bytes that the heap source is allowed to allocate from the system.  
// Clamps to the appropriate maximum value.
static void setIdealFootprint(size_t max)
{
    
    
    HeapSource *hs = gHs;
    size_t maximumSize = getMaximumSize(hs);
    if (max > maximumSize) {
    
    
        max = maximumSize;
    }

    // 获取Zygote堆的大小overhead
    size_t overhead = getSoftFootprint(false); 
    size_t activeMax;
    if (overhead < max) {
    
    
        activeMax = max - overhead;
    } else {
    
    
        activeMax = 0;
    }

    setSoftLimit(hs, activeMax);
    hs->idealSize = max;
}

getSoftFootprint

  • 函数getSoftFootprint首先调用函数oldHeapOverhead获得Zygote堆的大小ret。当参数includeActive等于true时,就表示要返回的是Zygote堆的大小再加上Active堆当前已经分配的内存字节数的值。而当参数includeActive等于false时,要返回的仅仅是Zygote堆的大小。
  • 当参数includeActive等于true时,函数oldHeapOverhead返回的是Zygote堆和Active堆的大小之和,而当参数includeActive等于false时,函数oldHeapOverhead仅仅返回Zygote堆的大小。注意,hs->heaps[0]指向的是Active堆,而hs->heaps[1]指向的是Zygote堆。
/*
 * Return the real bytes used by old heaps plus the soft usage of the
 * current heap.  When a soft limit is in effect, this is effectively
 * what it's compared against (though, in practice, it only looks at
 * the current heap).
 */
static size_t getSoftFootprint(bool includeActive)
{
    
    
    HeapSource *hs = gHs;
    size_t ret = oldHeapOverhead(hs, false); // 获取zygote堆的overhead
    if (includeActive) {
    
    
        ret += hs->heaps[0].bytesAllocated;
    }

    return ret;
}

// Returns the current footprint of all heaps.  If includeActive is false, don't count the heap at index 0.
static size_t oldHeapOverhead(const HeapSource *hs, bool includeActive)
{
    
    
    size_t footprint = 0;
    size_t i;

    if (includeActive) {
    
    
        i = 0;
    } else {
    
    
        i = 1;
    }
    for (/* i = i */; i < hs->numHeaps; i++) {
    
    
        footprint += mspace_footprint(hs->heaps[i].msp);
    }
    return footprint;
}

setSoftLimit

  • 函数setSoftLimit首先是获得Active堆的当前大小currentHeapSize。
    如果参数softLimit的值小于Active堆的当前大小currentHeapSize,那么就意味着要给Active堆设置一个Soft Limit,这时候主要就是将参数softLimit的保存在hs->softLimit中。
  • 另一方面,如果参数softLimit的值大于等于Active堆的当前大小currentHeapSize,那么就意味着要去掉Active堆的Soft Limit,并且将Active堆的大小设置为参数softLimit的值。
// Sets the soft limit, handling any necessary changes to the allowed footprint of the active heap.
static void setSoftLimit(HeapSource *hs, size_t softLimit)
{
    
    
    // Compare against the actual footprint, rather than the max_allowed, because the heap may not have grown all the way to the allowed size yet.
    mspace msp = hs->heaps[0].msp;
    size_t currentHeapSize = mspace_footprint(msp);
    if (softLimit < currentHeapSize) {
    
    
        /* Don't let the heap grow any more, and impose a soft limit.
         */
        mspace_set_footprint_limit(msp, currentHeapSize);
        hs->softLimit = softLimit;
    } else {
    
    
        /* Let the heap grow to the requested max, and remove any
         * soft limit, if set.
         */
        mspace_set_footprint_limit(msp, softLimit);
        hs->softLimit = SIZE_MAX;
    }
}

堆GC时的内存释放trimHeaps

函数trimHeaps对Dalvik虚拟机使用的Java堆和默认Native堆做了同样的两件事情。

  • 第一件事情是调用C库提供的函数mspace_trim/dlmalloc_trim来将没有使用到的虚拟内存和物理内存归还给系统,这是通过系统调用mremap来实现的。
  • 第二件事情是调用C库提供的函数mspace_inspect_all/dlmalloc_inspect_all将不能使用的内存碎片对应的物理内存归还给系统,这是通过系统调用madvise来实现的。注意,在此种情况下,只能归还无用的物理内存,而不能归还无用的虚拟内存。因为归还内存碎片对应的虚拟内存会使得堆的整体虚拟地址不连续。
// Return unused memory to the system if possible.
static void trimHeaps()
{
    
    
    HeapSource *hs = gHs;
    size_t heapBytes = 0;
    for (size_t i = 0; i < hs->numHeaps; i++) {
    
    
        Heap *heap = &hs->heaps[i];

        /* Return the wilderness chunk to the system. */
        mspace_trim(heap->msp, 0);

        /* Return any whole free pages to the system. */
        mspace_inspect_all(heap->msp, releasePagesInRange, &heapBytes);
    }

    /* Same for the native heap. */
    dlmalloc_trim(0);
    size_t nativeBytes = 0;
    dlmalloc_inspect_all(releasePagesInRange, &nativeBytes);

    LOGD_HEAP("madvised %zd (GC) + %zd (native) = %zd total bytes",
            heapBytes, nativeBytes, heapBytes + nativeBytes);
}

堆GC时的内存大小调整dvmHeapSourceGrowForUtilization

函数dvmHeapSourceGrowForUtilization用来将堆的大小设置为理想值,每次gc完后都会调用该方法,Now’s a good time to adjust the heap size。

  • 每次GC执行完成之后,都需要根据预先设置的目标堆利用率和已经分配出去的内存字节数计算得到理想的堆大小。注意,已经分配出去的内存字节数只考虑在Active堆上分配出去的字节数。得到了Active堆已经分配出去的字节数currentHeapUsed之后,就可以调用函数getUtilizationTarget来计算Active堆的理想大小targetHeapSize了。
  • 得到了Active堆的理想大小targetHeapSize之后,还要以参数false调用函数getSoftFootprint得到Zygote堆的大小overhead。将targetHeapSize和overhead相加,将得到的结果调用函数setIdealFootprint设置Java堆的大小。由此我们就可以知道,函数setIdealFootprint设置的整个Java堆的大小,而不是Active堆的大小,因此前面需要得到Zygote堆的大小。
  • 调用函数getAllocLimit得到堆当前允许分配的最大值freeBytes,然后计算并行GC触发的条件。
    CONCURRENT_MIN_FREE定义为256K,而CONCURRENT_START定义为128K。
    由此我们就可以知道,当Active堆允许分配的内存小于256K时,禁止执行并行GC,而当Active堆允许分配的内存大于等于256K,并且剩余的空闲内存小于128K,就会触发并行GC。
  • 最后是将gHs->nativeNeedToRunFinalization的值设置为true,这样当以后我们调用dalvik.system.VMRuntime类的成员函数registerNativeAllocation注册Native内存分配数时,就会触发java.lang.System类的静态成员函数runFinalization被调用,从而使得那些定义了成员函数finalize又即将被回收的对象执行成员函数finalize。
/* Start a concurrent collection when free memory falls under this
 * many bytes. 131072
 */
#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))

// 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()
{
    
    
    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.
    size_t currentHeapUsed = heap->bytesAllocated;
    size_t targetHeapSize = getUtilizationTarget(hs, currentHeapUsed);

    // 获取zygote堆的大小overhead
    size_t overhead = getSoftFootprint(false);
    // 设置大小
    setIdealFootprint(targetHeapSize + overhead);

    // 计算触发gc阈值concurrentStartBytes
    size_t freeBytes = getAllocLimit(hs);
    if (freeBytes < CONCURRENT_MIN_FREE) {
    
    
        // 堆大小在CONCURRENT_MIN_FREE以下,禁止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;
}

getUtilizationTarget

该函数是一个三段的线性函数,比较简单。
x轴是已分配对象大小liveSize,y轴是对应的堆大小targetSize。

//  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
    if (targetSize > liveSize + hs->maxFree) {
    
    
        targetSize = liveSize + hs->maxFree;
    } else if (targetSize < liveSize + hs->minFree) {
    
    
        targetSize = liveSize + hs->minFree;
    }
    return targetSize;
}

Dalvik虚拟机为新创建对象分配内存的过程分析
Dalvik虚拟机垃圾收集(GC)过程分析

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/129334114