Dex2oat support for multiple oat file and image file outputs.

Multiple changes to dex2oat and the runtime to support a --multi-image
option. This generates a separate oat file and image file output for
each dex file input.

Change-Id: Ie1d6f0b8afa8aed5790065b8c2eb177990c60129
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 342e1d9..0518911 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -330,7 +330,7 @@
   Runtime* const runtime = Runtime::Current();
   gc::Heap* const heap = runtime->GetHeap();
 
-  CHECK(!heap->HasImageSpace()) << "Runtime has image. We should use it.";
+  CHECK(!heap->HasBootImageSpace()) << "Runtime has image. We should use it.";
   CHECK(!init_done_);
 
   // Use the pointer size from the runtime since we are probably creating the image.
@@ -736,7 +736,7 @@
 
 static void SanityCheckArtMethod(ArtMethod* m,
                                  mirror::Class* expected_class,
-                                 gc::space::ImageSpace* space)
+                                 std::vector<gc::space::ImageSpace*> spaces)
     SHARED_REQUIRES(Locks::mutator_lock_) {
   if (m->IsRuntimeMethod()) {
     CHECK(m->GetDeclaringClass() == nullptr) << PrettyMethod(m);
@@ -745,18 +745,22 @@
   } else if (expected_class != nullptr) {
     CHECK_EQ(m->GetDeclaringClassUnchecked(), expected_class) << PrettyMethod(m);
   }
-  if (space != nullptr) {
-    auto& header = space->GetImageHeader();
-    auto& methods = header.GetMethodsSection();
-    auto offset = reinterpret_cast<uint8_t*>(m) - space->Begin();
-    CHECK(methods.Contains(offset)) << m << " not in " << methods;
+  if (!spaces.empty()) {
+    bool contains = false;
+    for (gc::space::ImageSpace* space : spaces) {
+      auto& header = space->GetImageHeader();
+      auto& methods = header.GetMethodsSection();
+      auto offset = reinterpret_cast<uint8_t*>(m) - space->Begin();
+      contains |= methods.Contains(offset);
+    }
+    CHECK(contains) << m << " not found";
   }
 }
 
 static void SanityCheckArtMethodPointerArray(mirror::PointerArray* arr,
                                              mirror::Class* expected_class,
                                              size_t pointer_size,
-                                             gc::space::ImageSpace* space)
+                                             std::vector<gc::space::ImageSpace*> spaces)
     SHARED_REQUIRES(Locks::mutator_lock_) {
   CHECK(arr != nullptr);
   for (int32_t j = 0; j < arr->GetLength(); ++j) {
@@ -766,11 +770,12 @@
       CHECK(method != nullptr);
     }
     if (method != nullptr) {
-      SanityCheckArtMethod(method, expected_class, space);
+      SanityCheckArtMethod(method, expected_class, spaces);
     }
   }
 }
 
+/* TODO: Modify check to support multiple image spaces and reenable. b/26317072
 static void SanityCheckArtMethodPointerArray(
     ArtMethod** arr,
     size_t size,
@@ -790,6 +795,7 @@
     }
   }
 }
+*/
 
 static void SanityCheckObjectsCallback(mirror::Object* obj, void* arg ATTRIBUTE_UNUSED)
     SHARED_REQUIRES(Locks::mutator_lock_) {
@@ -805,29 +811,30 @@
       CHECK_EQ(field.GetDeclaringClass(), klass);
     }
     auto* runtime = Runtime::Current();
-    auto* image_space = runtime->GetHeap()->GetBootImageSpace();
+    auto image_spaces = runtime->GetHeap()->GetBootImageSpaces();
     auto pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
     for (auto& m : klass->GetMethods(pointer_size)) {
-      SanityCheckArtMethod(&m, klass, image_space);
+      SanityCheckArtMethod(&m, klass, image_spaces);
     }
     auto* vtable = klass->GetVTable();
     if (vtable != nullptr) {
-      SanityCheckArtMethodPointerArray(vtable, nullptr, pointer_size, image_space);
+      SanityCheckArtMethodPointerArray(vtable, nullptr, pointer_size, image_spaces);
     }
     if (klass->ShouldHaveEmbeddedImtAndVTable()) {
       for (size_t i = 0; i < mirror::Class::kImtSize; ++i) {
-        SanityCheckArtMethod(klass->GetEmbeddedImTableEntry(i, pointer_size), nullptr, image_space);
+        SanityCheckArtMethod(
+            klass->GetEmbeddedImTableEntry(i, pointer_size), nullptr, image_spaces);
       }
       for (int32_t i = 0; i < klass->GetEmbeddedVTableLength(); ++i) {
-        SanityCheckArtMethod(klass->GetEmbeddedVTableEntry(i, pointer_size), nullptr, image_space);
+        SanityCheckArtMethod(klass->GetEmbeddedVTableEntry(i, pointer_size), nullptr, image_spaces);
       }
     }
     auto* iftable = klass->GetIfTable();
     if (iftable != nullptr) {
       for (int32_t i = 0; i < klass->GetIfTableCount(); ++i) {
         if (iftable->GetMethodArrayCount(i) > 0) {
-          SanityCheckArtMethodPointerArray(iftable->GetMethodArray(i), nullptr, pointer_size,
-                                           image_space);
+          SanityCheckArtMethodPointerArray(
+              iftable->GetMethodArray(i), nullptr, pointer_size, image_spaces);
         }
       }
     }
@@ -856,6 +863,33 @@
   DISALLOW_COPY_AND_ASSIGN(SetInterpreterEntrypointArtMethodVisitor);
 };
 
+struct TrampolineCheckData {
+  const void* quick_resolution_trampoline;
+  const void* quick_imt_conflict_trampoline;
+  const void* quick_generic_jni_trampoline;
+  const void* quick_to_interpreter_bridge_trampoline;
+  size_t pointer_size;
+  ArtMethod* m;
+  bool error;
+};
+static void CheckTrampolines(mirror::Object* obj, void* arg) NO_THREAD_SAFETY_ANALYSIS {
+  if (obj->IsClass()) {
+    mirror::Class* klass = obj->AsClass();
+    TrampolineCheckData* d = reinterpret_cast<TrampolineCheckData*>(arg);
+    for (ArtMethod& m : klass->GetMethods(d->pointer_size)) {
+      const void* entrypoint = m.GetEntryPointFromQuickCompiledCodePtrSize(d->pointer_size);
+      if (entrypoint == d->quick_resolution_trampoline ||
+          entrypoint == d->quick_imt_conflict_trampoline ||
+          entrypoint == d->quick_generic_jni_trampoline ||
+          entrypoint == d->quick_to_interpreter_bridge_trampoline) {
+        d->m = &m;
+        d->error = true;
+        return;
+      }
+    }
+  }
+}
+
 bool ClassLinker::InitFromImage(std::string* error_msg) {
   VLOG(startup) << "ClassLinker::InitFromImage entering";
   CHECK(!init_done_);
@@ -863,28 +897,71 @@
   Runtime* const runtime = Runtime::Current();
   Thread* const self = Thread::Current();
   gc::Heap* const heap = runtime->GetHeap();
-  gc::space::ImageSpace* const space = heap->GetBootImageSpace();
-  CHECK(space != nullptr);
-  image_pointer_size_ = space->GetImageHeader().GetPointerSize();
+  std::vector<gc::space::ImageSpace*> spaces = heap->GetBootImageSpaces();
+  CHECK(!spaces.empty());
+  image_pointer_size_ = spaces[0]->GetImageHeader().GetPointerSize();
   dex_cache_boot_image_class_lookup_required_ = true;
-  const OatFile* oat_file = runtime->GetOatFileManager().RegisterImageOatFile(space);
-  DCHECK(oat_file != nullptr);
-  CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatChecksum(), 0U);
-  CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatDataBegin(), 0U);
-  const char* image_file_location = oat_file->GetOatHeader().
+  std::vector<const OatFile*> oat_files =
+      runtime->GetOatFileManager().RegisterImageOatFiles(spaces);
+  DCHECK(!oat_files.empty());
+  const OatHeader& default_oat_header = oat_files[0]->GetOatHeader();
+  CHECK_EQ(default_oat_header.GetImageFileLocationOatChecksum(), 0U);
+  CHECK_EQ(default_oat_header.GetImageFileLocationOatDataBegin(), 0U);
+  const char* image_file_location = oat_files[0]->GetOatHeader().
       GetStoreValueByKey(OatHeader::kImageLocationKey);
   CHECK(image_file_location == nullptr || *image_file_location == 0);
-  quick_resolution_trampoline_ = oat_file->GetOatHeader().GetQuickResolutionTrampoline();
-  quick_imt_conflict_trampoline_ = oat_file->GetOatHeader().GetQuickImtConflictTrampoline();
-  quick_generic_jni_trampoline_ = oat_file->GetOatHeader().GetQuickGenericJniTrampoline();
-  quick_to_interpreter_bridge_trampoline_ = oat_file->GetOatHeader().GetQuickToInterpreterBridge();
-  StackHandleScope<2> hs(self);
-  mirror::Object* dex_caches_object = space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(
-      hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
+  quick_resolution_trampoline_ = default_oat_header.GetQuickResolutionTrampoline();
+  quick_imt_conflict_trampoline_ = default_oat_header.GetQuickImtConflictTrampoline();
+  quick_generic_jni_trampoline_ = default_oat_header.GetQuickGenericJniTrampoline();
+  quick_to_interpreter_bridge_trampoline_ = default_oat_header.GetQuickToInterpreterBridge();
+  if (kIsDebugBuild) {
+    // Check that the other images use the same trampoline.
+    for (size_t i = 1; i < oat_files.size(); ++i) {
+      const OatHeader& ith_oat_header = oat_files[i]->GetOatHeader();
+      const void* ith_quick_resolution_trampoline =
+          ith_oat_header.GetQuickResolutionTrampoline();
+      const void* ith_quick_imt_conflict_trampoline =
+          ith_oat_header.GetQuickImtConflictTrampoline();
+      const void* ith_quick_generic_jni_trampoline =
+          ith_oat_header.GetQuickGenericJniTrampoline();
+      const void* ith_quick_to_interpreter_bridge_trampoline =
+          ith_oat_header.GetQuickToInterpreterBridge();
+      if (ith_quick_resolution_trampoline != quick_resolution_trampoline_ ||
+          ith_quick_imt_conflict_trampoline != quick_imt_conflict_trampoline_ ||
+          ith_quick_generic_jni_trampoline != quick_generic_jni_trampoline_ ||
+          ith_quick_to_interpreter_bridge_trampoline != quick_to_interpreter_bridge_trampoline_) {
+        // Make sure that all methods in this image do not contain those trampolines as
+        // entrypoints. Otherwise the class-linker won't be able to work with a single set.
+        TrampolineCheckData data;
+        data.error = false;
+        data.pointer_size = GetImagePointerSize();
+        data.quick_resolution_trampoline = ith_quick_resolution_trampoline;
+        data.quick_imt_conflict_trampoline = ith_quick_imt_conflict_trampoline;
+        data.quick_generic_jni_trampoline = ith_quick_generic_jni_trampoline;
+        data.quick_to_interpreter_bridge_trampoline = ith_quick_to_interpreter_bridge_trampoline;
+        ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
+        spaces[i]->GetLiveBitmap()->Walk(CheckTrampolines, &data);
+        if (data.error) {
+          ArtMethod* m = data.m;
+          LOG(ERROR) << "Found a broken ArtMethod: " << PrettyMethod(m);
+          *error_msg = "Found an ArtMethod with a bad entrypoint";
+          return false;
+        }
+      }
+    }
+  }
 
-  Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
-      space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
+  StackHandleScopeCollection handles(self);
+  std::vector<Handle<mirror::ObjectArray<mirror::DexCache>>> dex_caches_vector;
+  for (gc::space::ImageSpace* space : spaces) {
+    Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(handles.NewHandle(
+        space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches)->
+        AsObjectArray<mirror::DexCache>()));
+    dex_caches_vector.push_back(dex_caches);
+  }
+
+  Handle<mirror::ObjectArray<mirror::Class>> class_roots(handles.NewHandle(
+      spaces[0]->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
       AsObjectArray<mirror::Class>()));
   class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(class_roots.Get());
 
@@ -896,56 +973,70 @@
   java_lang_Object->SetObjectSize(sizeof(mirror::Object));
   // Allocate in non-movable so that it's possible to check if a JNI weak global ref has been
   // cleared without triggering the read barrier and unintentionally mark the sentinel alive.
-  runtime->SetSentinel(heap->AllocNonMovableObject<true>(self,
-                                                         java_lang_Object,
-                                                         java_lang_Object->GetObjectSize(),
-                                                         VoidFunctor()));
+  runtime->SetSentinel(heap->AllocNonMovableObject<true>(
+      self, java_lang_Object, java_lang_Object->GetObjectSize(), VoidFunctor()));
 
-  if (oat_file->GetOatHeader().GetDexFileCount() !=
-      static_cast<uint32_t>(dex_caches->GetLength())) {
+  uint32_t dex_file_count = 0;
+  for (const OatFile* oat_file : oat_files) {
+    dex_file_count += oat_file->GetOatHeader().GetDexFileCount();
+  }
+  uint32_t dex_caches_count = 0;
+  for (auto dex_caches : dex_caches_vector) {
+    dex_caches_count += dex_caches->GetLength();
+  }
+  if (dex_file_count != dex_caches_count) {
     *error_msg = "Dex cache count and dex file count mismatch while trying to initialize from "
                  "image";
     return false;
   }
-  for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-    StackHandleScope<1> hs2(self);
-    Handle<mirror::DexCache> dex_cache(hs2.NewHandle(dex_caches->Get(i)));
-    const std::string& dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8());
-    const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file_location.c_str(),
-                                                                      nullptr);
-    if (oat_dex_file == nullptr) {
-      *error_msg = StringPrintf("Failed finding oat dex file for %s %s",
-                                oat_file->GetLocation().c_str(),
-                                dex_file_location.c_str());
-      return false;
-    }
-    std::string inner_error_msg;
-    std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&inner_error_msg);
-    if (dex_file == nullptr) {
-      *error_msg = StringPrintf("Failed to open dex file %s from within oat file %s error '%s'",
-                                dex_file_location.c_str(),
-                                oat_file->GetLocation().c_str(),
-                                inner_error_msg.c_str());
-      return false;
-    }
+  for (auto dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+      StackHandleScope<1> hs2(self);
+      Handle<mirror::DexCache> dex_cache(hs2.NewHandle(dex_caches->Get(i)));
+      const std::string& dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8());
+      const OatFile::OatDexFile* oat_dex_file = nullptr;
+      for (const OatFile* oat_file : oat_files) {
+        const OatFile::OatDexFile* oat_dex =
+            oat_file->GetOatDexFile(dex_file_location.c_str(), nullptr, false);
+        if (oat_dex != nullptr) {
+          DCHECK(oat_dex_file == nullptr);
+          oat_dex_file = oat_dex;
+        }
+      }
 
-    if (kSanityCheckObjects) {
-      SanityCheckArtMethodPointerArray(dex_cache->GetResolvedMethods(),
-                                       dex_cache->NumResolvedMethods(),
-                                       image_pointer_size_,
-                                       space);
-    }
+      if (oat_dex_file == nullptr) {
+        *error_msg = StringPrintf("Failed finding oat dex file for %s",
+                                  dex_file_location.c_str());
+        return false;
+      }
+      std::string inner_error_msg;
+      std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&inner_error_msg);
+      if (dex_file == nullptr) {
+        *error_msg = StringPrintf("Failed to open dex file %s error '%s'",
+                                  dex_file_location.c_str(),
+                                  inner_error_msg.c_str());
+        return false;
+      }
 
-    if (dex_file->GetLocationChecksum() != oat_dex_file->GetDexFileLocationChecksum()) {
-      *error_msg = StringPrintf("Checksums do not match for %s: %x vs %x",
-                                dex_file_location.c_str(),
-                                dex_file->GetLocationChecksum(),
-                                oat_dex_file->GetDexFileLocationChecksum());
-      return false;
-    }
+      // TODO: Modify check to support multiple image spaces and reenable.
+//      if (kSanityCheckObjects) {
+//        SanityCheckArtMethodPointerArray(dex_cache->GetResolvedMethods(),
+//                                         dex_cache->NumResolvedMethods(),
+//                                         image_pointer_size_,
+//                                         spaces);
+//      }
 
-    AppendToBootClassPath(*dex_file.get(), dex_cache);
-    opened_dex_files_.push_back(std::move(dex_file));
+      if (dex_file->GetLocationChecksum() != oat_dex_file->GetDexFileLocationChecksum()) {
+        *error_msg = StringPrintf("Checksums do not match for %s: %x vs %x",
+                                  dex_file_location.c_str(),
+                                  dex_file->GetLocationChecksum(),
+                                  oat_dex_file->GetDexFileLocationChecksum());
+        return false;
+      }
+
+      AppendToBootClassPath(*dex_file.get(), dex_cache);
+      opened_dex_files_.push_back(std::move(dex_file));
+    }
   }
 
   if (!ValidPointerSize(image_pointer_size_)) {
@@ -968,12 +1059,14 @@
   }
 
   if (kSanityCheckObjects) {
-    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-      auto* dex_cache = dex_caches->Get(i);
-      for (size_t j = 0; j < dex_cache->NumResolvedFields(); ++j) {
-        auto* field = dex_cache->GetResolvedField(j, image_pointer_size_);
-        if (field != nullptr) {
-          CHECK(field->GetDeclaringClass()->GetClass() != nullptr);
+    for (auto dex_caches : dex_caches_vector) {
+      for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+        auto* dex_cache = dex_caches->Get(i);
+        for (size_t j = 0; j < dex_cache->NumResolvedFields(); ++j) {
+          auto* field = dex_cache->GetResolvedField(j, image_pointer_size_);
+          if (field != nullptr) {
+            CHECK(field->GetDeclaringClass()->GetClass() != nullptr);
+          }
         }
       }
     }
@@ -982,10 +1075,12 @@
 
   // Set entry point to interpreter if in InterpretOnly mode.
   if (!runtime->IsAotCompiler() && runtime->GetInstrumentation()->InterpretOnly()) {
-    const ImageHeader& header = space->GetImageHeader();
-    const ImageSection& methods = header.GetMethodsSection();
-    SetInterpreterEntrypointArtMethodVisitor visitor(image_pointer_size_);
-    methods.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
+    for (gc::space::ImageSpace* space : spaces) {
+      const ImageHeader& header = space->GetImageHeader();
+      const ImageSection& methods = header.GetMethodsSection();
+      SetInterpreterEntrypointArtMethodVisitor visitor(image_pointer_size_);
+      methods.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
+    }
   }
 
   // reinit class_roots_
@@ -1014,13 +1109,15 @@
   mirror::Throwable::SetClass(GetClassRoot(kJavaLangThrowable));
   mirror::StackTraceElement::SetClass(GetClassRoot(kJavaLangStackTraceElement));
 
-  const ImageHeader& header = space->GetImageHeader();
-  const ImageSection& section = header.GetImageSection(ImageHeader::kSectionClassTable);
-  if (section.Size() > 0u) {
-    WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-    ClassTable* const class_table = InsertClassTableForClassLoader(nullptr);
-    class_table->ReadFromMemory(space->Begin() + section.Offset());
-    dex_cache_boot_image_class_lookup_required_ = false;
+  for (gc::space::ImageSpace* space : spaces) {
+    const ImageHeader& header = space->GetImageHeader();
+    const ImageSection& section = header.GetImageSection(ImageHeader::kSectionClassTable);
+    if (section.Size() > 0u) {
+      WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+      ClassTable* const class_table = InsertClassTableForClassLoader(nullptr);
+      class_table->ReadFromMemory(space->Begin() + section.Offset());
+      dex_cache_boot_image_class_lookup_required_ = false;
+    }
   }
 
   FinishInit(self);
@@ -1974,7 +2071,7 @@
   }
   Runtime* runtime = Runtime::Current();
   if (!runtime->IsStarted()) {
-    if (runtime->IsAotCompiler() || runtime->GetHeap()->HasImageSpace()) {
+    if (runtime->IsAotCompiler() || runtime->GetHeap()->HasBootImageSpace()) {
       return;  // OAT file unavailable.
     }
   }
@@ -2783,23 +2880,27 @@
   return result;
 }
 
-static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches(gc::space::ImageSpace* image_space)
-    SHARED_REQUIRES(Locks::mutator_lock_) {
-  CHECK(image_space != nullptr);
-  mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  DCHECK(root != nullptr);
-  return root->AsObjectArray<mirror::DexCache>();
+static std::vector<mirror::ObjectArray<mirror::DexCache>*> GetImageDexCaches(
+    std::vector<gc::space::ImageSpace*> image_spaces) SHARED_REQUIRES(Locks::mutator_lock_) {
+  CHECK(!image_spaces.empty());
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector;
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+    DCHECK(root != nullptr);
+    dex_caches_vector.push_back(root->AsObjectArray<mirror::DexCache>());
+  }
+  return dex_caches_vector;
 }
 
 void ClassLinker::AddBootImageClassesToClassTable() {
   if (dex_cache_boot_image_class_lookup_required_) {
-    AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpace(),
+    AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpaces(),
                                 /*class_loader*/nullptr);
     dex_cache_boot_image_class_lookup_required_ = false;
   }
 }
 
-void ClassLinker::AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+void ClassLinker::AddImageClassesToClassTable(std::vector<gc::space::ImageSpace*> image_spaces,
                                               mirror::ClassLoader* class_loader) {
   Thread* self = Thread::Current();
   WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -2807,25 +2908,28 @@
 
   ClassTable* const class_table = InsertClassTableForClassLoader(class_loader);
 
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(image_space);
   std::string temp;
-  for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
-    for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
-      mirror::Class* klass = types[j].Read();
-      if (klass != nullptr) {
-        DCHECK_EQ(klass->GetClassLoader(), class_loader);
-        const char* descriptor = klass->GetDescriptor(&temp);
-        size_t hash = ComputeModifiedUtf8Hash(descriptor);
-        mirror::Class* existing = class_table->Lookup(descriptor, hash);
-        if (existing != nullptr) {
-          CHECK_EQ(existing, klass) << PrettyClassAndClassLoader(existing) << " != "
-              << PrettyClassAndClassLoader(klass);
-        } else {
-          class_table->Insert(klass);
-          if (log_new_class_table_roots_) {
-            new_class_roots_.push_back(GcRoot<mirror::Class>(klass));
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector =
+      GetImageDexCaches(image_spaces);
+  for (mirror::ObjectArray<mirror::DexCache>* dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
+      for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
+        mirror::Class* klass = types[j].Read();
+        if (klass != nullptr) {
+          DCHECK_EQ(klass->GetClassLoader(), class_loader);
+          const char* descriptor = klass->GetDescriptor(&temp);
+          size_t hash = ComputeModifiedUtf8Hash(descriptor);
+          mirror::Class* existing = class_table->Lookup(descriptor, hash);
+          if (existing != nullptr) {
+            CHECK_EQ(existing, klass) << PrettyClassAndClassLoader(existing) << " != "
+                << PrettyClassAndClassLoader(klass);
+          } else {
+            class_table->Insert(klass);
+            if (log_new_class_table_roots_) {
+              new_class_roots_.push_back(GcRoot<mirror::Class>(klass));
+            }
           }
         }
       }
@@ -2856,18 +2960,20 @@
 
 mirror::Class* ClassLinker::LookupClassFromBootImage(const char* descriptor) {
   ScopedAssertNoThreadSuspension ants(Thread::Current(), "Image class lookup");
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(
-      Runtime::Current()->GetHeap()->GetBootImageSpace());
-  for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    const DexFile* dex_file = dex_cache->GetDexFile();
-    // Try binary searching the type index by descriptor.
-    const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor);
-    if (type_id != nullptr) {
-      uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id);
-      mirror::Class* klass = dex_cache->GetResolvedType(type_idx);
-      if (klass != nullptr) {
-        return klass;
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector =
+      GetImageDexCaches(Runtime::Current()->GetHeap()->GetBootImageSpaces());
+  for (mirror::ObjectArray<mirror::DexCache>* dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      // Try binary searching the type index by descriptor.
+      const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor);
+      if (type_id != nullptr) {
+        uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id);
+        mirror::Class* klass = dex_cache->GetResolvedType(type_idx);
+        if (klass != nullptr) {
+          return klass;
+        }
       }
     }
   }
@@ -3167,7 +3273,7 @@
   // the runtime isn't started. On the other hand, app classes can be re-verified even if they are
   // already pre-opted, as then the runtime is started.
   if (!Runtime::Current()->IsAotCompiler() &&
-      !Runtime::Current()->GetHeap()->HasImageSpace() &&
+      !Runtime::Current()->GetHeap()->HasBootImageSpace() &&
       klass->GetClassLoader() != nullptr) {
     return false;
   }
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index f16fe92..9d432c6 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -505,7 +505,7 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Add image classes to the class table.
-  void AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+  void AddImageClassesToClassTable(std::vector<gc::space::ImageSpace*> image_spaces,
                                    mirror::ClassLoader* class_loader)
       REQUIRES(!Locks::classlinker_classes_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index 8f7bb94..d16afd9 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -487,7 +487,9 @@
 
 // Mark all references to the alloc space(s).
 void ModUnionTableCardCache::UpdateAndMarkReferences(MarkObjectVisitor* visitor) {
-  auto* image_space = heap_->GetBootImageSpace();
+  // TODO: Needs better support for multi-images? b/26317072
+  space::ImageSpace* image_space =
+      heap_->GetBootImageSpaces().empty() ? nullptr : heap_->GetBootImageSpaces()[0];
   // If we don't have an image space, just pass in space_ as the immune space. Pass in the same
   // space_ instead of image_space to avoid a null check in ModUnionUpdateObjectReferencesVisitor.
   CardBitVisitor bit_visitor(visitor, space_, image_space != nullptr ? image_space : space_,
diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h
index 3be7181..61c67f8 100644
--- a/runtime/gc/accounting/space_bitmap-inl.h
+++ b/runtime/gc/accounting/space_bitmap-inl.h
@@ -167,7 +167,10 @@
   uintptr_t* address = &bitmap_begin_[index];
   uintptr_t old_word = *address;
   if (kSetBit) {
-    *address = old_word | mask;
+    if ((old_word & mask) == 0) {
+      // Avoid dirtying the page if possible.
+      *address = old_word | mask;
+    }
   } else {
     *address = old_word & ~mask;
   }
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 6d72f31..3e432c7 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -233,8 +233,7 @@
       backtrace_lock_(nullptr),
       seen_backtrace_count_(0u),
       unique_backtrace_count_(0u),
-      gc_disabled_for_shutdown_(false),
-      boot_image_space_(nullptr) {
+      gc_disabled_for_shutdown_(false) {
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "Heap() entering";
   }
@@ -260,23 +259,107 @@
     CHECK_GE(300 * MB, non_moving_space_capacity);
     requested_alloc_space_begin = reinterpret_cast<uint8_t*>(300 * MB) - non_moving_space_capacity;
   }
+
+  // Load image space(s).
   if (!image_file_name.empty()) {
-    ATRACE_BEGIN("ImageSpace::Create");
-    std::string error_msg;
-    boot_image_space_ = space::ImageSpace::Create(image_file_name.c_str(),
-                                                  image_instruction_set,
-                                                  &error_msg);
-    ATRACE_END();
-    if (boot_image_space_ != nullptr) {
-      AddSpace(boot_image_space_);
-      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
-      // isn't going to get in the middle
-      uint8_t* oat_file_end_addr = boot_image_space_->GetImageHeader().GetOatFileEnd();
-      CHECK_GT(oat_file_end_addr, boot_image_space_->End());
-      requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
-    } else {
-      LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. "
-                   << "Attempting to fall back to imageless running. Error was: " << error_msg;
+    // For code reuse, handle this like a work queue.
+    std::vector<std::string> image_file_names;
+    image_file_names.push_back(image_file_name);
+
+    for (size_t index = 0; index < image_file_names.size(); ++index) {
+      std::string& image_name = image_file_names[index];
+      ATRACE_BEGIN("ImageSpace::Create");
+      std::string error_msg;
+      space::ImageSpace* boot_image_space = space::ImageSpace::Create(image_name.c_str(),
+                                                                      image_instruction_set,
+                                                                      index > 0,
+                                                                      &error_msg);
+      ATRACE_END();
+      if (boot_image_space != nullptr) {
+        AddSpace(boot_image_space);
+        // Oat files referenced by image files immediately follow them in memory, ensure alloc space
+        // isn't going to get in the middle
+        uint8_t* oat_file_end_addr = boot_image_space->GetImageHeader().GetOatFileEnd();
+        CHECK_GT(oat_file_end_addr, boot_image_space->End());
+        requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
+        boot_image_spaces_.push_back(boot_image_space);
+
+        if (index == 0) {
+          // If this was the first space, check whether there are more images to load.
+          const OatFile* boot_oat_file = boot_image_space->GetOatFile();
+          if (boot_oat_file == nullptr) {
+            continue;
+          }
+
+          const OatHeader& boot_oat_header = boot_oat_file->GetOatHeader();
+          const char* boot_classpath =
+              boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+          if (boot_classpath == nullptr) {
+            continue;
+          }
+
+          std::vector<std::string> images;
+          Split(boot_classpath, ':', &images);
+
+          // Add the rest into the list. We have to adjust locations, possibly:
+          //
+          // For example, image_file_name is /a/b/c/d/e.art
+          //              images[0] is          f/c/d/e.art
+          // ----------------------------------------------
+          //              images[1] is          g/h/i/j.art  -> /a/b/h/i/j.art
+
+          // Derive pattern.
+          std::vector<std::string> left;
+          Split(image_file_name, '/', &left);
+          std::vector<std::string> right;
+          Split(images[0], '/', &right);
+
+          size_t common = 1;
+          while (common < left.size() && common < right.size()) {
+            if (left[left.size() - common - 1] != right[right.size() - common - 1]) {
+              break;
+            }
+            common++;
+          }
+
+          std::vector<std::string> prefix_vector(left.begin(), left.end() - common);
+          std::string common_prefix = Join(prefix_vector, '/');
+          if (!common_prefix.empty() && common_prefix[0] != '/' && image_file_name[0] == '/') {
+            common_prefix = "/" + common_prefix;
+          }
+
+          // Apply pattern to images[1] .. images[n].
+          for (size_t i = 1; i < images.size(); ++i) {
+            std::string image = images[i];
+
+            size_t rslash = std::string::npos;
+            for (size_t j = 0; j < common; ++j) {
+              if (rslash != std::string::npos) {
+                rslash--;
+              }
+
+              rslash = image.rfind('/', rslash);
+              if (rslash == std::string::npos) {
+                rslash = 0;
+              }
+              if (rslash == 0) {
+                break;
+              }
+            }
+            std::string image_part = image.substr(rslash);
+
+            std::string new_image = common_prefix + (StartsWith(image_part, "/") ? "" : "/") +
+                image_part;
+            image_file_names.push_back(new_image);
+          }
+        }
+      } else {
+        LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. "
+            << "Attempting to fall back to imageless running. Error was: " << error_msg
+            << "\nAttempted image: " << image_name;
+        // TODO: Remove already loaded spaces.
+        break;
+      }
     }
   }
   /*
@@ -456,13 +539,15 @@
     rb_table_.reset(new accounting::ReadBarrierTable());
     DCHECK(rb_table_->IsAllCleared());
   }
-  if (GetBootImageSpace() != nullptr) {
+  if (HasBootImageSpace()) {
     // Don't add the image mod union table if we are running without an image, this can crash if
     // we use the CardCache implementation.
-    accounting::ModUnionTable* mod_union_table = new accounting::ModUnionTableToZygoteAllocspace(
-        "Image mod-union table", this, GetBootImageSpace());
-    CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
-    AddModUnionTable(mod_union_table);
+    for (space::ImageSpace* image_space : GetBootImageSpaces()) {
+      accounting::ModUnionTable* mod_union_table = new accounting::ModUnionTableToZygoteAllocspace(
+          "Image mod-union table", this, image_space);
+      CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
+      AddModUnionTable(mod_union_table);
+    }
   }
   if (collector::SemiSpace::kUseRememberedSet && non_moving_space_ != main_space_) {
     accounting::RememberedSet* non_moving_space_rem_set =
@@ -525,11 +610,12 @@
       garbage_collectors_.push_back(mark_compact_collector_);
     }
   }
-  if (GetBootImageSpace() != nullptr && non_moving_space_ != nullptr &&
+  if (!GetBootImageSpaces().empty() && non_moving_space_ != nullptr &&
       (is_zygote || separate_non_moving_space || foreground_collector_type_ == kCollectorTypeGSS)) {
     // Check that there's no gap between the image space and the non moving space so that the
     // immune region won't break (eg. due to a large object allocated in the gap). This is only
     // required when we're the zygote or using GSS.
+    /* TODO: Modify this check to support multi-images. b/26317072
     bool no_gap = MemMap::CheckNoGaps(GetBootImageSpace()->GetMemMap(),
                                       non_moving_space_->GetMemMap());
     if (!no_gap) {
@@ -537,6 +623,7 @@
       MemMap::DumpMaps(LOG(ERROR), true);
       LOG(FATAL) << "There's a gap between the image space and the non-moving space";
     }
+    */
   }
   instrumentation::Instrumentation* const instrumentation = runtime->GetInstrumentation();
   if (gc_stress_mode_) {
@@ -1202,8 +1289,8 @@
   return FindDiscontinuousSpaceFromObject(obj, fail_ok);
 }
 
-space::ImageSpace* Heap::GetBootImageSpace() const {
-  return boot_image_space_;
+std::vector<space::ImageSpace*> Heap::GetBootImageSpaces() const {
+  return boot_image_spaces_;
 }
 
 void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index e23b1a3..e7ea983 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -580,9 +580,8 @@
   // Unbind any bound bitmaps.
   void UnBindBitmaps() REQUIRES(Locks::heap_bitmap_lock_);
 
-  // Returns the boot image space. There may be multiple image spaces, but there is only one boot
-  // image space.
-  space::ImageSpace* GetBootImageSpace() const;
+  // Returns the boot image spaces. There may be multiple boot image spaces.
+  std::vector<space::ImageSpace*> GetBootImageSpaces() const;
 
   // Permenantly disable moving garbage collection.
   void DisableMovingGc() REQUIRES(!*gc_complete_lock_);
@@ -660,8 +659,8 @@
   void RemoveRememberedSet(space::Space* space);
 
   bool IsCompilingBoot() const;
-  bool HasImageSpace() const {
-    return boot_image_space_ != nullptr;
+  bool HasBootImageSpace() const {
+    return !boot_image_spaces_.empty();
   }
 
   ReferenceProcessor* GetReferenceProcessor() {
@@ -1322,8 +1321,8 @@
   // allocating.
   bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_);
 
-  // Boot image space.
-  space::ImageSpace* boot_image_space_;
+  // Boot image spaces.
+  std::vector<space::ImageSpace*> boot_image_spaces_;
 
   friend class CollectorTransitionTask;
   friend class collector::GarbageCollector;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 8f67c21..952759c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -43,12 +43,17 @@
 
 Atomic<uint32_t> ImageSpace::bitmap_index_(0);
 
-ImageSpace::ImageSpace(const std::string& image_filename, const char* image_location,
-                       MemMap* mem_map, accounting::ContinuousSpaceBitmap* live_bitmap,
-                       uint8_t* end)
+ImageSpace::ImageSpace(const std::string& image_filename,
+                       const char* image_location,
+                       MemMap* mem_map,
+                       accounting::ContinuousSpaceBitmap* live_bitmap,
+                       uint8_t* end,
+                       MemMap* shadow_map)
     : MemMapSpace(image_filename, mem_map, mem_map->Begin(), end, end,
                   kGcRetentionPolicyNeverCollect),
-      image_location_(image_location) {
+      oat_file_non_owned_(nullptr),
+      image_location_(image_location),
+      shadow_map_(shadow_map) {
   DCHECK(live_bitmap != nullptr);
   live_bitmap_.reset(live_bitmap);
 }
@@ -470,6 +475,7 @@
 
 ImageSpace* ImageSpace::Create(const char* image_location,
                                const InstructionSet image_isa,
+                               bool secondary_image,
                                std::string* error_msg) {
   std::string system_filename;
   bool has_system = false;
@@ -481,7 +487,7 @@
                                              &has_system, &cache_filename, &dalvik_cache_exists,
                                              &has_cache, &is_global_cache);
 
-  if (Runtime::Current()->IsZygote()) {
+  if (Runtime::Current()->IsZygote() && !secondary_image) {
     MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
   }
 
@@ -686,7 +692,7 @@
     return nullptr;
   }
 
-  if (kIsDebugBuild) {
+  if (VLOG_IS_ON(startup)) {
     LOG(INFO) << "Dumping image sections";
     for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
       const auto section_idx = static_cast<ImageHeader::ImageSections>(i);
@@ -799,11 +805,52 @@
     return nullptr;
   }
 
+  // In case of multi-images, the images are spaced apart so that the bitmaps don't overlap. We
+  // need to reserve the slack, as otherwise the large object space might allocate in there.
+  // TODO: Reconsider the multi-image layout. b/26317072
+  std::unique_ptr<MemMap> shadow_map;
+  {
+    uintptr_t image_begin = reinterpret_cast<uintptr_t>(image_header.GetImageBegin());
+    uintptr_t image_end = RoundUp(image_begin + image_header.GetImageSize(), kPageSize);
+    uintptr_t oat_begin = reinterpret_cast<uintptr_t>(image_header.GetOatFileBegin());
+    if (image_end < oat_begin) {
+      // There's a gap. Could be multi-image, could be the oat file spaced apart. Go ahead and
+      // dummy-reserve the space covered by the bitmap (which will be a shadow that introduces
+      // a gap to the next image).
+      uintptr_t heap_size = bitmap->HeapSize();
+      uintptr_t bitmap_coverage_end = RoundUp(image_begin + heap_size, kPageSize);
+      if (bitmap_coverage_end > image_end) {
+        VLOG(startup) << "Reserving bitmap shadow ["
+                      << std::hex << image_end << ";"
+                      << std::hex << bitmap_coverage_end << ";] (oat file begins at "
+                      << std::hex << oat_begin;
+        // Note: we cannot use MemMap::Dummy here, as that won't reserve the space in 32-bit mode.
+        shadow_map.reset(MemMap::MapAnonymous("Image bitmap shadow",
+                                              reinterpret_cast<uint8_t*>(image_end),
+                                              bitmap_coverage_end - image_end,
+                                              PROT_NONE,
+                                              false,
+                                              false,
+                                              error_msg));
+        if (shadow_map == nullptr) {
+          return nullptr;
+        }
+        // madvise it away, we don't really want it, just reserve the address space.
+        // TODO: Should we use MadviseDontNeedAndZero? b/26317072
+        madvise(shadow_map->BaseBegin(), shadow_map->BaseSize(), MADV_DONTNEED);
+      }
+    }
+  }
+
   // We only want the mirror object, not the ArtFields and ArtMethods.
   uint8_t* const image_end =
       map->Begin() + image_header.GetImageSection(ImageHeader::kSectionObjects).End();
-  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, image_location,
-                                                   map.release(), bitmap.release(), image_end));
+  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
+                                                   image_location,
+                                                   map.release(),
+                                                   bitmap.release(),
+                                                   image_end,
+                                                   shadow_map.release()));
 
   // VerifyImageAllocations() will be called later in Runtime::Init()
   // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_
@@ -826,16 +873,18 @@
   Runtime* runtime = Runtime::Current();
   runtime->SetInstructionSet(space->oat_file_->GetOatHeader().GetInstructionSet());
 
-  runtime->SetResolutionMethod(image_header.GetImageMethod(ImageHeader::kResolutionMethod));
-  runtime->SetImtConflictMethod(image_header.GetImageMethod(ImageHeader::kImtConflictMethod));
-  runtime->SetImtUnimplementedMethod(
-      image_header.GetImageMethod(ImageHeader::kImtUnimplementedMethod));
-  runtime->SetCalleeSaveMethod(
-      image_header.GetImageMethod(ImageHeader::kCalleeSaveMethod), Runtime::kSaveAll);
-  runtime->SetCalleeSaveMethod(
-      image_header.GetImageMethod(ImageHeader::kRefsOnlySaveMethod), Runtime::kRefsOnly);
-  runtime->SetCalleeSaveMethod(
-      image_header.GetImageMethod(ImageHeader::kRefsAndArgsSaveMethod), Runtime::kRefsAndArgs);
+  if (!runtime->HasResolutionMethod()) {
+    runtime->SetResolutionMethod(image_header.GetImageMethod(ImageHeader::kResolutionMethod));
+    runtime->SetImtConflictMethod(image_header.GetImageMethod(ImageHeader::kImtConflictMethod));
+    runtime->SetImtUnimplementedMethod(
+        image_header.GetImageMethod(ImageHeader::kImtUnimplementedMethod));
+    runtime->SetCalleeSaveMethod(
+        image_header.GetImageMethod(ImageHeader::kCalleeSaveMethod), Runtime::kSaveAll);
+    runtime->SetCalleeSaveMethod(
+        image_header.GetImageMethod(ImageHeader::kRefsOnlySaveMethod), Runtime::kRefsOnly);
+    runtime->SetCalleeSaveMethod(
+        image_header.GetImageMethod(ImageHeader::kRefsAndArgsSaveMethod), Runtime::kRefsAndArgs);
+  }
 
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "ImageSpace::Init exiting (" << PrettyDuration(NanoTime() - start_time)
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index babd672..a54358a 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -43,7 +43,10 @@
   // creation of the alloc space. The ReleaseOatFile will later be
   // used to transfer ownership of the OatFile to the ClassLinker when
   // it is initialized.
-  static ImageSpace* Create(const char* image, InstructionSet image_isa, std::string* error_msg)
+  static ImageSpace* Create(const char* image,
+                            InstructionSet image_isa,
+                            bool secondary_image,
+                            std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Reads the image header from the specified image location for the
@@ -158,8 +161,12 @@
 
   std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap_;
 
-  ImageSpace(const std::string& name, const char* image_location,
-             MemMap* mem_map, accounting::ContinuousSpaceBitmap* live_bitmap, uint8_t* end);
+  ImageSpace(const std::string& name,
+             const char* image_location,
+             MemMap* mem_map,
+             accounting::ContinuousSpaceBitmap* live_bitmap,
+             uint8_t* end,
+             MemMap* shadow_map = nullptr);
 
   // The OatFile associated with the image during early startup to
   // reserve space contiguous to the image. It is later released to
@@ -172,6 +179,10 @@
 
   const std::string image_location_;
 
+  // A MemMap reserving the space of the bitmap "shadow," so that we don't allocate into it. Only
+  // used in the multi-image case.
+  std::unique_ptr<MemMap> shadow_map_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ImageSpace);
 };
diff --git a/runtime/image.cc b/runtime/image.cc
index f8f930b..3856787 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -55,7 +55,6 @@
   CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize));
   CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize));
   CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize));
-  CHECK_LT(image_begin, image_roots);
   CHECK_LT(image_roots, oat_file_begin);
   CHECK_LE(oat_file_begin, oat_data_begin);
   CHECK_LT(oat_data_begin, oat_data_end);
@@ -100,9 +99,6 @@
   if (oat_file_begin_ >= oat_data_begin_) {
     return false;
   }
-  if (image_roots_ <= image_begin_ || oat_file_begin_ <= image_roots_) {
-    return false;
-  }
   if (!IsAligned<kPageSize>(patch_delta_)) {
     return false;
   }
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index e2e4782..d035f5d 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -187,24 +187,27 @@
   if (image_added_to_intern_table_) {
     return nullptr;
   }
-  gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetBootImageSpace();
-  if (image == nullptr) {
+  std::vector<gc::space::ImageSpace*> image_spaces =
+      Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  if (image_spaces.empty()) {
     return nullptr;  // No image present.
   }
-  mirror::Object* root = image->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>();
   const std::string utf8 = s->ToModifiedUtf8();
-  for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    const DexFile* dex_file = dex_cache->GetDexFile();
-    // Binary search the dex file for the string index.
-    const DexFile::StringId* string_id = dex_file->FindStringId(utf8.c_str());
-    if (string_id != nullptr) {
-      uint32_t string_idx = dex_file->GetIndexForStringId(*string_id);
-      // GetResolvedString() contains a RB.
-      mirror::String* image_string = dex_cache->GetResolvedString(string_idx);
-      if (image_string != nullptr) {
-        return image_string;
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+    mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>();
+    for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      // Binary search the dex file for the string index.
+      const DexFile::StringId* string_id = dex_file->FindStringId(utf8.c_str());
+      if (string_id != nullptr) {
+        uint32_t string_idx = dex_file->GetIndexForStringId(*string_id);
+        // GetResolvedString() contains a RB.
+        mirror::String* image_string = dex_cache->GetResolvedString(string_idx);
+        if (image_string != nullptr) {
+          return image_string;
+        }
       }
     }
   }
diff --git a/runtime/oat.h b/runtime/oat.h
index 5ed1977..13fd6a4 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -39,6 +39,7 @@
   static constexpr const char* kPicKey = "pic";
   static constexpr const char* kDebuggableKey = "debuggable";
   static constexpr const char* kClassPathKey = "classpath";
+  static constexpr const char* kBootClassPath = "bootclasspath";
 
   static constexpr const char kTrueValue[] = "true";
   static constexpr const char kFalseValue[] = "false";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 83e594b..e3de14b 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -983,6 +983,7 @@
     LOG(WARNING) << "Failed to find OatDexFile for DexFile " << dex_location
                  << " ( canonical path " << dex_canonical_location << ")"
                  << " with checksum " << checksum << " in OatFile " << GetLocation();
+    /* TODO: Modify for multi-image support and reenable. b/26317072
     if (kIsDebugBuild) {
       for (const OatDexFile* odf : oat_dex_files_storage_) {
         LOG(WARNING) << "OatFile " << GetLocation()
@@ -991,6 +992,7 @@
                      << " with checksum 0x" << std::hex << odf->GetDexFileLocationChecksum();
       }
     }
+    */
   }
 
   return nullptr;
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 0f3a013..cb80cc9 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -846,11 +846,7 @@
 
 std::string OatFileAssistant::ImageLocation() {
   Runtime* runtime = Runtime::Current();
-  const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-  if (image_space == nullptr) {
-    return "";
-  }
-  return image_space->GetImageLocation();
+  return runtime->GetHeap()->GetBootImageSpaces()[0]->GetImageLocation();
 }
 
 const uint32_t* OatFileAssistant::GetRequiredDexChecksum() {
@@ -949,12 +945,13 @@
     image_info_load_attempted_ = true;
 
     Runtime* runtime = Runtime::Current();
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-    if (image_space != nullptr) {
-      cached_image_info_.location = image_space->GetImageLocation();
+    std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces();
+    if (!image_spaces.empty()) {
+      // TODO: Better support multi-images? b/26317072
+      cached_image_info_.location = image_spaces[0]->GetImageLocation();
 
       if (isa_ == kRuntimeISA) {
-        const ImageHeader& image_header = image_space->GetImageHeader();
+        const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
         cached_image_info_.oat_checksum = image_header.GetOatChecksum();
         cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
             image_header.GetOatDataBegin());
@@ -969,7 +966,7 @@
         cached_image_info_.patch_delta = image_header->GetPatchDelta();
       }
     }
-    image_info_load_succeeded_ = (image_space != nullptr);
+    image_info_load_succeeded_ = (!image_spaces.empty());
   }
   return image_info_load_succeeded_ ? &cached_image_info_ : nullptr;
 }
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 40cd50b..f994f0c 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -223,9 +223,10 @@
         false, dex_location.c_str(), &error_msg));
     ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
 
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-    ASSERT_TRUE(image_space != nullptr);
-    const ImageHeader& image_header = image_space->GetImageHeader();
+    const std::vector<gc::space::ImageSpace*> image_spaces =
+        runtime->GetHeap()->GetBootImageSpaces();
+    ASSERT_TRUE(!image_spaces.empty() && image_spaces[0] != nullptr);
+    const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
     const OatHeader& oat_header = odex_file->GetOatHeader();
     EXPECT_FALSE(odex_file->IsPic());
     EXPECT_EQ(image_header.GetOatChecksum(), oat_header.GetImageFileLocationOatChecksum());
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index ea6d3ff..36a967f 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -78,17 +78,23 @@
   return nullptr;
 }
 
-const OatFile* OatFileManager::GetBootOatFile() const {
-  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-  return (image_space == nullptr) ? nullptr : image_space->GetOatFile();
+std::vector<const OatFile*> OatFileManager::GetBootOatFiles() const {
+  std::vector<const OatFile*> oat_files;
+  std::vector<gc::space::ImageSpace*> image_spaces =
+      Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    oat_files.push_back(image_space->GetOatFile());
+  }
+  return oat_files;
 }
 
 const OatFile* OatFileManager::GetPrimaryOatFile() const {
   ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
-  const OatFile* boot_oat_file = GetBootOatFile();
-  if (boot_oat_file != nullptr) {
+  std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
+  if (!boot_oat_files.empty()) {
     for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
-      if (oat_file.get() != boot_oat_file) {
+      if (std::find(boot_oat_files.begin(), boot_oat_files.end(), oat_file.get()) ==
+          boot_oat_files.end()) {
         return oat_file.get();
       }
     }
@@ -102,8 +108,13 @@
   oat_files_.clear();
 }
 
-const OatFile* OatFileManager::RegisterImageOatFile(gc::space::ImageSpace* space) {
-  return RegisterOatFile(space->ReleaseOatFile());
+std::vector<const OatFile*> OatFileManager::RegisterImageOatFiles(
+    std::vector<gc::space::ImageSpace*> spaces) {
+  std::vector<const OatFile*> oat_files;
+  for (gc::space::ImageSpace* space : spaces) {
+    oat_files.push_back(RegisterOatFile(space->ReleaseOatFile()));
+  }
+  return oat_files;
 }
 
 class DexFileAndClassPair : ValueObject {
@@ -213,7 +224,7 @@
   std::priority_queue<DexFileAndClassPair> queue;
 
   // Add dex files from already loaded oat files, but skip boot.
-  const OatFile* boot_oat = GetBootOatFile();
+  std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
   // The same OatFile can be loaded multiple times at different addresses. In this case, we don't
   // need to check both against each other since they would have resolved the same way at compile
   // time.
@@ -221,8 +232,8 @@
   for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) {
     DCHECK_NE(loaded_oat_file.get(), oat_file);
     const std::string& location = loaded_oat_file->GetLocation();
-    if (loaded_oat_file.get() != boot_oat &&
-        location != oat_file->GetLocation() &&
+    if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) ==
+        boot_oat_files.end() && location != oat_file->GetLocation() &&
         unique_locations.find(location) == unique_locations.end()) {
       unique_locations.insert(location);
       AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue);
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index af7efb4..4690e45 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -73,15 +73,15 @@
     return have_non_pic_oat_file_;
   }
 
-  // Returns the boot image oat file.
-  const OatFile* GetBootOatFile() const;
+  // Returns the boot image oat files.
+  std::vector<const OatFile*> GetBootOatFiles() const;
 
   // Returns the first non-image oat file in the class path.
   const OatFile* GetPrimaryOatFile() const REQUIRES(!Locks::oat_file_manager_lock_);
 
-  // Return the oat file for an image, registers the oat file. Takes ownership of the imagespace's
-  // underlying oat file.
-  const OatFile* RegisterImageOatFile(gc::space::ImageSpace* space)
+  // Returns the oat files for the images, registers the oat files.
+  // Takes ownership of the imagespace's underlying oat files.
+  std::vector<const OatFile*> RegisterImageOatFiles(std::vector<gc::space::ImageSpace*> spaces)
       REQUIRES(!Locks::oat_file_manager_lock_);
 
   // Finds or creates the oat file holding dex_location. Then loads and returns
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index 03cad08..5643739 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -44,7 +44,8 @@
     uintptr_t code = reinterpret_cast<uintptr_t>(code_ptr);
     uintptr_t header = code - OFFSETOF_MEMBER(OatQuickMethodHeader, code_);
     DCHECK(IsAlignedParam(code, GetInstructionSetAlignment(kRuntimeISA)) ||
-           IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA)));
+           IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA)))
+        << std::hex << code << " " << std::hex << header;
     return reinterpret_cast<OatQuickMethodHeader*>(header);
   }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index eeaadd4..be64bff 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -547,15 +547,15 @@
   // Use !IsAotCompiler so that we get test coverage, tests are never the zygote.
   if (!IsAotCompiler()) {
     ScopedObjectAccess soa(self);
-    gc::space::ImageSpace* image_space = heap_->GetBootImageSpace();
-    if (image_space != nullptr) {
+    std::vector<gc::space::ImageSpace*> image_spaces = heap_->GetBootImageSpaces();
+    for (gc::space::ImageSpace* image_space : image_spaces) {
       ATRACE_BEGIN("AddImageStringsToTable");
       GetInternTable()->AddImageStringsToTable(image_space);
       ATRACE_END();
-      ATRACE_BEGIN("MoveImageClassesToClassTable");
-      GetClassLinker()->AddBootImageClassesToClassTable();
-      ATRACE_END();
     }
+    ATRACE_BEGIN("MoveImageClassesToClassTable");
+    GetClassLinker()->AddBootImageClassesToClassTable();
+    ATRACE_END();
   }
 
   // If we are the zygote then we need to wait until after forking to create the code cache
@@ -564,7 +564,7 @@
     CreateJit();
   }
 
-  if (!IsImageDex2OatEnabled() || !GetHeap()->HasImageSpace()) {
+  if (!IsImageDex2OatEnabled() || !GetHeap()->HasBootImageSpace()) {
     ScopedObjectAccess soa(self);
     StackHandleScope<1> hs(soa.Self());
     auto klass(hs.NewHandle<mirror::Class>(mirror::Class::GetJavaLangClass()));
@@ -754,61 +754,96 @@
   VLOG(startup) << "Runtime::StartDaemonThreads exiting";
 }
 
+// Attempts to open dex files from image(s). Given the image location, try to find the oat file
+// and open it to get the stored dex file. If the image is the first for a multi-image boot
+// classpath, go on and also open the other images.
 static bool OpenDexFilesFromImage(const std::string& image_location,
                                   std::vector<std::unique_ptr<const DexFile>>* dex_files,
                                   size_t* failures) {
   DCHECK(dex_files != nullptr) << "OpenDexFilesFromImage: out-param is nullptr";
-  std::string system_filename;
-  bool has_system = false;
-  std::string cache_filename_unused;
-  bool dalvik_cache_exists_unused;
-  bool has_cache_unused;
-  bool is_global_cache_unused;
-  bool found_image = gc::space::ImageSpace::FindImageFilename(image_location.c_str(),
-                                                              kRuntimeISA,
-                                                              &system_filename,
-                                                              &has_system,
-                                                              &cache_filename_unused,
-                                                              &dalvik_cache_exists_unused,
-                                                              &has_cache_unused,
-                                                              &is_global_cache_unused);
-  *failures = 0;
-  if (!found_image || !has_system) {
-    return false;
-  }
-  std::string error_msg;
-  // We are falling back to non-executable use of the oat file because patching failed, presumably
-  // due to lack of space.
-  std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(system_filename.c_str());
-  std::string oat_location = ImageHeader::GetOatLocationFromImageLocation(image_location.c_str());
-  std::unique_ptr<File> file(OS::OpenFileForReading(oat_filename.c_str()));
-  if (file.get() == nullptr) {
-    return false;
-  }
-  std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.release(), false, false, &error_msg));
-  if (elf_file.get() == nullptr) {
-    return false;
-  }
-  std::unique_ptr<const OatFile> oat_file(
-      OatFile::OpenWithElfFile(elf_file.release(), oat_location, nullptr, &error_msg));
-  if (oat_file == nullptr) {
-    LOG(WARNING) << "Unable to use '" << oat_filename << "' because " << error_msg;
-    return false;
-  }
 
-  for (const OatFile::OatDexFile* oat_dex_file : oat_file->GetOatDexFiles()) {
-    if (oat_dex_file == nullptr) {
-      *failures += 1;
-      continue;
+  // Use a work-list approach, so that we can easily reuse the opening code.
+  std::vector<std::string> image_locations;
+  image_locations.push_back(image_location);
+
+  for (size_t index = 0; index < image_locations.size(); ++index) {
+    std::string system_filename;
+    bool has_system = false;
+    std::string cache_filename_unused;
+    bool dalvik_cache_exists_unused;
+    bool has_cache_unused;
+    bool is_global_cache_unused;
+    bool found_image = gc::space::ImageSpace::FindImageFilename(image_locations[index].c_str(),
+                                                                kRuntimeISA,
+                                                                &system_filename,
+                                                                &has_system,
+                                                                &cache_filename_unused,
+                                                                &dalvik_cache_exists_unused,
+                                                                &has_cache_unused,
+                                                                &is_global_cache_unused);
+
+    if (!found_image || !has_system) {
+      return false;
     }
-    std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
-    if (dex_file.get() == nullptr) {
-      *failures += 1;
-    } else {
-      dex_files->push_back(std::move(dex_file));
+
+    // We are falling back to non-executable use of the oat file because patching failed, presumably
+    // due to lack of space.
+    std::string oat_filename =
+        ImageHeader::GetOatLocationFromImageLocation(system_filename.c_str());
+    std::string oat_location =
+        ImageHeader::GetOatLocationFromImageLocation(image_locations[index].c_str());
+    // Note: in the multi-image case, the image location may end in ".jar," and not ".art." Handle
+    //       that here.
+    if (EndsWith(oat_location, ".jar")) {
+      oat_location.replace(oat_location.length() - 3, 3, "oat");
     }
+
+    std::unique_ptr<File> file(OS::OpenFileForReading(oat_filename.c_str()));
+    if (file.get() == nullptr) {
+      return false;
+    }
+    std::string error_msg;
+    std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.release(), false, false, &error_msg));
+    if (elf_file.get() == nullptr) {
+      return false;
+    }
+    std::unique_ptr<const OatFile> oat_file(
+        OatFile::OpenWithElfFile(elf_file.release(), oat_location, nullptr, &error_msg));
+    if (oat_file == nullptr) {
+      LOG(WARNING) << "Unable to use '" << oat_filename << "' because " << error_msg;
+      return false;
+    }
+
+    for (const OatFile::OatDexFile* oat_dex_file : oat_file->GetOatDexFiles()) {
+      if (oat_dex_file == nullptr) {
+        *failures += 1;
+        continue;
+      }
+      std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
+      if (dex_file.get() == nullptr) {
+        *failures += 1;
+      } else {
+        dex_files->push_back(std::move(dex_file));
+      }
+    }
+
+    if (index == 0) {
+      // First file. See if this is a multi-image environment, and if so, enqueue the other images.
+      const OatHeader& boot_oat_header = oat_file->GetOatHeader();
+      const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+      if (boot_cp != nullptr) {
+        std::vector<std::string> cp;
+        Split(boot_cp, ':', &cp);
+
+        if (cp.size() > 1) {
+          // More images, enqueue (skipping the first).
+          image_locations.insert(image_locations.end(), cp.begin() + 1, cp.end());
+        }
+      }
+    }
+
+    Runtime::Current()->GetOatFileManager().RegisterOatFile(std::move(oat_file));
   }
-  Runtime::Current()->GetOatFileManager().RegisterOatFile(std::move(oat_file));
   return true;
 }
 
@@ -946,7 +981,7 @@
                        runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));
   ATRACE_END();
 
-  if (heap_->GetBootImageSpace() == nullptr && !allow_dex_file_fallback_) {
+  if (!heap_->HasBootImageSpace() && !allow_dex_file_fallback_) {
     LOG(ERROR) << "Dex file fallback disabled, cannot continue without image.";
     ATRACE_END();
     return false;
@@ -1054,7 +1089,7 @@
 
   CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U);
   class_linker_ = new ClassLinker(intern_table_);
-  if (GetHeap()->HasImageSpace()) {
+  if (GetHeap()->HasBootImageSpace()) {
     ATRACE_BEGIN("InitFromImage");
     std::string error_msg;
     bool result = class_linker_->InitFromImage(&error_msg);
@@ -1063,9 +1098,13 @@
       LOG(ERROR) << "Could not initialize from image: " << error_msg;
       return false;
     }
+    /* TODO: Modify check to support multiple image spaces and reenable. b/26317072
     if (kIsDebugBuild) {
-      GetHeap()->GetBootImageSpace()->VerifyImageAllocations();
+      for (auto image_space : GetHeap()->GetBootImageSpaces()) {
+        image_space->VerifyImageAllocations();
+      }
     }
+    */
     if (boot_class_path_string_.empty()) {
       // The bootclasspath is not explicitly specified: construct it from the loaded dex files.
       const std::vector<const DexFile*>& boot_class_path = GetClassLinker()->GetBootClassPath();