Android ART 虚拟机简析

源码基于:Android U

1. prop

名称选项名称heap 变量名称功能

dalvik.vm.heapstartsize

MemoryInitialSize

initial_heap_size_

虚拟机在启动时,向系统申请的起始内存

dalvik.vm.heapgrowthlimit

HeapGrowthLimit

growth_limit_

应用可使用的 max heap,超过这个值就会产生 OOM

dalvik.vm.heapsize

MemoryMaximumSize

capacity_

特殊应用的内存最大值,需要在应用manifest.xml 中设定: android:largeHeap="true" 此时,变量growth_limit_ 被重置为capacity_ 另外,对于CC 收集器RegionSpace 的内存是 capacity_ * 2

dalvik.vm.foreground-heap-growth-multiplier

ForegroundHeapGrowthMultiplier

foreground_heap_growth_multiplier_

low memry模式下,且没有定义该prop 时,该值为 1.0f 如果定义了该prop,最终在prop值的基础上 +1.0f

dalvik.vm.heapminfree

HeapMinFree

min_free_

单次堆内存调整的最小值,也是管理内存需要的最小空闲内存

dalvik.vm.heapmaxfree

HeapMaxFree

max_free_

单次堆内存调整的最大值,也是管理内存需要的最大空闲内存

dalvik.vm.heaptargetutilization

HeapTargetUtilization

target_utilization_

堆目标利用率

首先,对比 dalvik.vm.heapsize 和 dalvik.vm.heapgrowthlimit 两个属性,在 ActivityThread.java 中handleBindApplication() 函数中会根据 android:largeHeap 属性确定使用哪个值为 heap 最大值:

        if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
            dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
        } else {
            // Small heap, clamp to the current growth limit and let the heap release
            // pages after the growth limit to the non growth limit capacity. b/18387825
            dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
        }

后面四个值用来确保每次GC 之后,java 堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC 的次数。

调整的方式按照如下的规则:

art/runtime/gc/heap.cc

void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran,
                              size_t bytes_allocated_before_gc) {
  ...
  
  if (gc_type != collector::kGcTypeSticky) {
    // Grow the heap for non sticky GC.
    uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
    DCHECK_LE(delta, std::numeric_limits<size_t>::max()) << "bytes_allocated=" << bytes_allocated
        << " target_utilization_=" << target_utilization_;
    grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
    grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
    target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
    next_gc_type_ = collector::kGcTypeSticky;
  }
  
  if (!ignore_target_footprint_) {
    SetIdealFootprint(target_size);
 
  ...
 }

按照上面公式,假设 live_size=120M,target_utilization=0.75,max_free=8M,min_free=2M,那么:

delta =120M * (1/0.75 - 1)=40M

delta(40M) > max_free(8M)

target_size=120M + 8M = 128M,即堆的尺寸在此次 GC 之后调整到128M。

这个target_size 对应下文第 2.4 节中的 total_memory 值。对应的log 如下:

system_server: NativeAlloc concurrent copying GC freed 405107(20MB) AllocSpace objects, 238(4760KB) LOS objects, 33% free, 46MB/70MB, paused 83us,119us total 245.909ms

system_server: NativeAlloc concurrent copying GC freed 330013(15MB) AllocSpace objects, 67(1340KB) LOS objects, 33% free, 47MB/71MB, paused 94us,276us total 214.045ms

system_server: Background concurrent copying GC freed 302867(18MB) AllocSpace objects, 565(11MB) LOS objects, 32% free, 48MB/72MB, paused 588us,176us total 346.099ms

system_server: NativeAlloc concurrent copying GC freed 365830(18MB) AllocSpace objects, 254(5080KB) LOS objects, 33% free, 48MB/72MB, paused 143us,133us total 306.945ms

system_server: Background concurrent copying GC freed 235782(15MB) AllocSpace objects, 666(13MB) LOS objects, 33% free, 47MB/71MB, paused 83us,127us total 160.238ms

上面计算的 target_size 是下次申请内存时是否需要 GC 的一个重要指标,下面结合场景理解。

场景一:

target_size=128M,live_size=120M,如果此时需要分配一个1M 内存对象?

管理的内存最大为8M,当请求分配 1M 内存是,可用内存 8 - 1 = 7M > min_free,所以此次申请无需GC,也不用调整 target_size。

场景二:

target_size=128M,live_size=120M,如果此时需要分配一个7M 内存对象?

管理的内存最大为8M,当请求分配 7M 内存是,可用内存 8 - 7 = 1M < min_free,所以此次申请需要GC,且调整 target_size。

场景三:

target_size=128M,live_size=120M,如果此时需要分配一个10M 内存对象?

管理的内存最大为8M,当请求分配 10M 内存是,已经超过了 8M 空间,先GC 并调整target_size,再次请求分配,如果还是失败,将 target_size 调整为最大,再次请求分配,失败就再 GC一次软引用,再次请求,还是失败那就是OOM,成功后要调整 target_size。

所以,Android在申请内存的时候,可能先分配,也可能先GC,也可能不GC,这里面最关键的点就是内存利用率跟Free内存的上下限。

2. GC log

system_server: Background concurrent copying GC freed 82590(11MB) AllocSpace objects, 1139(22MB) LOS objects, 31% free, 52MB/76MB, paused 326us,284us total 302.779ms

.mobile.service: Background young concurrent copying GC freed 185026(5214KB) AllocSpace objects, 60(7396KB) LOS objects, 59% free, 7072KB/17MB, paused 26.144ms,21us total 40.78

这里以 system_server 的 GC log 为例。

2.1 字段Background

这里展示是触发 GC 的原因,所有 GC 的原因都被记录在 gc_cause.h 中:

art/runtime/gc/gc_cause.h

enum GcCause {
    kGcCauseNone,
    kGcCauseForAlloc,
    kGcCauseBackground,
    kGcCauseExplicit,
    kGcCauseForNativeAlloc,
    ...
};
indexname备注

kGcCauseNone

None

无效类型,用于占位

kGcCauseForAlloc

Alloc

分配失败时触发GC,分配会被block,直到GC 完成 通过new 分配新对象时,如果heap size 超过max,需要先GC

kGcCauseBackground

Background

后台GC 这里的“后台”并不是指应用切到后台才会执行的GC,而是GC在运行时基本不会影响其他线程的执行,所以也可以理解为并发GC。在每一次成功分配Java对象后,都会去检测是否需要进行下一次GC,这就是GcCauseBackground GC的触发时机。触发的条件需要满足一个判断,如果new_num_bytes_allocated(所有已分配的字节数,包括此次新分配的对象) >= concurrent_start_bytes_(下一次GC触发的阈值),那么就请求一次新的GC。

kGcCauseExplicit

Explicit

显示调用 System.gc() 触发的 GC

kGcCauseForNativeAlloc

NativeAlloc

当native 分配,出现内存紧张时触发GC 需要确认 native + java 的权重是否超过了总的heap size

2.2 字段 concurrent

这里展示的是 GC 的收集器名称,代表不同 GC 算法。所有GC 收集器定义在 collector_type.h 中:

art/runtime/gc/collector_type.h

enum CollectorType {
    kCollectorTypeNone,
    kCollectorTypeMS,
    kCollectorTypeCMS,
    kCollectorTypeCMC,
    kCollectorTypeSS,
    kCollectorTypeHeapTrim,
    kCollectorTypeCC,
    ...
};
index收集器名称功能

kCollectorTypeMS

mark sweep

标记清除算法由标记阶段和清除阶段构成。 mark 阶段是把所有活动对象都做上标记,sweep 阶段是把那些没有标记的对象也就是非活动的对象进行回收的过程。通过这两个阶段,可以使用不用利用的内存空间重新得到利用。

kCollectorTypeCMS

concurrent mark sweep

并发的MS

kCollectorTypeCMC

concurrent mark compact

标记整理算法是将标记清除算法和复制算法相结合的产物。标记整理算法由标记阶段和压缩阶段构成。 mark 阶段是把所有活动对象都做上标记,compact 阶段通过数次搜索堆来重新装填活动对象。因 compact 而产生的优点是不用牺牲半个堆。

kCollectorTypeSS

semispace

综合了semi-space和mark-sweep,同时还支持compact

kCollectorTypeCC

concurrent copying

是对mark sweep 而导致内存碎片化的一个解决方案。 算法利用From 空间进行分配。当From 空间被完全占满时,GC 会将活动对象全部复制到To 空间。当复制完成后,该算法会把From 空间和To 空间互换,GC 也就结束了。From 空间和To 空间大小必须一致。这是为了保证能把From 空间中的所有活动对象都收纳到To 空间里。

2.3 GC freed 字段

GC freed 会统计两个数据:

  • AllocSpace objects,这里展示的是此次 GC 回收的非 LOS 的字节数;

  • LOS objects,这里展示的是此次 GC 回收的 LOS 的字节数;

2.4 31% free, 52MB/76MB 字段

这里展示当前进程在此次 GC 之后的内存情况。

这里记录已经分配的总内存(记作 current_heap_size)已经分配内存的最大值 (记作 total_memory,这个值不会超过最大值)。

52 M 就是 current_heap_size,76M 就是total_memory。

free 百分比 = (total_memory - current_heap_size) / total_memory

2.5 paused 字段

这里展示当前进程在此次GC 中应用挂起的时间以及次数。

每次挂起的时间都会打印出来,中间用逗号分隔。

2.6 total 字段

这里展示当前进程在此次 GC 中完成所需要的时间,其中包括 paused 时间。

2.7 源码

源码于 art/runtime/gc/heap.cc

 art/runtime/gc/heap.cc
 
 void Heap::LogGC(GcCause gc_cause, collector::GarbageCollector* collector) {
    ...
    
    LOG(INFO) << gc_cause << " " << collector->GetName()
              << (is_sampled ? " (sampled)" : "")
              << " GC freed "  << current_gc_iteration_.GetFreedObjects() << "("
              << PrettySize(current_gc_iteration_.GetFreedBytes()) << ") AllocSpace objects, "
              << current_gc_iteration_.GetFreedLargeObjects() << "("
              << PrettySize(current_gc_iteration_.GetFreedLargeObjectBytes()) << ") LOS objects, "
              << percent_free << "% free, " << PrettySize(current_heap_size) << "/"
              << PrettySize(total_memory) << ", " << "paused " << pause_string.str()
              << " total " << PrettyDuration((duration / 1000) * 1000);
    ...
}

3. 收集机制简介

Heap 类提供了三种GC 接口:

  • CollectGarbage(),用来执行显示GC,例如 system.gc() 接口;

  • ConcurrentGC(),用来执行并行GC,只能被 ART 运行时内部的GC 守护线程调用;

  • CollectGarbageInternal(),ART运行时内部调用的GC 接口,可以执行各种类型的GC;

ART runtime 将空间划分:Image Space、Malloc Space、Zygote Space、Bump Pointer Space、Region Space、Large Object Space。

art/runtime/gc/space/space.h

enum SpaceType {
  kSpaceTypeImageSpace,
  kSpaceTypeMallocSpace,
  kSpaceTypeZygoteSpace,
  kSpaceTypeBumpPointerSpace,
  kSpaceTypeLargeObjectSpace,
  kSpaceTypeRegionSpace,
};

其中前面都是在地址空间上连续的,即 Continuous Space,而 Large Object Space 是一些离散地址的集合,用来分配一些大对象,称为Discontinuous Space。

原先Davlik虚拟机使用的是传统的 dlmalloc 内存分配器进行内存分配。这个内存分配器是Linux上很常用的,但是它没有为多线程环境做过优化,因此Google为ART虚拟机开发了一个新的内存分配器:RoSalloc,它的全称是Rows of Slots allocator。RoSalloc相较于dlmalloc来说,在多线程环境下有更好的支持:在dlmalloc中,分配内存时使用了全局的内存锁,这就很容易造成性能不佳。而在RoSalloc中,允许在线程本地区域存储小对象,这就是避免了全局锁的等待时间。ART虚拟机中,这两种内存分配器都有使用。

Heap 类的 Heap::AllocObject是为对象分配内存的入口,如下:

art/runtime/gc/heap.h

  template <bool kInstrumented = true, typename PreFenceVisitor>
  mirror::Object* AllocObject(Thread* self,
                              ObjPtr<mirror::Class> klass,
                              size_t num_bytes,
                              const PreFenceVisitor& pre_fence_visitor)
      REQUIRES_SHARED(Locks::mutator_lock_)
      REQUIRES(!*gc_complete_lock_,
               !*pending_task_lock_,
               !*backtrace_lock_,
               !process_state_update_lock_,
               !Roles::uninterruptible_) {
     //AllocObjectWithAllocator() 实现在 heap-inl.h 中
    return AllocObjectWithAllocator<kInstrumented>(self,
                                                   klass,
                                                   num_bytes,
                                                   GetCurrentAllocator(),
                                                   pre_fence_visitor);
  }

会首先通过 Heap::TryToAllocate尝试进行内存的分配。在 Heap::TryToAllocate方法,会根据AllocatorType,选择不同的Space进行内存的分配。在 Heap::TryToAllocate 方法失败时,会调用 Heap::AllocateInternalWithGc 进行 GC,然后在尝试内存的分配。

参考:

https://developer.aliyun.com/article/652546#slide-5

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3dmax安装不完整Revit Interoperability

3dmax安装不完整Revit Interoperability 1.错误如图 2.在Autoremove界面中&#xff0c;点击扩展选项。 3.在扩展选项中&#xff0c;寻找并点击"1402 1406修复"。 4.根据软件指引&#xff0c;执行修复操作。Autoremove将自动修复无法打开注册表的问题。 如图 修…

[智能AI摄像头]使用docker搭建RV1126开发环境

创建ubuntu docker 创建dockerfile # 设置基础镜像为Ubuntu 18.04FROM ubuntu:20.04# 设置作者信息MAINTAINER warren "2016426377qq.com"# 设置环境变量&#xff0c;用于非交互式安装ENV DEBIAN_FRONTENDnoninteractive# 备份源列表文件RUN cp -a /etc/apt/source…

基于SSM的大学生兼职管理系统

基于SSM的大学生兼职管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 企业界面 前台学生界面 管理员界面 摘要 随着大学生兼职市场的日益繁…

LeetCode674:最长连续递增序列

题目描述 给定一个未经排序的整数数组&#xff0c;找到最长且 连续递增的子序列&#xff0c;并返回该序列的长度。 连续递增的子序列 可以由两个下标 l 和 r&#xff08;l < r&#xff09;确定&#xff0c;如果对于每个 l < i < r&#xff0c;都有 nums[i] < nums…

必应bing国内广告开户首充和开户费是多少?

微软必应Bing作为国内领先的搜索引擎之一&#xff0c;其广告平台凭借其精准的投放、高效的数据分析和广泛的用户覆盖&#xff0c;已成为众多企业的首选。 根据最新政策&#xff0c;2024年必应Bing国内广告开户预充值金额设定为1万元人民币起。这一调整旨在确保广告主在账户初始…

智研未来,直击 AI DevOps,阿里云用户交流日杭州站来啦!

在这个技术日新月异的时代&#xff0c;云上智能化 DevOps 正以前所未有的速度推动企业创新边界&#xff0c;重塑软件开发的效率与品质。 为深入探索这一变革之路&#xff0c;诚邀您参与我们的专属闭门技术沙龙&#xff0c;携手开启一场关于云上智能化 DevOps 的挑战、实践与未…

Vue学习笔记2——创建一个Vue项目

Vue项目 1、创建一个Vue项目2、Vue项目的目录结构3、模版语法4、属性绑定5、条件渲染 1、创建一个Vue项目 vue官方文档&#xff1a; https://cn.vuejs.org/打开命令行界面&#xff08; “winR"再输入"cmd”&#xff09;&#xff0c;切换位置到指定的位置创建vue项目…

通胀担忧仍存,美联储降息预期或又推迟

KlipC报道&#xff1a;周三&#xff0c;美联储公布4月30日至5月1日政策会议纪要&#xff0c;会议纪要显示美联储对通胀仍感到担忧&#xff0c;将更长时间维持利率不变&#xff0c;必要时进一步收紧政策。 尽管在前不久公布的4月CPI数据显示通胀有所缓解&#xff0c;但是被认为…

高刚性滚柱直线导轨有哪些优势?

滚柱导轨是机械传动系统中用于支持和引导滑块或导轨的装置&#xff0c;承载能力较高、刚性强及高精度等特点。特别适用于大负载和高刚性的工业设备&#xff0c;如机床、数控机床等设备&#xff0c;这些优势使其在工业生产和机械设备中得到了广泛的应用。 1、高精度&#xff1a;…

如何用java做一个模拟登录画面

要求&#xff1a; 实现registerAction方法中的注册逻辑。实现login方法中的登录逻辑&#xff0c;确保只有当用户名和密码都正确时才返回true。实现好友管理功能&#xff0c;包括添加好友、删除好友、查看好友列表。确保所有的文件操作&#xff08;如读取和写入credentials.txt…

Jenkins安装 :AWS EC2 Linux

1 JDK11 install # 用的yum安装 # 压缩包安装&#xff0c;下载的jdk-11.0.22_linux-x64_bin.tar.gz在EC2解压&#xff0c;配置环境变量&#xff0c;运行jenkins的时候会报错$ yum -y list java-11* Available Packages java-11-amazon-corretto-devel.x86_64 …

NLP(16)--生成式任务

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 输入输出均为不定长序列&#xff08;seq2seq&#xff09;自回归语言模型&#xff1a; x 为 str[start : end ]; y为 [start1 : end 1] 同时训练多个字&#xff0c;逐字计算交叉熵 encode-decode结构&#xff1a; Encoder将输…

设计模式——概述

1.设计模式定义 ​ 设计模式是软件设计中常见问题的典型解决方案,可用于解决代码中反复出现的设计问题。设计模式的出现可以让我们站在前人的肩膀上&#xff0c;通过一些成熟的设计方案来指导新项目的开发和设计&#xff0c;以便于我们开发出具有更好的灵活性和可扩展性&#…

如何在window中快速建立多个文件夹?

时隔多年&#xff0c;再次开始撰写文章&#xff0c;但是这次却是以设计师的身份 1. 几个基础快捷键先记一下&#xff0c;要不更高级的玩儿不转&#xff08;1&#xff09;快速打开资源管理器&#xff08;2&#xff09;快速建立新文件夹&#xff08;3&#xff09;快速修改文件文件…

【openlayers系统学习】1.1渲染GeoJSON,添加link交互

一、渲染GeoJSON 在进入编辑之前&#xff0c;我们将看一下使用矢量源和图层进行基本要素渲染。Workshop在 data​ 目录中包含一个 countries.json​ GeoJSON文件。我们首先加载该数据并将其渲染在地图上。 首先&#xff0c;编辑 index.html​ 以便向地图添加深色背景&#xf…

树洞陪聊系统源码/陪聊/陪玩/树洞/陪陪/公众号开发/源码交付/树洞系统源码

独立版本源码交付&#xff0c;自研UI和前后端代码 平台自带店员&#xff0c;无需自主招募&#xff0c;搭建直接运营 支持三方登录&#xff0c;官方支付、虎皮椒、易支付/码支付 支持首单体验、盲盒订单、指定下单等多个模式 支持钱包预充值、店员收藏、订单评价等功能 支持…

网页加载时,大图片文件如何分片加载,有示例代码。

浏览网页时候&#xff0c;碰到大图片半天加载不出来&#xff0c;急死人&#xff0c;本问分享一种分片加载的方式&#xff0c;其实还有其他方式&#xff0c;比如先模糊后清晰等。 一、为什么要分片加载 大图片文件可以通过分片加载来提高加载性能和用户体验。分片加载的基本思…

智能禁区监控:计算机视觉在人员禁区闯入检测中的应用

基于视觉分析的人员禁区闯入行为检测算法主要依赖于计算机视觉技术和深度学习算法。这些技术结合高性能的摄像头和图像处理硬件&#xff0c;实现了对监控区域内人员行为的自动识别和分析。具体来说&#xff0c;这种检测算法利用摄像头捕捉的视频数据&#xff0c;通过深度学习模…

科技前沿:IDEA插件Translation v3.6 带来革命性更新,翻译和发音更智能!

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

leetcode124 二叉树中的最大路径和-dp

题目 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root &…