Android ART虚拟机的创建与内存管理简易分析

从计算机的角度看,32位操作系统的每个进程可使用的地址空间有4G之多,而现在普遍的64位系统则有2的64次方字节(理论上的)。当然进程的虚拟地址空间会分成不同的区块(数据代码块、堆、共享库、栈、内核等),这个虚拟地址只是程序调用时的指针,和内存空间并无直接对应关系。比如在fork函数调用后,子进程读取数据时,其虚拟地址会映射到父进程虚拟地址所映射的物理地址,即二者映射到同一物理地址,而在子进程需要写入内存的时候将虚拟地址映射到拷贝内存后的另一段物理地址。

不管是早期的 dalvik 虚拟机还是现在的 ART 运行时,他们都是基于虚拟机垃圾回收的机制来实现堆内存管理的,两者再这个层面上的实现区别不大,本文介绍的是ART虚拟机的大概实现(源码均采用 GoogleSource 上 master 分支最新(2016/11/30)代码)。

zygote

zygote 进程的作用无需多说,在它的main函数里会调用到 AndroidRuntime.cpp 的start方法:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ...
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);
    ...

上面创建的 JniInvocation (platform/libnativehelper/JniInvocation.cpp) 对象会读取系统属性 persist.sys.dalvik.vm.lib.2 的值或者默认的值为 libart.so,通过 dlopen 加载到进程中,并将该so中的JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs三个函数指针导出保存在该对象的三个成员变量中(早期dalvik虚拟机的libdvm.so也实现了这三个方法)。

在 startVm 方法中,首先通过读取系统属性值设置虚拟机的一些参数(比如 heapsize 等),再通过 JNI_CreateJavaVM 方法通过参数initArgs传递给上面所说的 libart.so中的JNI_CreateJavaVM方法:

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    ...
    parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
    parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");

    parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
    parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree=");
    parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree=");
    
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }
    ...

在 ART 的 JNI_CreateJavaVM (art/runtime/java_vm_ext.cc)中,通过读取上述方法传递的参数,传递给Runtime的create方法:

...
RuntimeOptions options;
for (int i = 0; i < args->nOptions; ++i) {
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
}
bool ignore_unrecognized = args->ignoreUnrecognized;
if (!Runtime::Create(options, ignore_unrecognized)) {
    return JNI_ERR;
}
...

在 Runtime 的 create 方法里,会通过 parsed_options.cc 的 Parse 处理启动参数,完成一些传递的参数名到 runtime_options 中 map 具体 key 值的转换:

...
.Define("-Xms_")
      .WithType<MemoryKiB>()
      .IntoKey(M::MemoryInitialSize)
.Define("-Xmx_")
      .WithType<MemoryKiB>()
      .IntoKey(M::MemoryMaximumSize)
...        
if (!args.Exists(M::CompilerCallbacksPtr) && !args.Exists(M::Image)) {
    std::string image = GetAndroidRoot(); //utils.cc重定义,一般为system
    image += "/framework/boot.art";
    args.Set(M::Image, image);
}

// 0 means no growth limit, and growth limit should be always <= heap size
if (args.GetOrDefault(M::HeapGrowthLimit) <= 0u ||
  args.GetOrDefault(M::HeapGrowthLimit) > args.GetOrDefault(M::MemoryMaximumSize)) {
    args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize));
}
...

如果我们通过getprop方法得到系统的属性值,会发现许多时候并没有定义 dalvik.vm.heapgrowthlimit 的值,在这里会将该值设置成 -Xmx 的值,接着根据上面的参数调用 Runtime的 init 方法创建描述虚拟机堆的类型 Heap:

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
    ...
    heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize),
                   runtime_options.GetOrDefault(Opt::HeapGrowthLimit),
                   runtime_options.GetOrDefault(Opt::HeapMinFree),
                   runtime_options.GetOrDefault(Opt::HeapMaxFree),
                   runtime_options.GetOrDefault(Opt::HeapTargetUtilization),
                   runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier),
                   runtime_options.GetOrDefault(Opt::MemoryMaximumSize),
                   runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity),
                   runtime_options.GetOrDefault(Opt::Image),
                   runtime_options.GetOrDefault(Opt::ImageInstructionSet),
    ...

由于在5.0之后引入了 Compacting GC 使得 ART 虚拟机内部的 Heap 的 Space 种类变得比较复杂。而 5.0 之前只有Mark Sweep,虽然后者性能更高,但是难免会产生因为内存碎片过多而导致不能分配大的连续内存空间的问题。

ImageSpace

ImageSpace是 ART 虚拟机创建的第一个 Space,无论采用哪种 GC 回收机制的时候都会创建,在 Heap 类的构造函数中,首先会创建ImageSpace:

Heap::Heap(size_t initial_size,
       size_t growth_limit,
       size_t min_free,
       size_t max_free,
       double target_utilization,
       double foreground_heap_growth_multiplier,
       size_t capacity,   
       size_t non_moving_space_capacity,
       const std::string& image_file_name,
       const InstructionSet image_instruction_set,
    ...
    // Load image space(s).
    if (!image_file_name.empty()) {
        ...
        std::unique_ptr<space::ImageSpace> boot_image_space_uptr = space::ImageSpace::CreateBootImage(image_name.c_str(),
            image_instruction_set,
            index > 0,
            &error_msg);
        if (boot_image_space_uptr != nullptr) {
                space::ImageSpace* boot_image_space = boot_image_space_uptr.release();
                AddSpace(boot_image_space);
    ...

在 Heap 的构造函数里,会判断 image_file_name 参数是否为空,从上述可知该参数为 system/framework/boot.art,然后调用 ImageSpace 类型的 CreateBootImage 创建一个 ImageSpace。在 CreateBootImage 中,首先通过一些进程和路径判断等复杂操作,最终调用到 ImageSpaceLoader 的 Init 方法:

static std::unique_ptr<ImageSpace> Init(const char* image_filename,
                                      const char* image_location,
                                      bool validate_oat_file,
                                      const OatFile* oat_file,
                                      std::string* error_msg)
                                      
...                                    
file.reset(OS::OpenFileForReading(image_filename));
...
bool success = file->ReadFully(image_header, sizeof(*image_header));
...
std::unique_ptr<MemMap> map;
map.reset(LoadImageFile(image_filename,
                        image_location,
                        *image_header,
                        image_header->GetImageBegin(),
                        file->Fd(),
                        logger,
                        error_msg));
...
std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr,
                    bitmap_section.Size(),
                    PROT_READ, MAP_PRIVATE,
                    file->Fd(),
                    image_bitmap_offset,
                    /*low_4gb*/false,
                    /*reuse*/false,
                    image_filename,
                    error_msg));
...
space->oat_file_ = OpenOatFile(*space, image_filename, error_msg);  //实际上是将image_filename的后缀改成oat,即system/framework/boot.oat
...

std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
                                    image_location,
                                    map.release(),
                                    bitmap.release(),
                                    image_end));
...

通过上述代码可知,首先调用 OpenFileForReading 打开该 boot.art 文件,接着读取该文件的 image_header,然后生成一个 MemMap 类型的 map 对象并调用 LoadImageFile (实际上是 MapFileAtAddress 方法) 根据该 Header 中指定的起始地址和大小信息将boot.art 文件映射到内存里,该内存就是 ImageSpace 对象分配的地址上连续的空间,保存的是一些需要预加载的系统类对象。

接下来再次调用 MapFileAtAddress 方法返回一个 image_bitmap_map,该调用根据上述 Header 中指定的 bitmap 地址和偏移地址等将 boot.art 文件中的 Bitmap 信息也映射到内存中,最终会根据返回值调用 ContinuousSpaceBitmap 的 CreateFromMemMap 方法返回一个 live_bitmap, 用来标记上次 GC 后还存活的对象, 该结构内部存储的是一些 Space 中对象的地址。

接着会将 boot.art 对应的 boot.oat 文件也加载到内存中,oat 文件是 Android 私有的 ELF 文件,包含了DEX 文件编译而来的本地机器指定,在这里也指的是系统的预加载类型的信息。

最后根据上述的得到的信息创建一个 ImageSpace 对象,该对象的构造函数如下:

ImageSpace::ImageSpace(const std::string& image_filename,
                   const char* image_location,
                   MemMap* mem_map,
                   accounting::ContinuousSpaceBitmap* live_bitmap,
                   uint8_t* end)
: MemMapSpace(image_filename,
              mem_map,
              mem_map->Begin(),
              end,
              end,
              kGcRetentionPolicyNeverCollect),
  oat_file_non_owned_(nullptr),
  image_location_(image_location) {
  DCHECK(live_bitmap != nullptr);
  live_bitmap_.reset(live_bitmap);
}

到此 ImageSpace 创建完成了,我们可以看到它的回收策略是 kGcRetentionPolicyNeverCollect,代表着永远不会进行垃圾回收,也因为此,该 Space 只有一个 live_bitmap_ 用已标记本次 GC 后存活的对象。而这个ImageSpace的内存空间也会在 zygote fork 应用程序进程的时候得到复用,这样应用程序并不需要实例化许多系统预加载类型。

不同的CollectorType

由于 Compacting GC 需要在垃圾回收的时候对堆内存进行压缩,而代码中一些类型的方法等信息是一直存在的,所以讲他们分配到 Nonmoving Space 是一个更明智的选择。 另外,现在也可以分别设置进程的 foreground_collector_type_ 和 background_collector_type_了,而 CollectorType 也支持多种不同的实现方式,比如 kCollectorTypeSS(semispace)和 kCollectorTypeGSS(generational semispace)以及 kCollectorTypeCC 等。

在 Heap 的构造函数中创建完成 Image Space 后,会根据一些判断是否需要为 NonmovingSpace 创建独立的内存空间,接着会创建两个 MemMap,并根据一些条件判断条件映射到匿名共享内存中,该两个 MemMap 的大小 capacity_ 都为上述所说的启动虚拟机时传入的 dalvik.vm.heapsize 的大小。

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-  nonmoving space (non_moving_space_capacity)+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-????????????????????????????????????????????+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-main alloc space / bump space 1 (capacity_) +-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-                            
+-????????????????????????????????????????????+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+-main alloc space2 / bump space 2 (capacity_)+-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

在这里会根据垃圾回收器的类型创建不同的 Space,常见的是如上图的main alloc space。实际上是 RosAllocSpace 或者 DlMallocSpace,前者似乎是将内存分成很多相同大小的 Bucket,在分配内存时根据所需大小找到合适的 Bucket 已减少碎片,这似乎和 kCollectorTypeCC 模式下所使用的 RegionSpace 有点类似。

Heap 类型持有不同的垃圾回收器,比如 semi_space_collector_(大多数情况下都使用它),它是SemiSpace 的实例,在垃圾回收的时候,会通过设置FromSpace 和 ToSpace,并将FromSpace 中遗留的对象copy之 ToSpace 前方连续的内存中,再讲两者交换已达到内存压缩的目的,而在 kCollectorTypeGSS 模式下,会将多次 GC 存活下来的对象放入 Generational Space 中。

LargeObject

创建完上述的 Space 后,Heap 的构造函数会接着创建 LargeObjectSpace,它现在也支持两者不同的 Space了,分别是FreeListSpace 和 LargeObjectMapSpace, 两者都是非连续的内存空间,代码太多,无力再细看了。

应用程序

应用程序是又相对独立的Activity构成的,而在应用程序进程的入口函数中会执行 ActivityThread 的 attach 方法,该方法会执行如下代码:

BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                if (!mSomeActivitiesChanged) {
                    return;
                }
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                if (dalvikUsed > ((3*dalvikMax)/4)) {
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try {
                        mgr.releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        });

当 dalvikUsed 的值大于 dalvikMax(该值为上面所述的 capacity_ 的大小)的 3/4 的时候,会调用 ActivityManageService 的 releaseSomeActivities 方法,以执行一些后台不可见的 Activity 的 destory 操作。而 runtime.maxMemory() 等都是通过 OpenjdkJvm.cc中定义的 JNI 调用到 Heap 类型的相应方法。

而在BinderInternal中:

static WeakReference<GcWatcher> sGcWatcher
        = new WeakReference<GcWatcher>(new GcWatcher()); 
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;
        
static final class GcWatcher {
    @Override
    protected void finalize() throws Throwable {
        handleGc();
        sLastGcTime = SystemClock.uptimeMillis();
        synchronized (sGcWatchers) {
            sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
        }
        for (int i=0; i<sTmpWatchers.length; i++) {
            if (sTmpWatchers[i] != null) {
                sTmpWatchers[i].run();
            }
        }
        sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
    }
}        

public static void addGcWatcher(Runnable watcher) {
    synchronized (sGcWatchers) {
        sGcWatchers.add(watcher);
    }
}

该类型持有一个 GcWatcher 实例类型的弱引用,在 每次 GC 到来的时候该实例都会因为可回收而放入finalize队列中,当finalize线程会执行该队列中所有对象的finalize方法,从而执行到上述 releaseSomeActivities 方法,之后会重新创建一个 GcWatcher 实例类型的弱引用,已备下次 GC 的时候做判断。

我们看到这的确是一个非常轻巧的设计,而后台 Activity 的销毁也是随时都有可能发生的。我们学习这些,也是为了能理解其中的一些设计思想已使用到我们今后自己构建的系统甚至是业务系统中。

    原文作者:Penner
    原文地址: https://www.jianshu.com/p/4310756f590e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞