Android Dalvik虚拟机 堆初始化流程

news2024/9/29 16:14:41

前言

上篇文章介绍了dalvik虚拟机启动流程,在dalvik虚拟机启动时调用了dvmGcStartup来启动堆。
本文介绍我们在日常开发使用Java时的堆创建流程。

Dalvik堆介绍

Dalvik虚拟机中,堆是由heap[0] Active堆和heap[1] Zygote堆两部分组成的。其中,Zygote堆用来管理Zygote进程在启动过程中预加载和创建的各种对象,而Active堆是在Zygote进程fork第一个子进程之前创建的。
之后无论是Zygote进程还是其子进程,都在Active堆上进行对象分配和释放。这样做的目的是使得Zygote进程和其子进程最大限度地共享Zygote堆所占用的内存。
在这里插入图片描述
Dalvik虚拟机管理中的重要结构包括一个Card Table、两个Heap Bitmap和一个GcMarkStack。

HeapBitmap

HeapBitmap是堆的内存分配情况的映射图,它的每一个bit位记录着堆中每8个字节的分配情况。
堆中有两个HeapBitmap,一个称为LiveHeapBitmap,用来记录上次GC之后还存活的对象;另一个称为MarkHeapBitmap,用来记录当前GC中还存活的对象。这样,上次GC后存活的但是当前GC不存活的对象,就是需要释放的对象。

GcMarkStack

Davlk虚拟机使用标记-清除(Mark-Sweep)算法进行GC。在标记阶段,通过一个Mark Stack来实现递归检查被引用的对象,即在当前GC中存活的对象。有了这个Mark Stack,就可以通过循环来模拟函数递归调用。
在垃圾回收的过程中,需要通过递归的方式去检查系统中的每个对象。但是递归太深会引起栈溢出,因此,实际采用的回收算法中用GcMarkStack来保存中间的数据。

CardTable

Card Table是为了记录在垃圾收集过程中对象的引用情况的,用在Concurrent GC第二阶段记录非垃圾收集堆对象对垃圾收集堆对象的引用。后文会分析内存回收流程,即gc流程。
Card Table和Heap Bitmap的作用是类似的。区别在于:

  1. Card Table不是使用一个bit来描述一个对象,而是用一个byte来描述GC_CARD_SIZE个对象;
  2. Card Table不是用来描述对象的存活,而是用来描述在Concurrent GC的过程中被修改的对象,这些对象需要进行特殊处理。

初始化zygote堆

  • dalvik/vm/alloc/Alloc.cpp
// Initialize the GC universe.
bool dvmGcStartup()
{
    dvmInitMutex(&gDvm.gcHeapLock);
    pthread_cond_init(&gDvm.gcHeapCond, NULL);
    return dvmHeapStartup();
}
  • dalvik/vm/alloc/Heap.cpp
    初始化堆,当heapGrowthLimit=0时,使用heapMaximumSize
// Initialize the GC heap.
bool dvmHeapStartup()
{
    GcHeap *gcHeap;

    if (gDvm.heapGrowthLimit == 0) {
        gDvm.heapGrowthLimit = gDvm.heapMaximumSize;
    }

    gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize, gDvm.heapMaximumSize, gDvm.heapGrowthLimit);
    gDvm.gcHeap = gcHeap;

    // Set up the lists we'll use for cleared reference objects.
    gcHeap->clearedReferences = NULL;
    
	// 初始化cradTable
    dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit);

    return true;
}
  • dalvik/vm/alloc/HeapSource.cpp
    • dvmAllocRegion()函数来分配一块内存空间,然后把这块内存空间交给dlmalloc来管理;dvmAllocRegion()函数中使用ashmem_create_region()和mmap()函数来分配需要的内存空间,这也意味着dvmAllocRegion()分配的都是大块的内存。以下几个函数中内存分配都是在使用dvmAllocRegion()分配的内存,并没有从Dalvik的堆上分配,因为这几个对象在系统中会一直存在,不能被回收,因此,直接从系统内存中分配,不用Dalvik管理。
    • addInitialHeap()函数将创建出来的内存放到了heapSource的字段HeapSource[0]里。Dalvik并没有直接使用系统调用来自己管理动态内存,而是以“私有堆”的形式交给dlmalloc管理。
    • dvmHeapBitmapInit()函数创建了两个HeapBitmap的对象,HeapBitmap是堆的内存分配情况的映射图,它的每一个bit位记录着堆中每8个字节的分配情况。
    • allocMarkStack()函数分配了一块内存,并用它来初始化GcMarkStack结构。在垃圾回收的过程中,需要通过递归的方式去检查系统中的每个对象。但是递归太深会引起栈溢出,因此,实际采用的回收算法中用GcMarkStack来保存中间的数据。
// Initializes the heap source; 
GcHeap* dvmHeapSourceStartup(size_t startSize, size_t maximumSize, size_t growthLimit) {
    GcHeap *gcHeap;
    HeapSource *hs;
    mspace msp;
    size_t length;
    void *base;

    // Allocate a contiguous region of virtual memory to subdivided among the heaps managed by the garbage collector. 
    length = ALIGN_UP_TO_PAGE_SIZE(maximumSize);
    base = dvmAllocRegion(length, PROT_NONE, gDvm.zygote ? "dalvik-zygote" : "dalvik-heap");
    // Create an unlocked dlmalloc mspace to use as a heap source.
    msp = createMspace(base, kInitialMorecoreStart, startSize);

    gcHeap = (GcHeap *)calloc(1, sizeof(*gcHeap));
    hs = (HeapSource *)calloc(1, sizeof(*hs));

    hs->targetUtilization = gDvm.heapTargetUtilization * HEAP_UTILIZATION_MAX;
    hs->minFree = gDvm.heapMinFree;
    hs->maxFree = gDvm.heapMaxFree;
    hs->startSize = startSize;
    hs->maximumSize = maximumSize;
    hs->growthLimit = growthLimit;
    hs->idealSize = startSize;
    hs->softLimit = SIZE_MAX;    // no soft limit at first
    hs->numHeaps = 0;
    hs->sawZygote = gDvm.zygote;
    hs->nativeBytesAllocated = 0;
    hs->nativeFootprintGCWatermark = startSize;
    hs->nativeFootprintLimit = startSize * 2;
    hs->nativeNeedToRunFinalization = false;
    hs->hasGcThread = false;
    hs->heapBase = (char *)base;
    hs->heapLength = length;
  
    // Add the initial heap. 初始化heapSource中的第一个堆
    addInitialHeap(hs, msp, growthLimit);
    // Initialize a HeapBitmap so that it points to a bitmap large enough to cover a heap at <base> of <maxSize> bytes
    dvmHeapBitmapInit(&hs->liveBits, base, length, "dalvik-bitmap-1");
    dvmHeapBitmapInit(&hs->markBits, base, length, "dalvik-bitmap-2");

    allocMarkStack(&gcHeap->markContext.stack, hs->maximumSize);
    gcHeap->markContext.bitmap = &hs->markBits;
    gcHeap->heapSource = hs;

    gHs = hs;
    return gcHeap;
}

//Add the initial heap.  
static bool addInitialHeap(HeapSource *hs, mspace msp, size_t maximumSize)
{
    if (hs->numHeaps != 0) {
        return false;
    }
    hs->heaps[0].msp = msp;
    hs->heaps[0].maximumSize = maximumSize;
    hs->heaps[0].concurrentStartBytes = SIZE_MAX;
    hs->heaps[0].base = hs->heapBase;
    hs->heaps[0].limit = hs->heapBase + maximumSize;
    hs->heaps[0].brk = hs->heapBase + kInitialMorecoreStart;
    hs->numHeaps = 1;
    return true;
}

// Initialize a HeapBitmap so that it points to a bitmap large enough to cover a heap at <base> of <maxSize> bytes, where objects are guaranteed to be HB_OBJECT_ALIGNMENT-aligned.
bool dvmHeapBitmapInit(HeapBitmap *hb, const void *base, size_t maxSize, const char *name) {
    void *bits;
    size_t bitsLen;

    bitsLen = HB_OFFSET_TO_INDEX(maxSize) * sizeof(*hb->bits);
    bits = dvmAllocRegion(bitsLen, PROT_READ | PROT_WRITE, name);
    if (bits == NULL) {
        ALOGE("Could not mmap %zd-byte ashmem region '%s'", bitsLen, name);
        return false;
    }
    hb->bits = (unsigned long *)bits;
    hb->bitsLen = hb->allocLen = bitsLen;
    hb->base = (uintptr_t)base;
    hb->max = hb->base - 1;
    return true;
}

初始化active堆

  1. 直到dvmHeapStartup()函数结束,heapSource中的两个“堆”只有heaps[0]初始化了,heaps[1]仍然为NULL。因为dvmHeapStartup()的调用是在Zygote进程中进行的。
  2. 在第一个应用启动前,还会继续完成Dalvik内存模块的初始化工作,但该初始化active heap只会进行一次,由gDvm.newZygoteHeapAllocated布尔变量控制,即Zygote进程只会在fork第一个子进程的时候,才会将Java堆划一分为二来管理;这么设计是因为 We create a heap for all future zygote process allocations, in an attempt to avoid touching pages in the zygote heap。
  3. 在Zygote的nativeFork()函数中还会调用dvmGcPreZygoteFork()函数,其中会调用函数dvmHeapSourceStartupBeforeFork()去初始化active堆,并把该active堆放到heap数组前面,以后无论是Zygote进程,还是Zygote子进程,需要分配对象时,都在Active堆上进行。这样就可以使得Zygote堆最大限度地在Zygote进程及其子进程中共享。
  • dalvik/vm/native/dalvik_system_Zygote.cpp
static void Dalvik_dalvik_system_Zygote_fork(const u4* args, JValue* pResult)
{
    pid_t pid;
    dvmGcPreZygoteFork(); // 在fork前分配active堆
    setSignalHandler();
    dvmDumpLoaderStats("zygote");
    pid = fork();
    RETURN_INT(pid);
}
  • dalvik/vm/alloc/Alloc.cpp
// Do any last-minute preparation before we call fork() for the first time. 
bool dvmGcPreZygoteFork() {
    return dvmHeapSourceStartupBeforeFork();
}
  • dalvik/vm/alloc/HeapSource.cpp
    addNewHeap()函数主要的功能是创建了一个新的堆。
    创建的过程是将旧的heaps[0]第一页以后的内存地址空间分给了新的堆,然后对新堆的地址空间在原来地址的基础上重新执行mmap。接下来将heaps[0]指向的堆的尺寸减小为一页大小,最后将heaps[0]和heaps[1]的值交换。
    因此,两个堆都创建后,大小和以前还是一样,但是heaps[0]指向了一个新的、未分配内存的堆,而heaps[1]则包含了初始化时创建的内存对象,以后的内存分配都将在heaps[0]中进行。
/*
 * This is called while in zygote mode, right before we fork() for the
 * first time.  We create a heap for all future zygote process allocations,
 * in an attempt to avoid touching pages in the zygote heap.  (This would
 * probably be unnecessary if we had a compacting GC -- the source of our
 * troubles is small allocations filling in the gaps from larger ones.)
 */
bool dvmHeapSourceStartupBeforeFork()
{
    HeapSource *hs = gHs; // use a local to avoid the implicit "volatile"
    if (!gDvm.newZygoteHeapAllocated) {
        // Ensure heaps are trimmed to minimize footprint pre-fork.
        trimHeaps();
        // Create a new heap for post-fork zygote allocations.  We only try once, even if it fails.
        gDvm.newZygoteHeapAllocated = true;
        return addNewHeap(hs);
    }
    return true;
}


// Adds an additional heap to the heap source.  Returns false if there are too many heaps or insufficient free space to add another heap.
static bool addNewHeap(HeapSource *hs)
{
    Heap heap;
    memset(&heap, 0, sizeof(heap));

    // Heap storage comes from a common virtual memory reservation. The new heap will start on the page after the old heap.
    char *base = hs->heaps[0].brk;
    size_t overhead = base - hs->heaps[0].base;

    size_t morecoreStart = SYSTEM_PAGE_SIZE;
    heap.maximumSize = hs->growthLimit - overhead;
    heap.concurrentStartBytes = hs->minFree - CONCURRENT_START;
    heap.base = base;
    heap.limit = heap.base + heap.maximumSize;
    heap.brk = heap.base + morecoreStart;
    remapNewHeap(hs, &heap);
    heap.msp = createMspace(base, morecoreStart, hs->minFree);

    // Don't let the soon-to-be-old heap grow any further
    hs->heaps[0].maximumSize = overhead;
    hs->heaps[0].limit = base;
    mspace_set_footprint_limit(hs->heaps[0].msp, overhead);

    // Put the new heap in the list, at heaps[0]
    memmove(&hs->heaps[1], &hs->heaps[0], hs->numHeaps * sizeof(hs->heaps[0]));
    hs->heaps[0] = heap;
    hs->numHeaps++;

    return true;
}

/*
 * A helper for addNewHeap(). Remap the new heap so that it will have
 * a separate ashmem region with possibly a different name, etc. In
 * practice, this is used to give the app heap a separate ashmem
 * region from the zygote heap's.
 */
static bool remapNewHeap(HeapSource* hs, Heap* newHeap)
{
  char* newHeapBase = newHeap->base;
  size_t rem_size = hs->heapBase + hs->heapLength - newHeapBase;
  munmap(newHeapBase, rem_size);
  int fd = ashmem_create_region("dalvik-heap", rem_size);
  void* addr = mmap(newHeapBase, rem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
  int ret = close(fd);
  return true;
}

参考:Dalvik虚拟机Java堆创建过程分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338952.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

13 Day:实现内核线程

前言&#xff1a;我们昨天完成了内核的内存池以及内存管理程序&#xff0c;今天我们要揭开操作系统多任务执行的神秘面纱&#xff0c;来了解并实现一个多任务的操作系统。 一&#xff0c;实现内核线程 在聊线程之间我们先聊聊处理器吧&#xff0c;众所周之现在我们的CPU动不动…

心跳太快对身体带来影响?4种方法来减速!

心脏每时每刻都在跳动&#xff0c;跳动时遵循一定的节律。正常情况下成年人每分钟心跳达到60~120下&#xff0c;若心跳每分钟大于120下&#xff0c;被判断为心动过速&#xff1b;若心跳每分钟不足50下&#xff0c;被判断为心动过缓&#xff0c;无论是哪种因素均会影响身体健康。…

详解Redisson分布式限流的实现原理

我们目前在工作中遇到一个性能问题&#xff0c;我们有个定时任务需要处理大量的数据&#xff0c;为了提升吞吐量&#xff0c;所以部署了很多台机器&#xff0c;但这个任务在运行前需要从别的服务那拉取大量的数据&#xff0c;随着数据量的增大&#xff0c;如果同时多台机器并发…

如何用Python打包好exe文件,并替换图标

前言 Python打包&#xff1f;打包exe文件&#xff1f;怎么操作&#xff1f; ok&#xff0c;今天我来分享分享&#xff0c;教你们如何打包号文件&#xff0c;顺便还来展示一下&#xff0c;如何替换好图标 首先把你的代码准备好&#xff0c;尽量不要中文路径&#xff0c;容易报…

flex 布局

设为 Flex 布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效。 flex 和 inline-flexflex&#xff1a; 将对象作为弹性伸缩盒显示inline-flex&#xff1a;将对象作为内联块级弹性伸缩盒显示<style>.main {background-color: #0f0;display: flex; /*父…

【VictoriaMetrics】VictoriaMetrics启停脚本

先看结果,启动VictoriaMetrics UI界面可访问

有趣的Hack-A-Sat黑掉卫星挑战赛——定位卫星Jackson

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…

图解LeetCode——剑指 Offer 53 - I. 在排序数组中查找数字 I

一、题目 统计一个数字在排序数组中出现的次数。 二、示例 示例 1 【输入】nums [5,7,7,8,8,10], target 8 【输出】2 示例 2: 【输入】nums [5,7,7,8,8,10], target 6 【输出】0 提示&#xff1a; 0 < nums.length < 10^5-10^9 < nums[i] < 10^9nums 是一…

基于Java+SpringBoot+SpringCloud+Vue前后端分离医院管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建、毕业项目实战、项目定制✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《S…

一文总结 JUC 并发编程

文章目录一、JUC 并发编程二、协调锁1. Synchronized2. Synchronized 锁下线程通信3. Lock 锁4. Lock 锁实例 - ReentrantLock5. 读写锁 - ReadWriteLock三、CAS & 原子类1. CAS2. 原子类2.1 基础原子类2.2 数组类型原子类2.3 引用型原子类四、线程1. Callable 和 Future2.…

Filter防火墙(8)

实验目的 1、了解个人防火墙的基本工作原理&#xff1b; 2、掌握Filter防火墙的配置。 预备知识防火墙 防火墙&#xff08;Firewall&#xff09;是一项协助确保信息安全的设备&#xff0c;会依照特定的规则&#xff0c;允许或是限制传输的数据通过。防火墙可以是一台专属的硬…

Linux防火墙(7)

实验目的 通过该实验了解Linux防火墙iptables实现原理&#xff0c;掌握iptables基本使用方法&#xff0c;能够利用iptables对操作系统进行加固。预备知识基本原理 netfilter/iptables&#xff08;简称为iptables&#xff09;组成Linux平台下的包过滤防火墙&#xff0c;具有完成…

拉普拉斯矩阵的定义,常见的几种形式以及代码实现?

拉普拉斯矩阵 拉普拉斯矩阵(Laplacian matrix) 也叫做导纳矩阵、基尔霍夫矩阵或离散拉普拉斯算子,主要应用在图论中,作为一个图的矩阵表示。对于图 G=(V,E),其Laplacian 矩阵的定义为 L=D-A,其中 L 是Laplacian 矩阵, D=diag(d)是顶点的度矩阵(对角矩阵),d=rowSum(A),…

【Java 面试合集】简述下Java的三个特性 以及项目中的应用

简述下Java的特征 以及项目中的应用 1. 概述 上述截图中就是Java的三大特性&#xff0c;以及特性的实现方案。接下来就每个点展开来说说 2. 封装 满足&#xff1a;隐藏实现细节&#xff0c;公开使用方法 的都可以理解为是封装 而实现封装的有利手段就是权限修饰符了。可以根据…

【MT7628】开发环境搭建-安装Fedora12

1.下载Fedora安装镜像 1.1链接地址 http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/12/Fedora/i386/iso/ 1.2选择如下截图的软件包下载 1.3下载完成的软件包名称

06分支限界法

文章目录八数码难题普通BFS算法全局择优算法&#xff08;A算法&#xff0c;启发式搜索算法&#xff09;单源最短路径问题装载问题算法思想&#xff1a;队列式分支限界法优先队列式分支限界法布线问题最大团问题批处理作业调度问题分支限界法与回溯法的区别&#xff1a; &#x…

已解决sc delete MongoDB卸载MongoDB拒绝访问。

已解决sc delete MongoDB卸载MongoDB拒绝访问。 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 粉丝群里面的一个小伙伴遇到问题跑来私信我&#xff0c;想卸载MongoDB数据库&#xff0c;但是发生了报错&#xff08;当时他心里瞬间凉了一大截&…

select 与 where、order by、limit 子句执行优先级比较

当 select 和 其他三种语句的一者或者多者同时出现时&#xff0c;他们之间是存在执行先后顺序的。 他们的优先级顺序是&#xff1a;where > select > order by > limit 目录 1、select 与 where 2、select 与 order by 3、order by 与 limit 4、优先级证明 1、s…

低噪声与功放选型购买

低噪声与功率放大器的区别&#xff1f;购买时怎么区分&#xff1f; 低噪放 低噪放&#xff0c;低噪声射频放大器。作用就是要求噪声系数很低&#xff0c;放大电压信号。一般放在系统第一级&#xff0c;因为噪声系数低&#xff0c;接收放大的信号有很好的的信噪比。如天线的接…

大数据框架之Hadoop:入门(六)常见错误及解决方案

1&#xff09;防火墙没关闭、或者没有启动YARN INFO client.RMProxy: Connecting to ResourceManager at hdp101/192.168.10.101:80322&#xff09;主机名称配置错误 3&#xff09;IP地址配置错误 4&#xff09;ssh没有配置好 5&#xff09;root用户和vagrant两个用户启动集…