Image space cleanup for app images

Removed Heap::GetImageSpace, added Heap::GetBootImageSpace.

Generalized some logic in the class linker for image spaces.

Bug: 22858531

Change-Id: Ib3e12bb061a247e232d3dc93b0d6b35eb3a34d25
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index ecbc3a2..bf3a865 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1552,7 +1552,7 @@
       *type = sharp_type;
     }
   } else {
-    auto* image_space = heap->GetImageSpace();
+    auto* image_space = heap->GetBootImageSpace();
     bool method_in_image = false;
     if (image_space != nullptr) {
       const auto& method_section = image_space->GetImageHeader().GetMethodsSection();
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index a38e1f5..6df1527 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -181,7 +181,7 @@
   ASSERT_TRUE(heap->HasImageSpace());
   ASSERT_TRUE(heap->GetNonMovingSpace()->IsMallocSpace());
 
-  gc::space::ImageSpace* image_space = heap->GetImageSpace();
+  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
   ASSERT_TRUE(image_space != nullptr);
   ASSERT_LE(image_space->Size(), image_file_size);
 
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index dd2ea9e..3f18d9a 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -1589,7 +1589,7 @@
   // If we are compiling an app image, we need to use the stubs of the boot image.
   if (compile_app_image_) {
     // Use the current image pointers.
-    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
     DCHECK(image_space != nullptr);
     const OatFile* oat_file = image_space->GetOatFile();
     CHECK(oat_file != nullptr);
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 3f2271e..40a3f14 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -899,7 +899,7 @@
       // NOTE: We're using linker patches for app->boot references when the image can
       // be relocated and therefore we need to emit .oat_patches. We're not using this
       // for app->app references, so check that the method is an image method.
-      gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+      gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
       size_t method_offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin();
       CHECK(image_space->GetImageHeader().GetMethodsSection().Contains(method_offset));
     }
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 92ed58c..68cf6d9 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1357,7 +1357,7 @@
       int32_t image_patch_delta = 0;
 
       if (app_image_ && image_base_ == 0) {
-        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
         image_base_ = RoundUp(
             reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()),
             kPageSize);
@@ -1370,7 +1370,7 @@
 
       if (!IsBootImage()) {
         TimingLogger::ScopedTiming t3("Loading image checksum", timings_);
-        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
         image_file_location_oat_checksum = image_space->GetImageHeader().GetOatChecksum();
         image_file_location_oat_data_begin =
             reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatDataBegin());
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 304d4e5..5e71053 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -814,7 +814,7 @@
 
   static const ImageHeader& GetBootImageHeader() {
     gc::Heap* heap = Runtime::Current()->GetHeap();
-    gc::space::ImageSpace* image_space = heap->GetImageSpace();
+    gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
     CHECK(image_space != nullptr);
     const ImageHeader& image_header = image_space->GetImageHeader();
     return image_header;
@@ -838,7 +838,7 @@
                      std::ostream* os, pid_t image_diff_pid) {
   ScopedObjectAccess soa(Thread::Current());
   gc::Heap* heap = runtime->GetHeap();
-  gc::space::ImageSpace* image_space = heap->GetImageSpace();
+  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
   CHECK(image_space != nullptr);
   const ImageHeader& image_header = image_space->GetImageHeader();
   if (!image_header.IsValid()) {
diff --git a/imgdiag/imgdiag_test.cc b/imgdiag/imgdiag_test.cc
index 82bc8b9..0d6a8c9 100644
--- a/imgdiag/imgdiag_test.cc
+++ b/imgdiag/imgdiag_test.cc
@@ -42,7 +42,7 @@
     CommonRuntimeTest::SetUp();
 
     // We loaded the runtime with an explicit image. Therefore the image space must exist.
-    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
     ASSERT_TRUE(image_space != nullptr);
     boot_image_location_ = image_space->GetImageLocation();
   }
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index a163380..5a060af 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2374,7 +2374,7 @@
 
   ScopedObjectAccess soa(Thread::Current());
   gc::Heap* heap = runtime->GetHeap();
-  gc::space::ImageSpace* image_space = heap->GetImageSpace();
+  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
   CHECK(image_space != nullptr);
   const ImageHeader& image_header = image_space->GetImageHeader();
   if (!image_header.IsValid()) {
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 64b4722..c587f68 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -189,7 +189,7 @@
     LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg;
     return false;
   }
-  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace();
+  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace();
 
   PatchOat p(isa, image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), delta, timings);
   t.NewTiming("Patching files");
@@ -288,7 +288,7 @@
     LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg;
     return false;
   }
-  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace();
+  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace();
 
   std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat,
                                              PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg));
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index f252c1e..f649972 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -303,7 +303,7 @@
 ClassLinker::ClassLinker(InternTable* intern_table)
     // dex_lock_ is recursive as it may be used in stack dumping.
     : dex_lock_("ClassLinker dex lock", kDefaultMutexLevel),
-      dex_cache_image_class_lookup_required_(false),
+      dex_cache_boot_image_class_lookup_required_(false),
       failed_dex_cache_class_lookups_(0),
       class_roots_(nullptr),
       array_iftable_(nullptr),
@@ -794,7 +794,7 @@
       CHECK_EQ(field.GetDeclaringClass(), klass);
     }
     auto* runtime = Runtime::Current();
-    auto* image_space = runtime->GetHeap()->GetImageSpace();
+    auto* image_space = runtime->GetHeap()->GetBootImageSpace();
     auto pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
     for (auto& m : klass->GetDirectMethods(pointer_size)) {
       SanityCheckArtMethod(&m, klass, image_space);
@@ -855,10 +855,10 @@
   Runtime* const runtime = Runtime::Current();
   Thread* const self = Thread::Current();
   gc::Heap* const heap = runtime->GetHeap();
-  gc::space::ImageSpace* const space = heap->GetImageSpace();
+  gc::space::ImageSpace* const space = heap->GetBootImageSpace();
   CHECK(space != nullptr);
   image_pointer_size_ = space->GetImageHeader().GetPointerSize();
-  dex_cache_image_class_lookup_required_ = true;
+  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);
@@ -1086,8 +1086,8 @@
 }
 
 void ClassLinker::VisitClasses(ClassVisitor* visitor) {
-  if (dex_cache_image_class_lookup_required_) {
-    MoveImageClassesToClassTable();
+  if (dex_cache_boot_image_class_lookup_required_) {
+    AddBootImageClassesToClassTable();
   }
   Thread* const self = Thread::Current();
   ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -2643,11 +2643,13 @@
   if (existing != nullptr) {
     return existing;
   }
-  if (kIsDebugBuild && !klass->IsTemp() && class_loader == nullptr &&
-      dex_cache_image_class_lookup_required_) {
+  if (kIsDebugBuild &&
+      !klass->IsTemp() &&
+      class_loader == nullptr &&
+      dex_cache_boot_image_class_lookup_required_) {
     // Check a class loaded with the system class loader matches one in the image if the class
     // is in the image.
-    existing = LookupClassFromImage(descriptor);
+    existing = LookupClassFromBootImage(descriptor);
     if (existing != nullptr) {
       CHECK_EQ(klass, existing);
     }
@@ -2691,11 +2693,11 @@
       }
     }
   }
-  if (class_loader != nullptr || !dex_cache_image_class_lookup_required_) {
+  if (class_loader != nullptr || !dex_cache_boot_image_class_lookup_required_) {
     return nullptr;
   }
   // Lookup failed but need to search dex_caches_.
-  mirror::Class* result = LookupClassFromImage(descriptor);
+  mirror::Class* result = LookupClassFromBootImage(descriptor);
   if (result != nullptr) {
     result = InsertClass(descriptor, result, hash);
   } else {
@@ -2704,37 +2706,43 @@
     // classes into the class table.
     constexpr uint32_t kMaxFailedDexCacheLookups = 1000;
     if (++failed_dex_cache_class_lookups_ > kMaxFailedDexCacheLookups) {
-      MoveImageClassesToClassTable();
+      AddBootImageClassesToClassTable();
     }
   }
   return result;
 }
 
-static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches()
+static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches(gc::space::ImageSpace* image_space)
     SHARED_REQUIRES(Locks::mutator_lock_) {
-  gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetImageSpace();
-  CHECK(image != nullptr);
-  mirror::Object* root = image->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+  CHECK(image_space != nullptr);
+  mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+  DCHECK(root != nullptr);
   return root->AsObjectArray<mirror::DexCache>();
 }
 
-void ClassLinker::MoveImageClassesToClassTable() {
+void ClassLinker::AddBootImageClassesToClassTable() {
+  if (dex_cache_boot_image_class_lookup_required_) {
+    AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpace(),
+                                /*class_loader*/nullptr);
+    dex_cache_boot_image_class_lookup_required_ = false;
+  }
+}
+
+void ClassLinker::AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+                                              mirror::ClassLoader* class_loader) {
   Thread* self = Thread::Current();
   WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-  if (!dex_cache_image_class_lookup_required_) {
-    return;  // All dex cache classes are already in the class table.
-  }
   ScopedAssertNoThreadSuspension ants(self, "Moving image classes to class table");
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches();
+  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(image_space);
   std::string temp;
-  ClassTable* const class_table = InsertClassTableForClassLoader(nullptr);
+  ClassTable* const class_table = InsertClassTableForClassLoader(class_loader);
   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(klass->GetClassLoader() == 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);
@@ -2750,7 +2758,6 @@
       }
     }
   }
-  dex_cache_image_class_lookup_required_ = false;
 }
 
 class MoveClassTableToPreZygoteVisitor : public ClassLoaderVisitor {
@@ -2774,9 +2781,10 @@
   VisitClassLoaders(&visitor);
 }
 
-mirror::Class* ClassLinker::LookupClassFromImage(const char* descriptor) {
+mirror::Class* ClassLinker::LookupClassFromBootImage(const char* descriptor) {
   ScopedAssertNoThreadSuspension ants(Thread::Current(), "Image class lookup");
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches();
+  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();
@@ -2818,8 +2826,8 @@
 
 void ClassLinker::LookupClasses(const char* descriptor, std::vector<mirror::Class*>& result) {
   result.clear();
-  if (dex_cache_image_class_lookup_required_) {
-    MoveImageClassesToClassTable();
+  if (dex_cache_boot_image_class_lookup_required_) {
+    AddBootImageClassesToClassTable();
   }
   Thread* const self = Thread::Current();
   ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -4142,10 +4150,10 @@
         Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
       }
       CHECK_EQ(existing, klass.Get());
-      if (kIsDebugBuild && class_loader == nullptr && dex_cache_image_class_lookup_required_) {
+      if (kIsDebugBuild && class_loader == nullptr && dex_cache_boot_image_class_lookup_required_) {
         // Check a class loaded with the system class loader matches one in the image if the class
         // is in the image.
-        mirror::Class* const image_class = LookupClassFromImage(descriptor);
+        mirror::Class* const image_class = LookupClassFromBootImage(descriptor);
         if (image_class != nullptr) {
           CHECK_EQ(klass.Get(), existing) << descriptor;
         }
@@ -6423,8 +6431,8 @@
 
 void ClassLinker::DumpForSigQuit(std::ostream& os) {
   ScopedObjectAccess soa(Thread::Current());
-  if (dex_cache_image_class_lookup_required_) {
-    MoveImageClassesToClassTable();
+  if (dex_cache_boot_image_class_lookup_required_) {
+    AddBootImageClassesToClassTable();
   }
   ReaderMutexLock mu(soa.Self(), *Locks::classlinker_classes_lock_);
   os << "Zygote loaded classes=" << NumZygoteClasses() << " post zygote classes="
@@ -6461,8 +6469,8 @@
 }
 
 size_t ClassLinker::NumLoadedClasses() {
-  if (dex_cache_image_class_lookup_required_) {
-    MoveImageClassesToClassTable();
+  if (dex_cache_boot_image_class_lookup_required_) {
+    AddBootImageClassesToClassTable();
   }
   ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
   // Only return non zygote classes since these are the ones which apps which care about.
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index d6a27b1..21f9e7b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -487,10 +487,17 @@
     return class_roots;
   }
 
-  // Move all of the image classes into the class table for faster lookups.
-  void MoveImageClassesToClassTable()
+  // Move all of the boot image classes into the class table for faster lookups.
+  void AddBootImageClassesToClassTable()
       REQUIRES(!Locks::classlinker_classes_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
+
+  // Add image classes to the class table.
+  void AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+                                   mirror::ClassLoader* class_loader)
+      REQUIRES(!Locks::classlinker_classes_lock_)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
   // Move the class table to the pre-zygote table to reduce memory usage. This works by ensuring
   // that no more classes are ever added to the pre zygote table which makes it that the pages
   // always remain shared dirty instead of private dirty.
@@ -909,7 +916,7 @@
   void EnsurePreverifiedMethods(Handle<mirror::Class> c)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
-  mirror::Class* LookupClassFromImage(const char* descriptor)
+  mirror::Class* LookupClassFromBootImage(const char* descriptor)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Returns null if not found.
@@ -973,8 +980,8 @@
   // New class roots, only used by CMS since the GC needs to mark these in the pause.
   std::vector<GcRoot<mirror::Class>> new_class_roots_ GUARDED_BY(Locks::classlinker_classes_lock_);
 
-  // Do we need to search dex caches to find image classes?
-  bool dex_cache_image_class_lookup_required_;
+  // Do we need to search dex caches to find boot image classes?
+  bool dex_cache_boot_image_class_lookup_required_;
   // Number of times we've searched dex caches for a class. After a certain number of misses we move
   // the classes into the class_table_ to avoid dex cache based searches.
   Atomic<uint32_t> failed_dex_cache_class_lookups_;
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index 1361f7b..8f7bb94 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -487,7 +487,7 @@
 
 // Mark all references to the alloc space(s).
 void ModUnionTableCardCache::UpdateAndMarkReferences(MarkObjectVisitor* visitor) {
-  auto* image_space = heap_->GetImageSpace();
+  auto* image_space = heap_->GetBootImageSpace();
   // 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/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 4a49712..f4cf3ae 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -23,7 +23,7 @@
 #include "gc/accounting/space_bitmap-inl.h"
 #include "gc/reference_processor.h"
 #include "gc/space/image_space.h"
-#include "gc/space/space.h"
+#include "gc/space/space-inl.h"
 #include "intern_table.h"
 #include "mirror/class-inl.h"
 #include "mirror/object-inl.h"
@@ -358,13 +358,17 @@
     // scan the image objects from roots by relying on the card table,
     // but it's necessary for the RB to-space invariant to hold.
     TimingLogger::ScopedTiming split1("VisitImageRoots", GetTimings());
-    gc::space::ImageSpace* image = heap_->GetImageSpace();
-    if (image != nullptr) {
-      mirror::ObjectArray<mirror::Object>* image_root = image->GetImageHeader().GetImageRoots();
-      mirror::Object* marked_image_root = Mark(image_root);
-      CHECK_EQ(image_root, marked_image_root) << "An image object does not move";
-      if (ReadBarrier::kEnableToSpaceInvariantChecks) {
-        AssertToSpaceInvariant(nullptr, MemberOffset(0), marked_image_root);
+    for (space::ContinuousSpace* space : heap_->GetContinuousSpaces()) {
+      if (space->IsImageSpace()) {
+        gc::space::ImageSpace* image = space->AsImageSpace();
+        if (image != nullptr) {
+          mirror::ObjectArray<mirror::Object>* image_root = image->GetImageHeader().GetImageRoots();
+          mirror::Object* marked_image_root = Mark(image_root);
+          CHECK_EQ(image_root, marked_image_root) << "An image object does not move";
+          if (ReadBarrier::kEnableToSpaceInvariantChecks) {
+            AssertToSpaceInvariant(nullptr, MemberOffset(0), marked_image_root);
+          }
+        }
       }
     }
   }
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index fd2426b..da9a79e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -233,7 +233,8 @@
       backtrace_lock_(nullptr),
       seen_backtrace_count_(0u),
       unique_backtrace_count_(0u),
-      gc_disabled_for_shutdown_(false) {
+      gc_disabled_for_shutdown_(false),
+      boot_image_space_(nullptr) {
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "Heap() entering";
   }
@@ -262,15 +263,16 @@
   if (!image_file_name.empty()) {
     ATRACE_BEGIN("ImageSpace::Create");
     std::string error_msg;
-    auto* image_space = space::ImageSpace::Create(image_file_name.c_str(), image_instruction_set,
+    boot_image_space_ = space::ImageSpace::Create(image_file_name.c_str(),
+                                                  image_instruction_set,
                                                   &error_msg);
     ATRACE_END();
-    if (image_space != nullptr) {
-      AddSpace(image_space);
+    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 = image_space->GetImageHeader().GetOatFileEnd();
-      CHECK_GT(oat_file_end_addr, image_space->End());
+      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 << "'. "
@@ -454,11 +456,11 @@
     rb_table_.reset(new accounting::ReadBarrierTable());
     DCHECK(rb_table_->IsAllCleared());
   }
-  if (GetImageSpace() != nullptr) {
+  if (GetBootImageSpace() != nullptr) {
     // 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, GetImageSpace());
+        "Image mod-union table", this, GetBootImageSpace());
     CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
     AddModUnionTable(mod_union_table);
   }
@@ -523,12 +525,12 @@
       garbage_collectors_.push_back(mark_compact_collector_);
     }
   }
-  if (GetImageSpace() != nullptr && non_moving_space_ != nullptr &&
+  if (GetBootImageSpace() != nullptr && 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.
-    bool no_gap = MemMap::CheckNoGaps(GetImageSpace()->GetMemMap(),
+    bool no_gap = MemMap::CheckNoGaps(GetBootImageSpace()->GetMemMap(),
                                       non_moving_space_->GetMemMap());
     if (!no_gap) {
       PrintFileToLog("/proc/self/maps", LogSeverity::ERROR);
@@ -748,15 +750,6 @@
   return true;
 }
 
-bool Heap::HasImageSpace() const {
-  for (const auto& space : continuous_spaces_) {
-    if (space->IsImageSpace()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 void Heap::IncrementDisableMovingGC(Thread* self) {
   // Need to do this holding the lock to prevent races where the GC is about to run / running when
   // we attempt to disable it.
@@ -1209,13 +1202,8 @@
   return FindDiscontinuousSpaceFromObject(obj, fail_ok);
 }
 
-space::ImageSpace* Heap::GetImageSpace() const {
-  for (const auto& space : continuous_spaces_) {
-    if (space->IsImageSpace()) {
-      return space->AsImageSpace();
-    }
-  }
-  return nullptr;
+space::ImageSpace* Heap::GetBootImageSpace() const {
+  return boot_image_space_;
 }
 
 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 cc48172..e23b1a3 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -580,9 +580,9 @@
   // Unbind any bound bitmaps.
   void UnBindBitmaps() REQUIRES(Locks::heap_bitmap_lock_);
 
-  // DEPRECATED: Should remove in "near" future when support for multiple image spaces is added.
-  // Assumes there is only one image space.
-  space::ImageSpace* GetImageSpace() const;
+  // Returns the boot image space. There may be multiple image spaces, but there is only one boot
+  // image space.
+  space::ImageSpace* GetBootImageSpace() const;
 
   // Permenantly disable moving garbage collection.
   void DisableMovingGc() REQUIRES(!*gc_complete_lock_);
@@ -660,7 +660,9 @@
   void RemoveRememberedSet(space::Space* space);
 
   bool IsCompilingBoot() const;
-  bool HasImageSpace() const;
+  bool HasImageSpace() const {
+    return boot_image_space_ != nullptr;
+  }
 
   ReferenceProcessor* GetReferenceProcessor() {
     return reference_processor_.get();
@@ -1320,6 +1322,9 @@
   // allocating.
   bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_);
 
+  // Boot image space.
+  space::ImageSpace* boot_image_space_;
+
   friend class CollectorTransitionTask;
   friend class collector::GarbageCollector;
   friend class collector::MarkCompact;
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index f4658d5..e2e4782 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -187,7 +187,7 @@
   if (image_added_to_intern_table_) {
     return nullptr;
   }
-  gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetImageSpace();
+  gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetBootImageSpace();
   if (image == nullptr) {
     return nullptr;  // No image present.
   }
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 99080f6..0f3a013 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -846,7 +846,7 @@
 
 std::string OatFileAssistant::ImageLocation() {
   Runtime* runtime = Runtime::Current();
-  const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+  const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
   if (image_space == nullptr) {
     return "";
   }
@@ -949,21 +949,23 @@
     image_info_load_attempted_ = true;
 
     Runtime* runtime = Runtime::Current();
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
     if (image_space != nullptr) {
       cached_image_info_.location = image_space->GetImageLocation();
 
       if (isa_ == kRuntimeISA) {
         const ImageHeader& image_header = image_space->GetImageHeader();
         cached_image_info_.oat_checksum = image_header.GetOatChecksum();
-        cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin());
+        cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
+            image_header.GetOatDataBegin());
         cached_image_info_.patch_delta = image_header.GetPatchDelta();
       } else {
         std::unique_ptr<ImageHeader> image_header(
             gc::space::ImageSpace::ReadImageHeaderOrDie(
                 cached_image_info_.location.c_str(), isa_));
         cached_image_info_.oat_checksum = image_header->GetOatChecksum();
-        cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin());
+        cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
+            image_header->GetOatDataBegin());
         cached_image_info_.patch_delta = image_header->GetPatchDelta();
       }
     }
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index c54d7f8..8c7efb2 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -223,7 +223,7 @@
         false, dex_location.c_str(), &error_msg));
     ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
 
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
     ASSERT_TRUE(image_space != nullptr);
     const ImageHeader& image_header = image_space->GetImageHeader();
     const OatHeader& oat_header = odex_file->GetOatHeader();
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 9eee156..ea6d3ff 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -79,11 +79,8 @@
 }
 
 const OatFile* OatFileManager::GetBootOatFile() const {
-  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
-  if (image_space == nullptr) {
-    return nullptr;
-  }
-  return image_space->GetOatFile();
+  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
+  return (image_space == nullptr) ? nullptr : image_space->GetOatFile();
 }
 
 const OatFile* OatFileManager::GetPrimaryOatFile() const {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 556ba56..17f34c0 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -528,13 +528,13 @@
   // Use !IsAotCompiler so that we get test coverage, tests are never the zygote.
   if (!IsAotCompiler()) {
     ScopedObjectAccess soa(self);
-    gc::space::ImageSpace* image_space = heap_->GetImageSpace();
+    gc::space::ImageSpace* image_space = heap_->GetBootImageSpace();
     if (image_space != nullptr) {
       ATRACE_BEGIN("AddImageStringsToTable");
       GetInternTable()->AddImageStringsToTable(image_space);
       ATRACE_END();
       ATRACE_BEGIN("MoveImageClassesToClassTable");
-      GetClassLinker()->MoveImageClassesToClassTable();
+      GetClassLinker()->AddBootImageClassesToClassTable();
       ATRACE_END();
     }
   }
@@ -930,7 +930,7 @@
                        runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));
   ATRACE_END();
 
-  if (heap_->GetImageSpace() == nullptr && !allow_dex_file_fallback_) {
+  if (heap_->GetBootImageSpace() == nullptr && !allow_dex_file_fallback_) {
     LOG(ERROR) << "Dex file fallback disabled, cannot continue without image.";
     ATRACE_END();
     return false;
@@ -1043,7 +1043,7 @@
     class_linker_->InitFromImage();
     ATRACE_END();
     if (kIsDebugBuild) {
-      GetHeap()->GetImageSpace()->VerifyImageAllocations();
+      GetHeap()->GetBootImageSpace()->VerifyImageAllocations();
     }
     if (boot_class_path_string_.empty()) {
       // The bootclasspath is not explicitly specified: construct it from the loaded dex files.
diff --git a/test/117-nopatchoat/nopatchoat.cc b/test/117-nopatchoat/nopatchoat.cc
index 3e533ad..b6b1c43 100644
--- a/test/117-nopatchoat/nopatchoat.cc
+++ b/test/117-nopatchoat/nopatchoat.cc
@@ -35,7 +35,7 @@
   }
 
   static bool isRelocationDeltaZero() {
-    gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetImageSpace();
+    gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetBootImageSpace();
     return space != nullptr && space->GetImageHeader().GetPatchDelta() == 0;
   }
 
diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc
index 78f8842..7762b2d 100644
--- a/test/137-cfi/cfi.cc
+++ b/test/137-cfi/cfi.cc
@@ -92,7 +92,7 @@
 // detecting this.
 #if __linux__
 static bool IsPicImage() {
-  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
   CHECK(image_space != nullptr);  // We should be running with an image.
   const OatFile* oat_file = image_space->GetOatFile();
   CHECK(oat_file != nullptr);     // We should have an oat file to go with the image.