Merge "test: Re-enable javac/dx failing checker tests for Long.compare"
diff --git a/compiler/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index 627ab4e..f025c0a 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -167,22 +167,346 @@
   HandleShiftLatencies(instr);
 }
 
-void SchedulingLatencyVisitorARM::VisitCondition(HCondition* instr) {
-  switch (instr->GetLeft()->GetType()) {
-    case Primitive::kPrimLong:
-      last_visited_internal_latency_ = 4 * kArmIntegerOpLatency;
+void SchedulingLatencyVisitorARM::HandleGenerateConditionWithZero(IfCondition condition) {
+  switch (condition) {
+    case kCondEQ:
+    case kCondBE:
+    case kCondNE:
+    case kCondA:
+      last_visited_internal_latency_ += kArmIntegerOpLatency;
+      last_visited_latency_ = kArmIntegerOpLatency;
       break;
-    case Primitive::kPrimFloat:
-    case Primitive::kPrimDouble:
-      last_visited_internal_latency_ = 2 * kArmFloatingPointOpLatency;
+    case kCondGE:
+      // Mvn
+      last_visited_internal_latency_ += kArmIntegerOpLatency;
+      FALLTHROUGH_INTENDED;
+    case kCondLT:
+      // Lsr
+      last_visited_latency_ = kArmIntegerOpLatency;
+      break;
+    case kCondAE:
+      // Trivially true.
+      // Mov
+      last_visited_latency_ = kArmIntegerOpLatency;
+      break;
+    case kCondB:
+      // Trivially false.
+      // Mov
+      last_visited_latency_ = kArmIntegerOpLatency;
       break;
     default:
-      last_visited_internal_latency_ = 2 * kArmIntegerOpLatency;
-      break;
+      LOG(FATAL) << "Unexpected condition " << condition;
+      UNREACHABLE();
   }
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongTestConstant(HCondition* condition) {
+  DCHECK_EQ(condition->GetLeft()->GetType(), Primitive::kPrimLong);
+
+  IfCondition cond = condition->GetCondition();
+
+  HInstruction* right = condition->InputAt(1);
+
+  int64_t value = Uint64ConstantFrom(right);
+
+  // Comparisons against 0 are common enough, so codegen has special handling for them.
+  if (value == 0) {
+    switch (cond) {
+      case kCondNE:
+      case kCondA:
+      case kCondEQ:
+      case kCondBE:
+        // Orrs
+        last_visited_internal_latency_ += kArmIntegerOpLatency;
+        return;
+      case kCondLT:
+      case kCondGE:
+        // Cmp
+        last_visited_internal_latency_ += kArmIntegerOpLatency;
+        return;
+      case kCondB:
+      case kCondAE:
+        // Cmp
+        last_visited_internal_latency_ += kArmIntegerOpLatency;
+        return;
+      default:
+        break;
+    }
+  }
+
+  switch (cond) {
+    case kCondEQ:
+    case kCondNE:
+    case kCondB:
+    case kCondBE:
+    case kCondA:
+    case kCondAE: {
+      // Cmp, IT, Cmp
+      last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+      break;
+    }
+    case kCondLE:
+    case kCondGT:
+      // Trivially true or false.
+      if (value == std::numeric_limits<int64_t>::max()) {
+        // Cmp
+        last_visited_internal_latency_ += kArmIntegerOpLatency;
+        break;
+      }
+      FALLTHROUGH_INTENDED;
+    case kCondGE:
+    case kCondLT: {
+      // Cmp, Sbcs
+      last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+      break;
+    }
+    default:
+      LOG(FATAL) << "Unreachable";
+      UNREACHABLE();
+  }
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongTest(HCondition* condition) {
+  DCHECK_EQ(condition->GetLeft()->GetType(), Primitive::kPrimLong);
+
+  IfCondition cond = condition->GetCondition();
+
+  switch (cond) {
+    case kCondEQ:
+    case kCondNE:
+    case kCondB:
+    case kCondBE:
+    case kCondA:
+    case kCondAE: {
+      // Cmp, IT, Cmp
+      last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+      break;
+    }
+    case kCondLE:
+    case kCondGT:
+    case kCondGE:
+    case kCondLT: {
+      // Cmp, Sbcs
+      last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+      break;
+    }
+    default:
+      LOG(FATAL) << "Unreachable";
+      UNREACHABLE();
+  }
+}
+
+// The GenerateTest series of function all counted as internal latency.
+void SchedulingLatencyVisitorARM::HandleGenerateTest(HCondition* condition) {
+  const Primitive::Type type = condition->GetLeft()->GetType();
+
+  if (type == Primitive::kPrimLong) {
+    condition->InputAt(1)->IsConstant()
+        ? HandleGenerateLongTestConstant(condition)
+        : HandleGenerateLongTest(condition);
+  } else if (Primitive::IsFloatingPointType(type)) {
+    // GenerateVcmp + Vmrs
+    last_visited_internal_latency_ += 2 * kArmFloatingPointOpLatency;
+  } else {
+    // Cmp
+    last_visited_internal_latency_ += kArmIntegerOpLatency;
+  }
+}
+
+bool SchedulingLatencyVisitorARM::CanGenerateTest(HCondition* condition) {
+  if (condition->GetLeft()->GetType() == Primitive::kPrimLong) {
+    HInstruction* right = condition->InputAt(1);
+
+    if (right->IsConstant()) {
+      IfCondition c = condition->GetCondition();
+      const uint64_t value = Uint64ConstantFrom(right);
+
+      if (c < kCondLT || c > kCondGE) {
+        if (value != 0) {
+          return false;
+        }
+      } else if (c == kCondLE || c == kCondGT) {
+        if (value < std::numeric_limits<int64_t>::max() &&
+            !codegen_->GetAssembler()->ShifterOperandCanHold(SBC, High32Bits(value + 1), kCcSet)) {
+          return false;
+        }
+      } else if (!codegen_->GetAssembler()->ShifterOperandCanHold(SBC, High32Bits(value), kCcSet)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionGeneric(HCondition* cond) {
+  HandleGenerateTest(cond);
+
+  // Unlike codegen pass, we cannot check 'out' register IsLow() here,
+  // because scheduling is before liveness(location builder) and register allocator,
+  // so we can only choose to follow one path of codegen by assuming otu.IsLow() is true.
+  last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
   last_visited_latency_ = kArmIntegerOpLatency;
 }
 
+void SchedulingLatencyVisitorARM::HandleGenerateEqualLong(HCondition* cond) {
+  DCHECK_EQ(cond->GetLeft()->GetType(), Primitive::kPrimLong);
+
+  IfCondition condition = cond->GetCondition();
+
+  last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+
+  if (condition == kCondNE) {
+    // Orrs, IT, Mov
+    last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+  } else {
+    last_visited_internal_latency_ += kArmIntegerOpLatency;
+    HandleGenerateConditionWithZero(condition);
+  }
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongComparesAndJumps() {
+  last_visited_internal_latency_ += 4 * kArmIntegerOpLatency;
+  last_visited_internal_latency_ += kArmBranchLatency;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionLong(HCondition* cond) {
+  DCHECK_EQ(cond->GetLeft()->GetType(), Primitive::kPrimLong);
+
+  IfCondition condition = cond->GetCondition();
+  HInstruction* right = cond->InputAt(1);
+
+  if (right->IsConstant()) {
+    // Comparisons against 0 are common enough, so codegen has special handling for them.
+    if (Uint64ConstantFrom(right) == 0) {
+      switch (condition) {
+        case kCondNE:
+        case kCondA:
+        case kCondEQ:
+        case kCondBE:
+          // Orr
+          last_visited_internal_latency_ += kArmIntegerOpLatency;
+          HandleGenerateConditionWithZero(condition);
+          return;
+        case kCondLT:
+        case kCondGE:
+          FALLTHROUGH_INTENDED;
+        case kCondAE:
+        case kCondB:
+          HandleGenerateConditionWithZero(condition);
+          return;
+        case kCondLE:
+        case kCondGT:
+        default:
+          break;
+      }
+    }
+  }
+
+  if ((condition == kCondEQ || condition == kCondNE) &&
+      !CanGenerateTest(cond)) {
+    HandleGenerateEqualLong(cond);
+    return;
+  }
+
+  if (CanGenerateTest(cond)) {
+    HandleGenerateConditionGeneric(cond);
+    return;
+  }
+
+  HandleGenerateLongComparesAndJumps();
+
+  last_visited_internal_latency_ += kArmIntegerOpLatency;
+  last_visited_latency_ = kArmBranchLatency;;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionIntegralOrNonPrimitive(HCondition* cond) {
+  const Primitive::Type type = cond->GetLeft()->GetType();
+
+  DCHECK(Primitive::IsIntegralType(type) || type == Primitive::kPrimNot) << type;
+
+  if (type == Primitive::kPrimLong) {
+    HandleGenerateConditionLong(cond);
+    return;
+  }
+
+  IfCondition condition = cond->GetCondition();
+  HInstruction* right = cond->InputAt(1);
+  int64_t value;
+
+  if (right->IsConstant()) {
+    value = Uint64ConstantFrom(right);
+
+    // Comparisons against 0 are common enough, so codegen has special handling for them.
+    if (value == 0) {
+      switch (condition) {
+        case kCondNE:
+        case kCondA:
+        case kCondEQ:
+        case kCondBE:
+        case kCondLT:
+        case kCondGE:
+        case kCondAE:
+        case kCondB:
+          HandleGenerateConditionWithZero(condition);
+          return;
+        case kCondLE:
+        case kCondGT:
+        default:
+          break;
+      }
+    }
+  }
+
+  if (condition == kCondEQ || condition == kCondNE) {
+    if (condition == kCondNE) {
+      // CMP, IT, MOV.ne
+      last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+      last_visited_latency_ = kArmIntegerOpLatency;
+    } else {
+      last_visited_internal_latency_ += kArmIntegerOpLatency;
+      HandleGenerateConditionWithZero(condition);
+    }
+    return;
+  }
+
+  HandleGenerateConditionGeneric(cond);
+}
+
+void SchedulingLatencyVisitorARM::HandleCondition(HCondition* cond) {
+  if (cond->IsEmittedAtUseSite()) {
+    last_visited_latency_ = 0;
+    return;
+  }
+
+  const Primitive::Type type = cond->GetLeft()->GetType();
+
+  if (Primitive::IsFloatingPointType(type)) {
+    HandleGenerateConditionGeneric(cond);
+    return;
+  }
+
+  DCHECK(Primitive::IsIntegralType(type) || type == Primitive::kPrimNot) << type;
+
+  const IfCondition condition = cond->GetCondition();
+
+  if (type == Primitive::kPrimBoolean &&
+      cond->GetRight()->GetType() == Primitive::kPrimBoolean &&
+      (condition == kCondEQ || condition == kCondNE)) {
+    if (condition == kCondEQ) {
+      last_visited_internal_latency_ = kArmIntegerOpLatency;
+    }
+    last_visited_latency_ = kArmIntegerOpLatency;
+    return;
+  }
+
+  HandleGenerateConditionIntegralOrNonPrimitive(cond);
+}
+
+void SchedulingLatencyVisitorARM::VisitCondition(HCondition* instr) {
+  HandleCondition(instr);
+}
+
 void SchedulingLatencyVisitorARM::VisitCompare(HCompare* instr) {
   Primitive::Type type = instr->InputAt(0)->GetType();
   switch (type) {
diff --git a/compiler/optimizing/scheduler_arm.h b/compiler/optimizing/scheduler_arm.h
index a9f2295..fe274d2 100644
--- a/compiler/optimizing/scheduler_arm.h
+++ b/compiler/optimizing/scheduler_arm.h
@@ -109,6 +109,17 @@
 #undef DECLARE_VISIT_INSTRUCTION
 
  private:
+  bool CanGenerateTest(HCondition* cond);
+  void HandleGenerateConditionWithZero(IfCondition cond);
+  void HandleGenerateLongTestConstant(HCondition* cond);
+  void HandleGenerateLongTest(HCondition* cond);
+  void HandleGenerateLongComparesAndJumps();
+  void HandleGenerateTest(HCondition* cond);
+  void HandleGenerateConditionGeneric(HCondition* cond);
+  void HandleGenerateEqualLong(HCondition* cond);
+  void HandleGenerateConditionLong(HCondition* cond);
+  void HandleGenerateConditionIntegralOrNonPrimitive(HCondition* cond);
+  void HandleCondition(HCondition* instr);
   void HandleBinaryOperationLantencies(HBinaryOperation* instr);
   void HandleBitwiseOperationLantencies(HBinaryOperation* instr);
   void HandleShiftLatencies(HBinaryOperation* instr);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index dadea76..3cc41a6 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1497,10 +1497,9 @@
       Runtime* runtime = Runtime::Current();
       CHECK(runtime != nullptr);
       // Filter out class path classes since we don't want to include these in the image.
-      std::set<DexCacheResolvedClasses> resolved_classes(
-          profile_compilation_info_->GetResolvedClasses(dex_files_));
-      image_classes_.reset(new std::unordered_set<std::string>(
-          runtime->GetClassLinker()->GetClassDescriptorsForResolvedClasses(resolved_classes)));
+      image_classes_.reset(
+          new std::unordered_set<std::string>(
+              profile_compilation_info_->GetClassDescriptors(dex_files_)));
       VLOG(compiler) << "Loaded " << image_classes_->size()
                      << " image class descriptors from profile";
       if (VLOG_IS_ON(compiler)) {
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 2763c07..5d9e361 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -50,6 +50,803 @@
 
 using android::base::StringPrintf;
 
+namespace {
+
+constexpr size_t kMaxAddressPrint = 5;
+
+enum class ProcessType {
+  kZygote,
+  kRemote
+};
+
+enum class RemoteProcesses {
+  kImageOnly,
+  kZygoteOnly,
+  kImageAndZygote
+};
+
+struct MappingData {
+  // The count of pages that are considered dirty by the OS.
+  size_t dirty_pages = 0;
+  // The count of pages that differ by at least one byte.
+  size_t different_pages = 0;
+  // The count of differing bytes.
+  size_t different_bytes = 0;
+  // The count of differing four-byte units.
+  size_t different_int32s = 0;
+  // The count of pages that have mapping count == 1.
+  size_t private_pages = 0;
+  // The count of private pages that are also dirty.
+  size_t private_dirty_pages = 0;
+  // The count of pages that are marked dirty but do not differ.
+  size_t false_dirty_pages = 0;
+  // Set of the local virtual page indices that are dirty.
+  std::set<size_t> dirty_page_set;
+};
+
+static std::string GetClassDescriptor(mirror::Class* klass)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  CHECK(klass != nullptr);
+
+  std::string descriptor;
+  const char* descriptor_str = klass->GetDescriptor(&descriptor /*out*/);
+
+  return std::string(descriptor_str);
+}
+
+static std::string PrettyFieldValue(ArtField* field, mirror::Object* object)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  std::ostringstream oss;
+  switch (field->GetTypeAsPrimitiveType()) {
+    case Primitive::kPrimNot: {
+      oss << object->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
+          field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimBoolean: {
+      oss << static_cast<bool>(object->GetFieldBoolean<kVerifyNone>(field->GetOffset()));
+      break;
+    }
+    case Primitive::kPrimByte: {
+      oss << static_cast<int32_t>(object->GetFieldByte<kVerifyNone>(field->GetOffset()));
+      break;
+    }
+    case Primitive::kPrimChar: {
+      oss << object->GetFieldChar<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimShort: {
+      oss << object->GetFieldShort<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimInt: {
+      oss << object->GetField32<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimLong: {
+      oss << object->GetField64<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimFloat: {
+      oss << object->GetField32<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimDouble: {
+      oss << object->GetField64<kVerifyNone>(field->GetOffset());
+      break;
+    }
+    case Primitive::kPrimVoid: {
+      oss << "void";
+      break;
+    }
+  }
+  return oss.str();
+}
+
+template <typename K, typename V, typename D>
+static std::vector<std::pair<V, K>> SortByValueDesc(
+    const std::map<K, D> map,
+    std::function<V(const D&)> value_mapper = [](const D& d) { return static_cast<V>(d); }) {
+  // Store value->key so that we can use the default sort from pair which
+  // sorts by value first and then key
+  std::vector<std::pair<V, K>> value_key_vector;
+
+  for (const auto& kv_pair : map) {
+    value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
+  }
+
+  // Sort in reverse (descending order)
+  std::sort(value_key_vector.rbegin(), value_key_vector.rend());
+  return value_key_vector;
+}
+
+// Fixup a remote pointer that we read from a foreign boot.art to point to our own memory.
+// Returned pointer will point to inside of remote_contents.
+template <typename T>
+static T* FixUpRemotePointer(T* remote_ptr,
+                             std::vector<uint8_t>& remote_contents,
+                             const backtrace_map_t& boot_map) {
+  if (remote_ptr == nullptr) {
+    return nullptr;
+  }
+
+  uintptr_t remote = reinterpret_cast<uintptr_t>(remote_ptr);
+
+  CHECK_LE(boot_map.start, remote);
+  CHECK_GT(boot_map.end, remote);
+
+  off_t boot_offset = remote - boot_map.start;
+
+  return reinterpret_cast<T*>(&remote_contents[boot_offset]);
+}
+
+template <typename T>
+static T* RemoteContentsPointerToLocal(T* remote_ptr,
+                                       std::vector<uint8_t>& remote_contents,
+                                       const ImageHeader& image_header) {
+  if (remote_ptr == nullptr) {
+    return nullptr;
+  }
+
+  uint8_t* remote = reinterpret_cast<uint8_t*>(remote_ptr);
+  ptrdiff_t boot_offset = remote - &remote_contents[0];
+
+  const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + boot_offset;
+
+  return reinterpret_cast<T*>(const_cast<uint8_t*>(local_ptr));
+}
+
+template <typename T> size_t EntrySize(T* entry);
+template<> size_t EntrySize(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
+  return object->SizeOf();
+}
+template<> size_t EntrySize(ArtMethod* art_method) REQUIRES_SHARED(Locks::mutator_lock_) {
+  return sizeof(*art_method);
+}
+
+template <typename T>
+static bool EntriesDiffer(T* entry1, T* entry2) REQUIRES_SHARED(Locks::mutator_lock_) {
+  return memcmp(entry1, entry2, EntrySize(entry1)) != 0;
+}
+
+template <typename T>
+struct RegionCommon {
+ public:
+  RegionCommon(std::ostream* os,
+               std::vector<uint8_t>* remote_contents,
+               std::vector<uint8_t>* zygote_contents,
+               const backtrace_map_t& boot_map,
+               const ImageHeader& image_header) :
+    os_(*os),
+    remote_contents_(remote_contents),
+    zygote_contents_(zygote_contents),
+    boot_map_(boot_map),
+    image_header_(image_header),
+    different_entries_(0),
+    dirty_entry_bytes_(0),
+    false_dirty_entry_bytes_(0) {
+    CHECK(remote_contents != nullptr);
+    CHECK(zygote_contents != nullptr);
+  }
+
+  void DumpSamplesAndOffsetCount() {
+    os_ << "      sample object addresses: ";
+    for (size_t i = 0; i < dirty_entries_.size() && i < kMaxAddressPrint; ++i) {
+      T* entry = dirty_entries_[i];
+      os_ << reinterpret_cast<void*>(entry) << ", ";
+    }
+    os_ << "\n";
+    os_ << "      dirty byte +offset:count list = ";
+    std::vector<std::pair<size_t, off_t>> field_dirty_count_sorted =
+        SortByValueDesc<off_t, size_t, size_t>(field_dirty_count_);
+    for (const std::pair<size_t, off_t>& pair : field_dirty_count_sorted) {
+      off_t offset = pair.second;
+      size_t count = pair.first;
+      os_ << "+" << offset << ":" << count << ", ";
+    }
+    os_ << "\n";
+  }
+
+  size_t GetDifferentEntryCount() const { return different_entries_; }
+  size_t GetDirtyEntryBytes() const { return dirty_entry_bytes_; }
+  size_t GetFalseDirtyEntryCount() const { return false_dirty_entries_.size(); }
+  size_t GetFalseDirtyEntryBytes() const { return false_dirty_entry_bytes_; }
+  size_t GetZygoteDirtyEntryCount() const { return zygote_dirty_entries_.size(); }
+
+ protected:
+  bool IsEntryOnDirtyPage(T* entry, const std::set<size_t>& dirty_pages) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    size_t size = EntrySize(entry);
+    size_t page_off = 0;
+    size_t current_page_idx;
+    uintptr_t entry_address = reinterpret_cast<uintptr_t>(entry);
+    // Iterate every page this entry belongs to
+    do {
+      current_page_idx = entry_address / kPageSize + page_off;
+      if (dirty_pages.find(current_page_idx) != dirty_pages.end()) {
+        // This entry is on a dirty page
+        return true;
+      }
+      page_off++;
+    } while ((current_page_idx * kPageSize) < RoundUp(entry_address + size, kObjectAlignment));
+    return false;
+  }
+
+  void AddZygoteDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+    zygote_dirty_entries_.insert(entry);
+  }
+
+  void AddImageDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+    image_dirty_entries_.insert(entry);
+  }
+
+  void AddFalseDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+    false_dirty_entries_.push_back(entry);
+    false_dirty_entry_bytes_ += EntrySize(entry);
+  }
+
+  // The output stream to write to.
+  std::ostream& os_;
+  // The byte contents of the remote (image) process' image.
+  std::vector<uint8_t>* remote_contents_;
+  // The byte contents of the zygote process' image.
+  std::vector<uint8_t>* zygote_contents_;
+  const backtrace_map_t& boot_map_;
+  const ImageHeader& image_header_;
+
+  // Count of entries that are different.
+  size_t different_entries_;
+
+  // Local entries that are dirty (differ in at least one byte).
+  size_t dirty_entry_bytes_;
+  std::vector<T*> dirty_entries_;
+
+  // Local entries that are clean, but located on dirty pages.
+  size_t false_dirty_entry_bytes_;
+  std::vector<T*> false_dirty_entries_;
+
+  // Image dirty entries
+  // If zygote_pid_only_ == true, these are shared dirty entries in the zygote.
+  // If zygote_pid_only_ == false, these are private dirty entries in the application.
+  std::set<T*> image_dirty_entries_;
+
+  // Zygote dirty entries (probably private dirty).
+  // We only add entries here if they differed in both the image and the zygote, so
+  // they are probably private dirty.
+  std::set<T*> zygote_dirty_entries_;
+
+  std::map<off_t /* field offset */, size_t /* count */> field_dirty_count_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RegionCommon);
+};
+
+template <typename T>
+class RegionSpecializedBase : public RegionCommon<T> {
+};
+
+// Region analysis for mirror::Objects
+template<>
+class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object> {
+ public:
+  RegionSpecializedBase(std::ostream* os,
+                        std::vector<uint8_t>* remote_contents,
+                        std::vector<uint8_t>* zygote_contents,
+                        const backtrace_map_t& boot_map,
+                        const ImageHeader& image_header) :
+    RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
+    os_(*os) { }
+
+  void CheckEntrySanity(const uint8_t* current) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    CHECK_ALIGNED(current, kObjectAlignment);
+    mirror::Object* entry = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
+    // Sanity check that we are reading a real mirror::Object
+    CHECK(entry->GetClass() != nullptr) << "Image object at address "
+                                        << entry
+                                        << " has null class";
+    if (kUseBakerReadBarrier) {
+      entry->AssertReadBarrierState();
+    }
+  }
+
+  mirror::Object* GetNextEntry(mirror::Object* entry)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint8_t* next =
+        reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
+    return reinterpret_cast<mirror::Object*>(next);
+  }
+
+  void VisitEntry(mirror::Object* entry)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Unconditionally store the class descriptor in case we need it later
+    mirror::Class* klass = entry->GetClass();
+    class_data_[klass].descriptor = GetClassDescriptor(klass);
+  }
+
+  void AddCleanEntry(mirror::Object* entry)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    class_data_[entry->GetClass()].AddCleanObject();
+  }
+
+  void AddFalseDirtyEntry(mirror::Object* entry)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    RegionCommon<mirror::Object>::AddFalseDirtyEntry(entry);
+    class_data_[entry->GetClass()].AddFalseDirtyObject(entry);
+  }
+
+  void AddDirtyEntry(mirror::Object* entry, mirror::Object* entry_remote)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    size_t entry_size = EntrySize(entry);
+    ++different_entries_;
+    dirty_entry_bytes_ += entry_size;
+    // Log dirty count and objects for class objects only.
+    mirror::Class* klass = entry->GetClass();
+    if (klass->IsClassClass()) {
+      // Increment counts for the fields that are dirty
+      const uint8_t* current = reinterpret_cast<const uint8_t*>(entry);
+      const uint8_t* current_remote = reinterpret_cast<const uint8_t*>(entry_remote);
+      for (size_t i = 0; i < entry_size; ++i) {
+        if (current[i] != current_remote[i]) {
+          field_dirty_count_[i]++;
+        }
+      }
+      dirty_entries_.push_back(entry);
+    }
+    class_data_[klass].AddDirtyObject(entry, entry_remote);
+  }
+
+  void DiffEntryContents(mirror::Object* entry, uint8_t* remote_bytes)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    const char* tabs = "    ";
+    // Attempt to find fields for all dirty bytes.
+    mirror::Class* klass = entry->GetClass();
+    if (entry->IsClass()) {
+      os_ << tabs
+          << "Class " << mirror::Class::PrettyClass(entry->AsClass()) << " " << entry << "\n";
+    } else {
+      os_ << tabs
+          << "Instance of " << mirror::Class::PrettyClass(klass) << " " << entry << "\n";
+    }
+
+    std::unordered_set<ArtField*> dirty_instance_fields;
+    std::unordered_set<ArtField*> dirty_static_fields;
+    // Examine the bytes comprising the Object, computing which fields are dirty
+    // and recording them for later display.  If the Object is an array object,
+    // compute the dirty entries.
+    const uint8_t* entry_bytes = reinterpret_cast<const uint8_t*>(entry);
+    mirror::Object* remote_entry = reinterpret_cast<mirror::Object*>(remote_bytes);
+    for (size_t i = 0, count = entry->SizeOf(); i < count; ++i) {
+      if (entry_bytes[i] != remote_bytes[i]) {
+        ArtField* field = ArtField::FindInstanceFieldWithOffset</*exact*/false>(klass, i);
+        if (field != nullptr) {
+          dirty_instance_fields.insert(field);
+        } else if (entry->IsClass()) {
+          field = ArtField::FindStaticFieldWithOffset</*exact*/false>(entry->AsClass(), i);
+          if (field != nullptr) {
+            dirty_static_fields.insert(field);
+          }
+        }
+        if (field == nullptr) {
+          if (klass->IsArrayClass()) {
+            mirror::Class* component_type = klass->GetComponentType();
+            Primitive::Type primitive_type = component_type->GetPrimitiveType();
+            size_t component_size = Primitive::ComponentSize(primitive_type);
+            size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value();
+            if (i >= data_offset) {
+              os_ << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n";
+              // Skip to next element to prevent spam.
+              i += component_size - 1;
+              continue;
+            }
+          }
+          os_ << tabs << "No field for byte offset " << i << "\n";
+        }
+      }
+    }
+    // Dump different fields.
+    if (!dirty_instance_fields.empty()) {
+      os_ << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n";
+      for (ArtField* field : dirty_instance_fields) {
+        os_ << tabs << ArtField::PrettyField(field)
+            << " original=" << PrettyFieldValue(field, entry)
+            << " remote=" << PrettyFieldValue(field, remote_entry) << "\n";
+      }
+    }
+    if (!dirty_static_fields.empty()) {
+      os_ << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
+      for (ArtField* field : dirty_static_fields) {
+        os_ << tabs << ArtField::PrettyField(field)
+            << " original=" << PrettyFieldValue(field, entry)
+            << " remote=" << PrettyFieldValue(field, remote_entry) << "\n";
+      }
+    }
+    os_ << "\n";
+  }
+
+  void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+    // vector of pairs (size_t count, Class*)
+    auto dirty_object_class_values =
+        SortByValueDesc<mirror::Class*, size_t, ClassData>(
+            class_data_,
+            [](const ClassData& d) { return d.dirty_object_count; });
+    os_ << "\n" << "  Dirty object count by class:\n";
+    for (const auto& vk_pair : dirty_object_class_values) {
+      size_t dirty_object_count = vk_pair.first;
+      mirror::Class* klass = vk_pair.second;
+      ClassData& class_data = class_data_[klass];
+      size_t object_sizes = class_data.dirty_object_size_in_bytes;
+      float avg_dirty_bytes_per_class =
+          class_data.dirty_object_byte_count * 1.0f / object_sizes;
+      float avg_object_size = object_sizes * 1.0f / dirty_object_count;
+      const std::string& descriptor = class_data.descriptor;
+      os_ << "    " << mirror::Class::PrettyClass(klass) << " ("
+          << "objects: " << dirty_object_count << ", "
+          << "avg dirty bytes: " << avg_dirty_bytes_per_class << ", "
+          << "avg object size: " << avg_object_size << ", "
+          << "class descriptor: '" << descriptor << "'"
+          << ")\n";
+      if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
+        DumpSamplesAndOffsetCount();
+        os_ << "      field contents:\n";
+        for (mirror::Object* object : class_data.dirty_objects) {
+          // remote class object
+          auto remote_klass = reinterpret_cast<mirror::Class*>(object);
+          // local class object
+          auto local_klass =
+              RemoteContentsPointerToLocal(remote_klass,
+                                           *RegionCommon<mirror::Object>::remote_contents_,
+                                           RegionCommon<mirror::Object>::image_header_);
+          os_ << "        " << reinterpret_cast<const void*>(object) << " ";
+          os_ << "  class_status (remote): " << remote_klass->GetStatus() << ", ";
+          os_ << "  class_status (local): " << local_klass->GetStatus();
+          os_ << "\n";
+        }
+      }
+    }
+  }
+
+  void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+    // vector of pairs (size_t count, Class*)
+    auto false_dirty_object_class_values =
+        SortByValueDesc<mirror::Class*, size_t, ClassData>(
+            class_data_,
+            [](const ClassData& d) { return d.false_dirty_object_count; });
+    os_ << "\n" << "  False-dirty object count by class:\n";
+    for (const auto& vk_pair : false_dirty_object_class_values) {
+      size_t object_count = vk_pair.first;
+      mirror::Class* klass = vk_pair.second;
+      ClassData& class_data = class_data_[klass];
+      size_t object_sizes = class_data.false_dirty_byte_count;
+      float avg_object_size = object_sizes * 1.0f / object_count;
+      const std::string& descriptor = class_data.descriptor;
+      os_ << "    " << mirror::Class::PrettyClass(klass) << " ("
+          << "objects: " << object_count << ", "
+          << "avg object size: " << avg_object_size << ", "
+          << "total bytes: " << object_sizes << ", "
+          << "class descriptor: '" << descriptor << "'"
+          << ")\n";
+    }
+  }
+
+  void DumpCleanEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+    // vector of pairs (size_t count, Class*)
+    auto clean_object_class_values =
+        SortByValueDesc<mirror::Class*, size_t, ClassData>(
+            class_data_,
+            [](const ClassData& d) { return d.clean_object_count; });
+    os_ << "\n" << "  Clean object count by class:\n";
+    for (const auto& vk_pair : clean_object_class_values) {
+      os_ << "    " << mirror::Class::PrettyClass(vk_pair.second) << " (" << vk_pair.first << ")\n";
+    }
+  }
+
+ private:
+  // Aggregate and detail class data from an image diff.
+  struct ClassData {
+    size_t dirty_object_count = 0;
+    // Track only the byte-per-byte dirtiness (in bytes)
+    size_t dirty_object_byte_count = 0;
+    // Track the object-by-object dirtiness (in bytes)
+    size_t dirty_object_size_in_bytes = 0;
+    size_t clean_object_count = 0;
+    std::string descriptor;
+    size_t false_dirty_byte_count = 0;
+    size_t false_dirty_object_count = 0;
+    std::vector<mirror::Object*> false_dirty_objects;
+    // Remote pointers to dirty objects
+    std::vector<mirror::Object*> dirty_objects;
+
+    void AddCleanObject() REQUIRES_SHARED(Locks::mutator_lock_) {
+      ++clean_object_count;
+    }
+
+    void AddDirtyObject(mirror::Object* object, mirror::Object* object_remote)
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      ++dirty_object_count;
+      dirty_object_byte_count += CountDirtyBytes(object, object_remote);
+      dirty_object_size_in_bytes += EntrySize(object);
+      dirty_objects.push_back(object_remote);
+    }
+
+    void AddFalseDirtyObject(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
+      ++false_dirty_object_count;
+      false_dirty_objects.push_back(object);
+      false_dirty_byte_count += EntrySize(object);
+    }
+
+   private:
+    // Go byte-by-byte and figure out what exactly got dirtied
+    static size_t CountDirtyBytes(mirror::Object* object1, mirror::Object* object2)
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      const uint8_t* cur1 = reinterpret_cast<const uint8_t*>(object1);
+      const uint8_t* cur2 = reinterpret_cast<const uint8_t*>(object2);
+      size_t dirty_bytes = 0;
+      size_t object_size = EntrySize(object1);
+      for (size_t i = 0; i < object_size; ++i) {
+        if (cur1[i] != cur2[i]) {
+          dirty_bytes++;
+        }
+      }
+      return dirty_bytes;
+    }
+  };
+
+  std::ostream& os_;
+  std::map<mirror::Class*, ClassData> class_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
+};
+
+// Region analysis for ArtMethods.
+// TODO: most of these need work.
+template<>
+class RegionSpecializedBase<ArtMethod> : RegionCommon<ArtMethod> {
+ public:
+  RegionSpecializedBase(std::ostream* os,
+                        std::vector<uint8_t>* remote_contents,
+                        std::vector<uint8_t>* zygote_contents,
+                        const backtrace_map_t& boot_map,
+                        const ImageHeader& image_header) :
+    RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
+    os_(*os) { }
+
+  void CheckEntrySanity(const uint8_t* current ATTRIBUTE_UNUSED) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  }
+
+  ArtMethod* GetNextEntry(ArtMethod* entry)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint8_t* next = reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
+    return reinterpret_cast<ArtMethod*>(next);
+  }
+
+  void VisitEntry(ArtMethod* method ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  }
+
+  void AddFalseDirtyEntry(ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    RegionCommon<ArtMethod>::AddFalseDirtyEntry(method);
+  }
+
+  void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
+  }
+
+  void AddDirtyEntry(ArtMethod* method, ArtMethod* method_remote)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    size_t entry_size = EntrySize(method);
+    ++different_entries_;
+    dirty_entry_bytes_ += entry_size;
+    // Increment counts for the fields that are dirty
+    const uint8_t* current = reinterpret_cast<const uint8_t*>(method);
+    const uint8_t* current_remote = reinterpret_cast<const uint8_t*>(method_remote);
+    // ArtMethods always log their dirty count and entries.
+    for (size_t i = 0; i < entry_size; ++i) {
+      if (current[i] != current_remote[i]) {
+        field_dirty_count_[i]++;
+      }
+    }
+    dirty_entries_.push_back(method);
+  }
+
+  void DiffEntryContents(ArtMethod* method ATTRIBUTE_UNUSED,
+                         uint8_t* remote_bytes ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  }
+
+  void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+    DumpSamplesAndOffsetCount();
+    os_ << "      field contents:\n";
+    for (ArtMethod* method : dirty_entries_) {
+      // remote method
+      auto art_method = reinterpret_cast<ArtMethod*>(method);
+      // remote class
+      mirror::Class* remote_declaring_class =
+        FixUpRemotePointer(art_method->GetDeclaringClass(),
+                           *RegionCommon<ArtMethod>::remote_contents_,
+                           RegionCommon<ArtMethod>::boot_map_);
+      // local class
+      mirror::Class* declaring_class =
+        RemoteContentsPointerToLocal(remote_declaring_class,
+                                     *RegionCommon<ArtMethod>::remote_contents_,
+                                     RegionCommon<ArtMethod>::image_header_);
+      DumpOneArtMethod(art_method, declaring_class, remote_declaring_class);
+    }
+  }
+
+  void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+    os_ << "      field contents:\n";
+    for (ArtMethod* method : false_dirty_entries_) {
+      // local class
+      mirror::Class* declaring_class = method->GetDeclaringClass();
+      DumpOneArtMethod(method, declaring_class, nullptr);
+    }
+  }
+
+  void DumpCleanEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+  }
+
+ private:
+  std::ostream& os_;
+
+  void DumpOneArtMethod(ArtMethod* art_method,
+                        mirror::Class* declaring_class,
+                        mirror::Class* remote_declaring_class)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    PointerSize pointer_size = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
+    os_ << "        " << reinterpret_cast<const void*>(art_method) << " ";
+    os_ << "  entryPointFromJni: "
+        << reinterpret_cast<const void*>(art_method->GetDataPtrSize(pointer_size)) << ", ";
+    os_ << "  entryPointFromQuickCompiledCode: "
+        << reinterpret_cast<const void*>(
+               art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size))
+        << ", ";
+    os_ << "  isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
+    os_ << "  class_status (local): " << declaring_class->GetStatus();
+    if (remote_declaring_class != nullptr) {
+      os_ << ",  class_status (remote): " << remote_declaring_class->GetStatus();
+    }
+    os_ << "\n";
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
+};
+
+template <typename T>
+class RegionData : public RegionSpecializedBase<T> {
+ public:
+  RegionData(std::ostream* os,
+             std::vector<uint8_t>* remote_contents,
+             std::vector<uint8_t>* zygote_contents,
+             const backtrace_map_t& boot_map,
+             const ImageHeader& image_header) :
+    RegionSpecializedBase<T>(os, remote_contents, zygote_contents, boot_map, image_header),
+    os_(*os) {
+    CHECK(remote_contents != nullptr);
+    CHECK(zygote_contents != nullptr);
+  }
+
+  // Walk over the type T entries in theregion between begin_image_ptr and end_image_ptr,
+  // collecting and reporting data regarding dirty, difference, etc.
+  void ProcessRegion(const MappingData& mapping_data,
+                     RemoteProcesses remotes,
+                     const uint8_t* begin_image_ptr,
+                     const uint8_t* end_image_ptr)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
+    T* entry = reinterpret_cast<T*>(const_cast<uint8_t*>(current));
+    while (reinterpret_cast<uintptr_t>(entry) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
+      ComputeEntryDirty(entry, begin_image_ptr, mapping_data.dirty_page_set);
+
+      entry = RegionSpecializedBase<T>::GetNextEntry(entry);
+    }
+
+    // Looking at only dirty pages, figure out how many of those bytes belong to dirty entries.
+    // TODO: fix this now that there are multiple regions in a mapping.
+    float true_dirtied_percent =
+        RegionCommon<T>::GetDirtyEntryBytes() * 1.0f / (mapping_data.dirty_pages * kPageSize);
+
+    // Entry specific statistics.
+    os_ << RegionCommon<T>::GetDifferentEntryCount() << " different entries, \n  "
+        << RegionCommon<T>::GetDirtyEntryBytes() << " different entry [bytes], \n  "
+        << RegionCommon<T>::GetFalseDirtyEntryCount() << " false dirty entries,\n  "
+        << RegionCommon<T>::GetFalseDirtyEntryBytes() << " false dirty entry [bytes], \n  "
+        << true_dirtied_percent << " different entries-vs-total in a dirty page;\n  "
+        << "";
+
+    if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) {
+      // We only reach this point if both pids were specified.  Furthermore,
+      // entries are only displayed here if they differed in both the image
+      // and the zygote, so they are probably private dirty.
+      CHECK(remotes == RemoteProcesses::kImageAndZygote);
+      os_ << "\n" << "  Zygote dirty entries (probably shared dirty): ";
+      DiffDirtyEntries(ProcessType::kZygote, begin_image_ptr, RegionCommon<T>::zygote_contents_);
+    }
+    os_ << "\n";
+    switch (remotes) {
+      case RemoteProcesses::kZygoteOnly:
+        os_ << "  Zygote shared dirty entries: ";
+        break;
+      case RemoteProcesses::kImageAndZygote:
+        os_ << "  Application dirty entries (private dirty): ";
+        break;
+      case RemoteProcesses::kImageOnly:
+        os_ << "  Application dirty entries (unknown whether private or shared dirty): ";
+        break;
+    }
+    DiffDirtyEntries(ProcessType::kRemote, begin_image_ptr, RegionCommon<T>::remote_contents_);
+    RegionSpecializedBase<T>::DumpDirtyEntries();
+    RegionSpecializedBase<T>::DumpFalseDirtyEntries();
+    RegionSpecializedBase<T>::DumpCleanEntries();
+  }
+
+ private:
+  std::ostream& os_;
+
+  void DiffDirtyEntries(ProcessType process_type,
+                        const uint8_t* begin_image_ptr,
+                        std::vector<uint8_t>* contents)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    os_ << RegionCommon<T>::dirty_entries_.size() << "\n";
+    const std::set<T*>& entries =
+        (process_type == ProcessType::kZygote) ?
+            RegionCommon<T>::zygote_dirty_entries_:
+            RegionCommon<T>::image_dirty_entries_;
+    for (T* entry : entries) {
+      uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
+      ptrdiff_t offset = entry_bytes - begin_image_ptr;
+      uint8_t* remote_bytes = &(*contents)[offset];
+      RegionSpecializedBase<T>::DiffEntryContents(entry, remote_bytes);
+    }
+  }
+
+  void ComputeEntryDirty(T* entry,
+                         const uint8_t* begin_image_ptr,
+                         const std::set<size_t>& dirty_pages)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Set up pointers in the remote and the zygote for comparison.
+    uint8_t* current = reinterpret_cast<uint8_t*>(entry);
+    ptrdiff_t offset = current - begin_image_ptr;
+    T* entry_remote =
+        reinterpret_cast<T*>(const_cast<uint8_t*>(&(*RegionCommon<T>::remote_contents_)[offset]));
+    const uint8_t* current_zygote =
+        RegionCommon<T>::zygote_contents_->empty() ? nullptr :
+                                                     &(*RegionCommon<T>::zygote_contents_)[offset];
+    T* entry_zygote = reinterpret_cast<T*>(const_cast<uint8_t*>(current_zygote));
+    // Visit and classify entries at the current location.
+    RegionSpecializedBase<T>::VisitEntry(entry);
+    bool different_image_entry = EntriesDiffer(entry, entry_remote);
+    if (different_image_entry) {
+      bool different_zygote_entry = false;
+      if (entry_zygote != nullptr) {
+        different_zygote_entry = EntriesDiffer(entry, entry_zygote);
+      }
+      if (different_zygote_entry) {
+        // Different from zygote.
+        RegionCommon<T>::AddZygoteDirtyEntry(entry);
+        RegionSpecializedBase<T>::AddDirtyEntry(entry, entry_remote);
+      } else {
+        // Just different from image.
+        RegionCommon<T>::AddImageDirtyEntry(entry);
+        RegionSpecializedBase<T>::AddDirtyEntry(entry, entry_remote);
+      }
+    } else {
+      RegionSpecializedBase<T>::AddCleanEntry(entry);
+    }
+    if (!different_image_entry && RegionCommon<T>::IsEntryOnDirtyPage(entry, dirty_pages)) {
+      // This entry was either never mutated or got mutated back to the same value.
+      // TODO: Do I want to distinguish a "different" vs a "dirty" page here?
+      RegionSpecializedBase<T>::AddFalseDirtyEntry(entry);
+    }
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RegionData);
+};
+
+}  // namespace
+
+
 class ImgDiagDumper {
  public:
   explicit ImgDiagDumper(std::ostream* os,
@@ -123,8 +920,6 @@
     CHECK(boot_map_.end >= boot_map_.start);
     boot_map_size_ = boot_map_.end - boot_map_.start;
 
-    pointer_size_ = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
-
     // Open /proc/<image_diff_pid_>/mem and read as remote_contents_.
     std::string image_file_name =
         StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_));  // NOLINT [runtime/int]
@@ -188,7 +983,7 @@
       return false;
     }
 
-    // Commit the mappings, etc., to the object state.
+    // Commit the mappings, etc.
     proc_maps_ = std::move(tmp_proc_maps);
     remote_contents_ = std::move(tmp_remote_contents);
     zygote_contents_ = std::move(tmp_zygote_contents);
@@ -228,14 +1023,7 @@
     return DumpImageDiffMap();
   }
 
-  bool ComputeDirtyBytes(const uint8_t* image_begin,
-                         size_t* dirty_pages /*out*/,
-                         size_t* different_pages /*out*/,
-                         size_t* different_bytes /*out*/,
-                         size_t* different_int32s /*out*/,
-                         size_t* private_pages /*out*/,
-                         size_t* private_dirty_pages /*out*/,
-                         std::set<size_t>* dirty_page_set_local) {
+  bool ComputeDirtyBytes(const uint8_t* image_begin, MappingData* mapping_data /*out*/) {
     std::ostream& os = *os_;
 
     size_t virtual_page_idx = 0;   // Virtual page number (for an absolute memory address)
@@ -254,7 +1042,7 @@
       uint8_t* remote_ptr = &remote_contents_[offset];
 
       if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) {
-        ++*different_pages;
+        mapping_data->different_pages++;
 
         // Count the number of 32-bit integers that are different.
         for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) {
@@ -262,7 +1050,7 @@
           const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr);
 
           if (remote_ptr_int32[i] != local_ptr_int32[i]) {
-            ++*different_int32s;
+            mapping_data->different_int32s++;
           }
         }
       }
@@ -286,7 +1074,7 @@
       page_idx = (offset + page_off_begin) / kPageSize;
       if (*local_ptr != *remote_ptr) {
         // Track number of bytes that are different
-        ++*different_bytes;
+        mapping_data->different_bytes++;
       }
 
       // Independently count the # of dirty pages on the remote side
@@ -307,294 +1095,38 @@
           os << error_msg;
           return false;
         } else if (dirtiness > 0) {
-          ++*dirty_pages;
-          dirty_page_set_local->insert(dirty_page_set_local->end(), virtual_page_idx);
+          mapping_data->dirty_pages++;
+          mapping_data->dirty_page_set.insert(mapping_data->dirty_page_set.end(), virtual_page_idx);
         }
 
         bool is_dirty = dirtiness > 0;
         bool is_private = page_count == 1;
 
         if (page_count == 1) {
-          ++*private_pages;
+          mapping_data->private_pages++;
         }
 
         if (is_dirty && is_private) {
-          ++*private_dirty_pages;
+          mapping_data->private_dirty_pages++;
         }
       }
     }
+    mapping_data->false_dirty_pages = mapping_data->dirty_pages - mapping_data->different_pages;
+    // Print low-level (bytes, int32s, pages) statistics.
+    os << mapping_data->different_bytes << " differing bytes,\n  "
+       << mapping_data->different_int32s << " differing int32s,\n  "
+       << mapping_data->different_pages << " differing pages,\n  "
+       << mapping_data->dirty_pages << " pages are dirty;\n  "
+       << mapping_data->false_dirty_pages << " pages are false dirty;\n  "
+       << mapping_data->private_pages << " pages are private;\n  "
+       << mapping_data->private_dirty_pages << " pages are Private_Dirty\n  ";
+
     return true;
   }
 
-  bool ObjectIsOnDirtyPage(const uint8_t* item,
-                           size_t size,
-                           const std::set<size_t>& dirty_page_set_local) {
-    size_t page_off = 0;
-    size_t current_page_idx;
-    uintptr_t object_address = reinterpret_cast<uintptr_t>(item);
-    // Iterate every page this object belongs to
-    do {
-      current_page_idx = object_address / kPageSize + page_off;
-
-      if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) {
-        // This object is on a dirty page
-        return true;
-      }
-
-      page_off++;
-    } while ((current_page_idx * kPageSize) < RoundUp(object_address + size, kObjectAlignment));
-
-    return false;
-  }
-
-  static std::string PrettyFieldValue(ArtField* field, mirror::Object* obj)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    std::ostringstream oss;
-    switch (field->GetTypeAsPrimitiveType()) {
-      case Primitive::kPrimNot: {
-        oss << obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
-            field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimBoolean: {
-        oss << static_cast<bool>(obj->GetFieldBoolean<kVerifyNone>(field->GetOffset()));
-        break;
-      }
-      case Primitive::kPrimByte: {
-        oss << static_cast<int32_t>(obj->GetFieldByte<kVerifyNone>(field->GetOffset()));
-        break;
-      }
-      case Primitive::kPrimChar: {
-        oss << obj->GetFieldChar<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimShort: {
-        oss << obj->GetFieldShort<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimInt: {
-        oss << obj->GetField32<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimLong: {
-        oss << obj->GetField64<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimFloat: {
-        oss << obj->GetField32<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimDouble: {
-        oss << obj->GetField64<kVerifyNone>(field->GetOffset());
-        break;
-      }
-      case Primitive::kPrimVoid: {
-        oss << "void";
-        break;
-      }
-    }
-    return oss.str();
-  }
-
-  // Aggregate and detail class data from an image diff.
-  struct ClassData {
-    size_t dirty_object_count = 0;
-
-    // Track only the byte-per-byte dirtiness (in bytes)
-    size_t dirty_object_byte_count = 0;
-
-    // Track the object-by-object dirtiness (in bytes)
-    size_t dirty_object_size_in_bytes = 0;
-
-    size_t clean_object_count = 0;
-
-    std::string descriptor;
-
-    size_t false_dirty_byte_count = 0;
-    size_t false_dirty_object_count = 0;
-    std::vector<const uint8_t*> false_dirty_objects;
-
-    // Remote pointers to dirty objects
-    std::vector<const uint8_t*> dirty_objects;
-  };
-
-  void DiffObjectContents(mirror::Object* obj,
-                          uint8_t* remote_bytes,
-                          std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_) {
-    const char* tabs = "    ";
-    // Attempt to find fields for all dirty bytes.
-    mirror::Class* klass = obj->GetClass();
-    if (obj->IsClass()) {
-      os << tabs << "Class " << mirror::Class::PrettyClass(obj->AsClass()) << " " << obj << "\n";
-    } else {
-      os << tabs << "Instance of " << mirror::Class::PrettyClass(klass) << " " << obj << "\n";
-    }
-
-    std::unordered_set<ArtField*> dirty_instance_fields;
-    std::unordered_set<ArtField*> dirty_static_fields;
-    const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj);
-    mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>(remote_bytes);
-    for (size_t i = 0, count = obj->SizeOf(); i < count; ++i) {
-      if (obj_bytes[i] != remote_bytes[i]) {
-        ArtField* field = ArtField::FindInstanceFieldWithOffset</*exact*/false>(klass, i);
-        if (field != nullptr) {
-          dirty_instance_fields.insert(field);
-        } else if (obj->IsClass()) {
-          field = ArtField::FindStaticFieldWithOffset</*exact*/false>(obj->AsClass(), i);
-          if (field != nullptr) {
-            dirty_static_fields.insert(field);
-          }
-        }
-        if (field == nullptr) {
-          if (klass->IsArrayClass()) {
-            mirror::Class* component_type = klass->GetComponentType();
-            Primitive::Type primitive_type = component_type->GetPrimitiveType();
-            size_t component_size = Primitive::ComponentSize(primitive_type);
-            size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value();
-            if (i >= data_offset) {
-              os << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n";
-              // Skip to next element to prevent spam.
-              i += component_size - 1;
-              continue;
-            }
-          }
-          os << tabs << "No field for byte offset " << i << "\n";
-        }
-      }
-    }
-    // Dump different fields. TODO: Dump field contents.
-    if (!dirty_instance_fields.empty()) {
-      os << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n";
-      for (ArtField* field : dirty_instance_fields) {
-        os << tabs << ArtField::PrettyField(field)
-           << " original=" << PrettyFieldValue(field, obj)
-           << " remote=" << PrettyFieldValue(field, remote_obj) << "\n";
-      }
-    }
-    if (!dirty_static_fields.empty()) {
-      os << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
-      for (ArtField* field : dirty_static_fields) {
-        os << tabs << ArtField::PrettyField(field)
-           << " original=" << PrettyFieldValue(field, obj)
-           << " remote=" << PrettyFieldValue(field, remote_obj) << "\n";
-      }
-    }
-    os << "\n";
-  }
-
-  struct ObjectRegionData {
-    // Count of objects that are different.
-    size_t different_objects = 0;
-
-    // Local objects that are dirty (differ in at least one byte).
-    size_t dirty_object_bytes = 0;
-    std::vector<const uint8_t*>* dirty_objects;
-
-    // Local objects that are clean, but located on dirty pages.
-    size_t false_dirty_object_bytes = 0;
-    std::vector<const uint8_t*> false_dirty_objects;
-
-    // Image dirty objects
-    // If zygote_pid_only_ == true, these are shared dirty objects in the zygote.
-    // If zygote_pid_only_ == false, these are private dirty objects in the application.
-    std::set<const uint8_t*> image_dirty_objects;
-
-    // Zygote dirty objects (probably private dirty).
-    // We only add objects here if they differed in both the image and the zygote, so
-    // they are probably private dirty.
-    std::set<const uint8_t*> zygote_dirty_objects;
-
-    std::map<off_t /* field offset */, size_t /* count */>* field_dirty_count;
-  };
-
-  void ComputeObjectDirty(const uint8_t* current,
-                          const uint8_t* current_remote,
-                          const uint8_t* current_zygote,
-                          ClassData* obj_class_data,
-                          size_t obj_size,
-                          const std::set<size_t>& dirty_page_set_local,
-                          ObjectRegionData* region_data /*out*/) {
-    bool different_image_object = memcmp(current, current_remote, obj_size) != 0;
-    if (different_image_object) {
-      bool different_zygote_object = false;
-      if (!zygote_contents_.empty()) {
-        different_zygote_object = memcmp(current, current_zygote, obj_size) != 0;
-      }
-      if (different_zygote_object) {
-        // Different from zygote.
-        region_data->zygote_dirty_objects.insert(current);
-      } else {
-        // Just different from image.
-        region_data->image_dirty_objects.insert(current);
-      }
-
-      ++region_data->different_objects;
-      region_data->dirty_object_bytes += obj_size;
-
-      ++obj_class_data->dirty_object_count;
-
-      // Go byte-by-byte and figure out what exactly got dirtied
-      size_t dirty_byte_count_per_object = 0;
-      for (size_t i = 0; i < obj_size; ++i) {
-        if (current[i] != current_remote[i]) {
-          dirty_byte_count_per_object++;
-        }
-      }
-      obj_class_data->dirty_object_byte_count += dirty_byte_count_per_object;
-      obj_class_data->dirty_object_size_in_bytes += obj_size;
-      obj_class_data->dirty_objects.push_back(current_remote);
-    } else {
-      ++obj_class_data->clean_object_count;
-    }
-
-    if (different_image_object) {
-      if (region_data->dirty_objects != nullptr) {
-        // print the fields that are dirty
-        for (size_t i = 0; i < obj_size; ++i) {
-          if (current[i] != current_remote[i]) {
-            size_t dirty_count = 0;
-            if (region_data->field_dirty_count->find(i) != region_data->field_dirty_count->end()) {
-              dirty_count = (*region_data->field_dirty_count)[i];
-            }
-            (*region_data->field_dirty_count)[i] = dirty_count + 1;
-          }
-        }
-
-        region_data->dirty_objects->push_back(current);
-      }
-      /*
-       * TODO: Resurrect this stuff in the client when we add ArtMethod iterator.
-      } else {
-        std::string descriptor = GetClassDescriptor(klass);
-        if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
-          // this is an ArtMethod
-          ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj);
-
-          // print the fields that are dirty
-          for (size_t i = 0; i < obj_size; ++i) {
-            if (current[i] != current_remote[i]) {
-              art_method_field_dirty_count[i]++;
-            }
-          }
-
-          art_method_dirty_objects.push_back(art_method);
-        }
-      }
-      */
-    } else if (ObjectIsOnDirtyPage(current, obj_size, dirty_page_set_local)) {
-      // This object was either never mutated or got mutated back to the same value.
-      // TODO: Do I want to distinguish a "different" vs a "dirty" page here?
-      region_data->false_dirty_objects.push_back(current);
-      obj_class_data->false_dirty_objects.push_back(current);
-      region_data->false_dirty_object_bytes += obj_size;
-      obj_class_data->false_dirty_byte_count += obj_size;
-      obj_class_data->false_dirty_object_count += 1;
-    }
-  }
-
   // Look at /proc/$pid/mem and only diff the things from there
   bool DumpImageDiffMap()
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     std::ostream& os = *os_;
     std::string error_msg;
 
@@ -624,384 +1156,37 @@
       // If we wanted even more validation we could map the ImageHeader from the file
     }
 
-    size_t dirty_pages = 0;
-    size_t different_pages = 0;
-    size_t different_bytes = 0;
-    size_t different_int32s = 0;
-    size_t private_pages = 0;
-    size_t private_dirty_pages = 0;
+    MappingData mapping_data;
 
-    // Set of the local virtual page indices that are dirty
-    std::set<size_t> dirty_page_set_local;
-
-    if (!ComputeDirtyBytes(image_begin,
-                           &dirty_pages,
-                           &different_pages,
-                           &different_bytes,
-                           &different_int32s,
-                           &private_pages,
-                           &private_dirty_pages,
-                           &dirty_page_set_local)) {
+    os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", "
+       << reinterpret_cast<void*>(boot_map_.end) << ") had:\n  ";
+    if (!ComputeDirtyBytes(image_begin, &mapping_data)) {
       return false;
     }
 
-    std::map<mirror::Class*, ClassData> class_data;
+    RegionData<mirror::Object> object_region_data(os_,
+                                                  &remote_contents_,
+                                                  &zygote_contents_,
+                                                  boot_map_,
+                                                  image_header_);
 
-    // Walk each object in the remote image space and compare it against ours
-    std::map<off_t /* field offset */, int /* count */> art_method_field_dirty_count;
-    std::vector<ArtMethod*> art_method_dirty_objects;
-
-    std::map<off_t /* field offset */, size_t /* count */> class_field_dirty_count;
-    std::vector<const uint8_t*> class_dirty_objects;
-
-
-    // Look up remote classes by their descriptor
-    std::map<std::string, mirror::Class*> remote_class_map;
-    // Look up local classes by their descriptor
-    std::map<std::string, mirror::Class*> local_class_map;
-
-    const uint8_t* begin_image_ptr = image_begin_unaligned;
-    const uint8_t* end_image_ptr = image_mirror_end_unaligned;
-
-    ObjectRegionData region_data;
-
-    const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
-    while (reinterpret_cast<uintptr_t>(current) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
-      CHECK_ALIGNED(current, kObjectAlignment);
-      mirror::Object* obj = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
-
-      // Sanity check that we are reading a real object
-      CHECK(obj->GetClass() != nullptr) << "Image object at address " << obj << " has null class";
-      if (kUseBakerReadBarrier) {
-        obj->AssertReadBarrierState();
-      }
-
-      mirror::Class* klass = obj->GetClass();
-      size_t obj_size = obj->SizeOf();
-      ClassData& obj_class_data = class_data[klass];
-
-      // Check against the other object and see if they are different
-      ptrdiff_t offset = current - begin_image_ptr;
-      const uint8_t* current_remote = &remote_contents_[offset];
-      const uint8_t* current_zygote =
-          zygote_contents_.empty() ? nullptr : &zygote_contents_[offset];
-
-      if (klass->IsClassClass()) {
-        region_data.field_dirty_count = &class_field_dirty_count;
-        region_data.dirty_objects = &class_dirty_objects;
-      } else {
-        region_data.field_dirty_count = nullptr;
-        region_data.dirty_objects = nullptr;
-      }
-
-
-      ComputeObjectDirty(current,
-                         current_remote,
-                         current_zygote,
-                         &obj_class_data,
-                         obj_size,
-                         dirty_page_set_local,
-                         &region_data);
-
-      // Object specific stuff.
-      std::string descriptor = GetClassDescriptor(klass);
-      if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
-        local_class_map[descriptor] = reinterpret_cast<mirror::Class*>(obj);
-        mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>(
-            const_cast<uint8_t*>(current_remote));
-        remote_class_map[descriptor] = reinterpret_cast<mirror::Class*>(remote_obj);
-      }
-
-      // Unconditionally store the class descriptor in case we need it later
-      obj_class_data.descriptor = descriptor;
-
-      current += RoundUp(obj_size, kObjectAlignment);
-    }
-
-    // Looking at only dirty pages, figure out how many of those bytes belong to dirty objects.
-    float true_dirtied_percent = region_data.dirty_object_bytes * 1.0f / (dirty_pages * kPageSize);
-    size_t false_dirty_pages = dirty_pages - different_pages;
-
-    os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", "
-       << reinterpret_cast<void*>(boot_map_.end) << ") had: \n  "
-       << different_bytes << " differing bytes, \n  "
-       << different_int32s << " differing int32s, \n  "
-       << region_data.different_objects << " different objects, \n  "
-       << region_data.dirty_object_bytes << " different object [bytes], \n  "
-       << region_data.false_dirty_objects.size() << " false dirty objects,\n  "
-       << region_data.false_dirty_object_bytes << " false dirty object [bytes], \n  "
-       << true_dirtied_percent << " different objects-vs-total in a dirty page;\n  "
-       << different_pages << " different pages; \n  "
-       << dirty_pages << " pages are dirty; \n  "
-       << false_dirty_pages << " pages are false dirty; \n  "
-       << private_pages << " pages are private; \n  "
-       << private_dirty_pages << " pages are Private_Dirty\n  "
-       << "";
-
-    // vector of pairs (int count, Class*)
-    auto dirty_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
-        class_data, [](const ClassData& d) { return d.dirty_object_count; });
-    auto clean_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
-        class_data, [](const ClassData& d) { return d.clean_object_count; });
-
-    if (!region_data.zygote_dirty_objects.empty()) {
-      // We only reach this point if both pids were specified.  Furthermore,
-      // objects are only displayed here if they differed in both the image
-      // and the zygote, so they are probably private dirty.
-      CHECK(image_diff_pid_ > 0 && zygote_diff_pid_ > 0);
-      os << "\n" << "  Zygote dirty objects (probably shared dirty): "
-         << region_data.zygote_dirty_objects.size() << "\n";
-      for (const uint8_t* obj_bytes : region_data.zygote_dirty_objects) {
-        auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));
-        ptrdiff_t offset = obj_bytes - begin_image_ptr;
-        uint8_t* remote_bytes = &zygote_contents_[offset];
-        DiffObjectContents(obj, remote_bytes, os);
-      }
-    }
-    os << "\n";
+    RemoteProcesses remotes;
     if (zygote_pid_only_) {
-      // image_diff_pid_ is the zygote process.
-      os << "  Zygote shared dirty objects: ";
+      remotes = RemoteProcesses::kZygoteOnly;
+    } else if (zygote_diff_pid_ > 0) {
+      remotes = RemoteProcesses::kImageAndZygote;
     } else {
-      // image_diff_pid_ is actually the image (application) process.
-      if (zygote_diff_pid_ > 0) {
-        os << "  Application dirty objects (private dirty): ";
-      } else {
-        os << "  Application dirty objects (unknown whether private or shared dirty): ";
-      }
-    }
-    os << region_data.image_dirty_objects.size() << "\n";
-    for (const uint8_t* obj_bytes : region_data.image_dirty_objects) {
-      auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));
-      ptrdiff_t offset = obj_bytes - begin_image_ptr;
-      uint8_t* remote_bytes = &remote_contents_[offset];
-      DiffObjectContents(obj, remote_bytes, os);
+      remotes = RemoteProcesses::kImageOnly;
     }
 
-    os << "\n" << "  Dirty object count by class:\n";
-    for (const auto& vk_pair : dirty_object_class_values) {
-      int dirty_object_count = vk_pair.first;
-      mirror::Class* klass = vk_pair.second;
-      int object_sizes = class_data[klass].dirty_object_size_in_bytes;
-      float avg_dirty_bytes_per_class =
-          class_data[klass].dirty_object_byte_count * 1.0f / object_sizes;
-      float avg_object_size = object_sizes * 1.0f / dirty_object_count;
-      const std::string& descriptor = class_data[klass].descriptor;
-      os << "    " << mirror::Class::PrettyClass(klass) << " ("
-         << "objects: " << dirty_object_count << ", "
-         << "avg dirty bytes: " << avg_dirty_bytes_per_class << ", "
-         << "avg object size: " << avg_object_size << ", "
-         << "class descriptor: '" << descriptor << "'"
-         << ")\n";
-
-      constexpr size_t kMaxAddressPrint = 5;
-      if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
-        os << "      sample object addresses: ";
-        for (size_t i = 0; i < art_method_dirty_objects.size() && i < kMaxAddressPrint; ++i) {
-          auto art_method = art_method_dirty_objects[i];
-
-          os << reinterpret_cast<void*>(art_method) << ", ";
-        }
-        os << "\n";
-
-        os << "      dirty byte +offset:count list = ";
-        auto art_method_field_dirty_count_sorted =
-            SortByValueDesc<off_t, int, int>(art_method_field_dirty_count);
-        for (auto pair : art_method_field_dirty_count_sorted) {
-          off_t offset = pair.second;
-          int count = pair.first;
-
-          os << "+" << offset << ":" << count << ", ";
-        }
-
-        os << "\n";
-
-        os << "      field contents:\n";
-        const auto& dirty_objects_list = class_data[klass].dirty_objects;
-        for (const uint8_t* uobj : dirty_objects_list) {
-          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
-          // remote method
-          auto art_method = reinterpret_cast<ArtMethod*>(obj);
-
-          // remote class
-          mirror::Class* remote_declaring_class =
-            FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents_, boot_map_);
-
-          // local class
-          mirror::Class* declaring_class =
-            RemoteContentsPointerToLocal(remote_declaring_class, remote_contents_, image_header_);
-
-          os << "        " << reinterpret_cast<void*>(obj) << " ";
-          os << "  entryPointFromJni: "
-             << reinterpret_cast<const void*>(
-                    art_method->GetDataPtrSize(pointer_size_)) << ", ";
-          os << "  entryPointFromQuickCompiledCode: "
-             << reinterpret_cast<const void*>(
-                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))
-             << ", ";
-          os << "  isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
-          os << "  class_status (local): " << declaring_class->GetStatus();
-          os << "  class_status (remote): " << remote_declaring_class->GetStatus();
-          os << "\n";
-        }
-      }
-      if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
-        os << "       sample object addresses: ";
-        for (size_t i = 0; i < class_dirty_objects.size() && i < kMaxAddressPrint; ++i) {
-          auto class_ptr = class_dirty_objects[i];
-
-          os << reinterpret_cast<const void*>(class_ptr) << ", ";
-        }
-        os << "\n";
-
-        os << "       dirty byte +offset:count list = ";
-        auto class_field_dirty_count_sorted =
-            SortByValueDesc<off_t, int, size_t>(class_field_dirty_count);
-        for (auto pair : class_field_dirty_count_sorted) {
-          off_t offset = pair.second;
-          int count = pair.first;
-
-          os << "+" << offset << ":" << count << ", ";
-        }
-        os << "\n";
-
-        os << "      field contents:\n";
-        // TODO: templatize this to avoid the awful casts down to uint8_t* and back.
-        const auto& dirty_objects_list = class_data[klass].dirty_objects;
-        for (const uint8_t* uobj : dirty_objects_list) {
-          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
-          // remote class object
-          auto remote_klass = reinterpret_cast<mirror::Class*>(obj);
-
-          // local class object
-          auto local_klass = RemoteContentsPointerToLocal(remote_klass,
-                                                          remote_contents_,
-                                                          image_header_);
-
-          os << "        " << reinterpret_cast<const void*>(obj) << " ";
-          os << "  class_status (remote): " << remote_klass->GetStatus() << ", ";
-          os << "  class_status (local): " << local_klass->GetStatus();
-          os << "\n";
-        }
-      }
-    }
-
-    auto false_dirty_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
-        class_data, [](const ClassData& d) { return d.false_dirty_object_count; });
-
-    os << "\n" << "  False-dirty object count by class:\n";
-    for (const auto& vk_pair : false_dirty_object_class_values) {
-      int object_count = vk_pair.first;
-      mirror::Class* klass = vk_pair.second;
-      int object_sizes = class_data[klass].false_dirty_byte_count;
-      float avg_object_size = object_sizes * 1.0f / object_count;
-      const std::string& descriptor = class_data[klass].descriptor;
-      os << "    " << mirror::Class::PrettyClass(klass) << " ("
-         << "objects: " << object_count << ", "
-         << "avg object size: " << avg_object_size << ", "
-         << "total bytes: " << object_sizes << ", "
-         << "class descriptor: '" << descriptor << "'"
-         << ")\n";
-
-      if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
-        // TODO: templatize this to avoid the awful casts down to uint8_t* and back.
-        auto& art_method_false_dirty_objects = class_data[klass].false_dirty_objects;
-
-        os << "      field contents:\n";
-        for (const uint8_t* uobj : art_method_false_dirty_objects) {
-          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
-          // local method
-          auto art_method = reinterpret_cast<ArtMethod*>(obj);
-
-          // local class
-          mirror::Class* declaring_class = art_method->GetDeclaringClass();
-
-          os << "        " << reinterpret_cast<const void*>(obj) << " ";
-          os << "  entryPointFromJni: "
-             << reinterpret_cast<const void*>(
-                    art_method->GetDataPtrSize(pointer_size_)) << ", ";
-          os << "  entryPointFromQuickCompiledCode: "
-             << reinterpret_cast<const void*>(
-                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))
-             << ", ";
-          os << "  isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
-          os << "  class_status (local): " << declaring_class->GetStatus();
-          os << "\n";
-        }
-      }
-    }
-
-    os << "\n" << "  Clean object count by class:\n";
-    for (const auto& vk_pair : clean_object_class_values) {
-      os << "    " << mirror::Class::PrettyClass(vk_pair.second) << " (" << vk_pair.first << ")\n";
-    }
+    object_region_data.ProcessRegion(mapping_data,
+                                     remotes,
+                                     image_begin_unaligned,
+                                     image_mirror_end_unaligned);
 
     return true;
   }
 
-  // Fixup a remote pointer that we read from a foreign boot.art to point to our own memory.
-  // Returned pointer will point to inside of remote_contents.
-  template <typename T>
-  static T* FixUpRemotePointer(T* remote_ptr,
-                               std::vector<uint8_t>& remote_contents,
-                               const backtrace_map_t& boot_map) {
-    if (remote_ptr == nullptr) {
-      return nullptr;
-    }
-
-    uintptr_t remote = reinterpret_cast<uintptr_t>(remote_ptr);
-
-    CHECK_LE(boot_map.start, remote);
-    CHECK_GT(boot_map.end, remote);
-
-    off_t boot_offset = remote - boot_map.start;
-
-    return reinterpret_cast<T*>(&remote_contents[boot_offset]);
-  }
-
-  template <typename T>
-  static T* RemoteContentsPointerToLocal(T* remote_ptr,
-                                         std::vector<uint8_t>& remote_contents,
-                                         const ImageHeader& image_header) {
-    if (remote_ptr == nullptr) {
-      return nullptr;
-    }
-
-    uint8_t* remote = reinterpret_cast<uint8_t*>(remote_ptr);
-    ptrdiff_t boot_offset = remote - &remote_contents[0];
-
-    const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + boot_offset;
-
-    return reinterpret_cast<T*>(const_cast<uint8_t*>(local_ptr));
-  }
-
-  static std::string GetClassDescriptor(mirror::Class* klass)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-    CHECK(klass != nullptr);
-
-    std::string descriptor;
-    const char* descriptor_str = klass->GetDescriptor(&descriptor);
-
-    return std::string(descriptor_str);
-  }
-
-  template <typename K, typename V, typename D>
-  static std::vector<std::pair<V, K>> SortByValueDesc(
-      const std::map<K, D> map,
-      std::function<V(const D&)> value_mapper = [](const D& d) { return static_cast<V>(d); }) {
-    // Store value->key so that we can use the default sort from pair which
-    // sorts by value first and then key
-    std::vector<std::pair<V, K>> value_key_vector;
-
-    for (const auto& kv_pair : map) {
-      value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
-    }
-
-    // Sort in reverse (descending order)
-    std::sort(value_key_vector.rbegin(), value_key_vector.rend());
-    return value_key_vector;
-  }
-
   static bool GetPageFrameNumber(File* page_map_file,
                                 size_t virtual_page_index,
                                 uint64_t* page_frame_number,
@@ -1142,8 +1327,6 @@
   pid_t zygote_diff_pid_;  // Dump image diff against zygote boot.art if pid is non-negative
   bool zygote_pid_only_;  // The user only specified a pid for the zygote.
 
-  // Pointer size constant for object fields, etc.
-  PointerSize pointer_size_;
   // BacktraceMap used for finding the memory mapping of the image file.
   std::unique_ptr<BacktraceMap> proc_maps_;
   // Boot image mapping.
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 6133dd7..9e3e76c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -5619,12 +5619,18 @@
     return false;
   }
   // Verify
-  if (super->IsFinal() || super->IsInterface()) {
+  if (super->IsFinal()) {
+    ThrowVerifyError(klass.Get(),
+                     "Superclass %s of %s is declared final",
+                     super->PrettyDescriptor().c_str(),
+                     klass->PrettyDescriptor().c_str());
+    return false;
+  }
+  if (super->IsInterface()) {
     ThrowIncompatibleClassChangeError(klass.Get(),
-                                      "Superclass %s of %s is %s",
+                                      "Superclass %s of %s is an interface",
                                       super->PrettyDescriptor().c_str(),
-                                      klass->PrettyDescriptor().c_str(),
-                                      super->IsFinal() ? "declared final" : "an interface");
+                                      klass->PrettyDescriptor().c_str());
     return false;
   }
   if (!klass->CanAccess(super)) {
@@ -8980,51 +8986,6 @@
   return ret;
 }
 
-std::unordered_set<std::string> ClassLinker::GetClassDescriptorsForResolvedClasses(
-    const std::set<DexCacheResolvedClasses>& classes) {
-  ScopedTrace trace(__PRETTY_FUNCTION__);
-  std::unordered_set<std::string> ret;
-  Thread* const self = Thread::Current();
-  std::unordered_map<std::string, const DexFile*> location_to_dex_file;
-  ScopedObjectAccess soa(self);
-  ScopedAssertNoThreadSuspension ants(__FUNCTION__);
-  ReaderMutexLock mu(self, *Locks::dex_lock_);
-  for (const ClassLinker::DexCacheData& data : GetDexCachesData()) {
-    if (!self->IsJWeakCleared(data.weak_root)) {
-      ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(data.weak_root);
-      if (dex_cache != nullptr) {
-        const DexFile* dex_file = dex_cache->GetDexFile();
-        // There could be duplicates if two dex files with the same location are mapped.
-        location_to_dex_file.emplace(dex_file->GetLocation(), dex_file);
-      }
-    }
-  }
-  for (const DexCacheResolvedClasses& info : classes) {
-    const std::string& location = info.GetDexLocation();
-    auto found = location_to_dex_file.find(location);
-    if (found != location_to_dex_file.end()) {
-      const DexFile* dex_file = found->second;
-      VLOG(profiler) << "Found opened dex file for " << dex_file->GetLocation() << " with "
-                     << info.GetClasses().size() << " classes";
-      DCHECK_EQ(dex_file->GetLocationChecksum(), info.GetLocationChecksum());
-      for (dex::TypeIndex type_idx : info.GetClasses()) {
-        if (!dex_file->IsTypeIndexValid(type_idx)) {
-          // Something went bad. The profile is probably corrupted. Abort and return an emtpy set.
-          LOG(WARNING) << "Corrupted profile: invalid type index "
-              << type_idx.index_ << " in dex " << location;
-          return std::unordered_set<std::string>();
-        }
-        const DexFile::TypeId& type_id = dex_file->GetTypeId(type_idx);
-        const char* descriptor = dex_file->GetTypeDescriptor(type_id);
-        ret.insert(descriptor);
-      }
-    } else {
-      VLOG(class_linker) << "Failed to find opened dex file for location " << location;
-    }
-  }
-  return ret;
-}
-
 class ClassLinker::FindVirtualMethodHolderVisitor : public ClassVisitor {
  public:
   FindVirtualMethodHolderVisitor(const ArtMethod* method, PointerSize pointer_size)
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 4a99c66..cb28187 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -614,11 +614,6 @@
   std::set<DexCacheResolvedClasses> GetResolvedClasses(bool ignore_boot_classes)
       REQUIRES(!Locks::dex_lock_);
 
-  // Returns the class descriptors for loaded dex files.
-  std::unordered_set<std::string> GetClassDescriptorsForResolvedClasses(
-      const std::set<DexCacheResolvedClasses>& classes)
-      REQUIRES(!Locks::dex_lock_);
-
   static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
                                 ObjPtr<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 8dda04e..7e762c3 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -785,6 +785,60 @@
   return classpath;
 }
 
+void CommonRuntimeTestImpl::FillHeap(Thread* self,
+                                     ClassLinker* class_linker,
+                                     VariableSizedHandleScope* handle_scope) {
+  DCHECK(handle_scope != nullptr);
+
+  Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
+
+  // Class java.lang.Object.
+  Handle<mirror::Class> c(handle_scope->NewHandle(
+      class_linker->FindSystemClass(self, "Ljava/lang/Object;")));
+  // Array helps to fill memory faster.
+  Handle<mirror::Class> ca(handle_scope->NewHandle(
+      class_linker->FindSystemClass(self, "[Ljava/lang/Object;")));
+
+  // Start allocating with ~128K
+  size_t length = 128 * KB;
+  while (length > 40) {
+    const int32_t array_length = length / 4;  // Object[] has elements of size 4.
+    MutableHandle<mirror::Object> h(handle_scope->NewHandle<mirror::Object>(
+        mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), array_length)));
+    if (self->IsExceptionPending() || h == nullptr) {
+      self->ClearException();
+
+      // Try a smaller length
+      length = length / 2;
+      // Use at most a quarter the reported free space.
+      size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
+      if (length * 4 > mem) {
+        length = mem / 4;
+      }
+    }
+  }
+
+  // Allocate simple objects till it fails.
+  while (!self->IsExceptionPending()) {
+    handle_scope->NewHandle<mirror::Object>(c->AllocObject(self));
+  }
+  self->ClearException();
+}
+
+void CommonRuntimeTestImpl::SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options) {
+  // Use a smaller heap
+  bool found = false;
+  for (std::pair<std::string, const void*>& pair : *options) {
+    if (pair.first.find("-Xmx") == 0) {
+      pair.first = "-Xmx4M";  // Smallest we can go.
+      found = true;
+    }
+  }
+  if (!found) {
+    options->emplace_back("-Xmx4M", nullptr);
+  }
+}
+
 CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
   vm_->SetCheckJniAbortHook(Hook, &actual_);
 }
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index daf9ac3..74bc0b2 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -44,6 +44,8 @@
 class JavaVMExt;
 class Runtime;
 typedef std::vector<std::pair<std::string, const void*>> RuntimeOptions;
+class Thread;
+class VariableSizedHandleScope;
 
 uint8_t* DecodeBase64(const char* src, size_t* dst_size);
 
@@ -105,6 +107,14 @@
   // Retuerns the filename for a test dex (i.e. XandY or ManyMethods).
   std::string GetTestDexFileName(const char* name) const;
 
+  // A helper function to fill the heap.
+  static void FillHeap(Thread* self,
+                       ClassLinker* class_linker,
+                       VariableSizedHandleScope* handle_scope)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // A helper to set up a small heap (4M) to make FillHeap faster.
+  static void SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options);
+
  protected:
   // Allow subclases such as CommonCompilerTest to add extra options.
   virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) {}
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 27501b9..3bee560 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -747,7 +747,7 @@
 #ifdef __aarch64__
 
 static void FlushJitCodeCacheRange(uint8_t* code_ptr,
-                                   uint8_t* writable_ptr ATTRIBUTE_UNUSED,
+                                   uint8_t* writable_ptr,
                                    size_t code_size) {
   // Cache maintenance instructions can cause permission faults when a
   // page is not present (e.g. swapped out or not backed). These
@@ -765,35 +765,63 @@
   // cache-line sizes for big and little cores.
   static const uintptr_t kSafeCacheLineSize = 32;
 
-  // Ensure stores are present in data cache.
-  __asm __volatile("dsb sy");
+  // Ensure stores are present in L1 data cache.
+  __asm __volatile("dsb ish" ::: "memory");
 
-  uintptr_t addr = RoundDown(reinterpret_cast<uintptr_t>(code_ptr), kSafeCacheLineSize);
-  const uintptr_t limit_addr = RoundUp(reinterpret_cast<uintptr_t>(code_ptr) + code_size,
-                                       kSafeCacheLineSize);
   volatile uint8_t mutant;
-  while (addr < limit_addr) {
+
+  // Push dirty cache-lines out to the point of unification (PoU). The
+  // point of unification is the first point in the cache/memory
+  // hierarchy where the instruction cache and data cache have the
+  // same view of memory. The PoU is where an instruction fetch will
+  // fetch the new code generated by the JIT.
+  //
+  // See: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch11s04.html
+  uintptr_t writable_addr = RoundDown(reinterpret_cast<uintptr_t>(writable_ptr),
+                                      kSafeCacheLineSize);
+  uintptr_t writable_end  = RoundUp(reinterpret_cast<uintptr_t>(writable_ptr) + code_size,
+                                    kSafeCacheLineSize);
+  while (writable_addr < writable_end) {
     // Read from the cache-line to minimize the chance that a cache
     // maintenance instruction causes a fault (see kernel bug comment
     // above).
-    mutant = *reinterpret_cast<const uint8_t*>(addr);
+    mutant = *reinterpret_cast<const uint8_t*>(writable_addr);
+
+    // Flush cache-line
+    __asm volatile("dc cvau, %0" :: "r"(writable_addr) : "memory");
+    writable_addr += kSafeCacheLineSize;
+  }
+
+  __asm __volatile("dsb ish" ::: "memory");
+
+  uintptr_t code_addr = RoundDown(reinterpret_cast<uintptr_t>(code_ptr), kSafeCacheLineSize);
+  const uintptr_t code_end = RoundUp(reinterpret_cast<uintptr_t>(code_ptr) + code_size,
+                                     kSafeCacheLineSize);
+  while (code_addr < code_end) {
+    // Read from the cache-line to minimize the chance that a cache
+    // maintenance instruction causes a fault (see kernel bug comment
+    // above).
+    mutant = *reinterpret_cast<const uint8_t*>(code_addr);
 
     // Invalidating the data cache line is only strictly necessary
     // when the JIT code cache has two mappings (the default). We know
     // this cache line is clean so this is just invalidating it (using
-    // "dc ivac" would be preferable, but is privileged).
-    __asm volatile("dc cvau, %0" :: "r"(addr));
+    // "dc ivac" would be preferable, but counts as a write and this
+    // memory may not be mapped write permission).
+    __asm volatile("dc cvau, %0" :: "r"(code_addr) : "memory");
 
     // Invalidate the instruction cache line to force instructions in
     // range to be re-fetched following update.
-    __asm volatile("ic ivau, %0" :: "r"(addr));
+    __asm volatile("ic ivau, %0" :: "r"(code_addr) : "memory");
 
-    addr += kSafeCacheLineSize;
+    code_addr += kSafeCacheLineSize;
   }
 
-  // Drain data and instruction buffers.
-  __asm __volatile("dsb sy");
-  __asm __volatile("isb sy");
+  // Wait for code cache invalidations to complete.
+  __asm __volatile("dsb ish" ::: "memory");
+
+  // Reset fetched instruction stream.
+  __asm __volatile("isb");
 }
 
 #else  // __aarch64
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 45c3792..0b7063d 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -1652,4 +1652,27 @@
   return &(inline_cache->FindOrAdd(dex_pc, DexPcData(&arena_))->second);
 }
 
+std::unordered_set<std::string> ProfileCompilationInfo::GetClassDescriptors(
+    const std::vector<const DexFile*>& dex_files) {
+  std::unordered_set<std::string> ret;
+  for (const DexFile* dex_file : dex_files) {
+    const DexFileData* data = FindDexData(dex_file);
+    if (data != nullptr) {
+      for (dex::TypeIndex type_idx : data->class_set) {
+        if (!dex_file->IsTypeIndexValid(type_idx)) {
+          // Something went bad. The profile is probably corrupted. Abort and return an emtpy set.
+          LOG(WARNING) << "Corrupted profile: invalid type index "
+              << type_idx.index_ << " in dex " << dex_file->GetLocation();
+          return std::unordered_set<std::string>();
+        }
+        const DexFile::TypeId& type_id = dex_file->GetTypeId(type_idx);
+        ret.insert(dex_file->GetTypeDescriptor(type_id));
+      }
+    } else {
+      VLOG(compiler) << "Failed to find profile data for " << dex_file->GetLocation();
+    }
+  }
+  return ret;
+}
+
 }  // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 079ce8d..4ab8be8 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -380,6 +380,9 @@
 
   ArenaAllocator* GetArena() { return &arena_; }
 
+  // Return all of the class descriptors in the profile for a set of dex files.
+  std::unordered_set<std::string> GetClassDescriptors(const std::vector<const DexFile*>& dex_files);
+
  private:
   enum ProfileLoadSatus {
     kProfileLoadWouldOverwiteData,
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index 27ce149..fb12841 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -36,11 +36,8 @@
  protected:
   void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
     // Use a smaller heap
-    for (std::pair<std::string, const void*>& pair : *options) {
-      if (pair.first.find("-Xmx") == 0) {
-        pair.first = "-Xmx4M";  // Smallest we can go.
-      }
-    }
+    SetUpRuntimeOptionsForFillHeap(options);
+
     options->push_back(std::make_pair("-Xint", nullptr));
   }
  public:
@@ -56,52 +53,6 @@
   bool completed_;
 };
 
-// Fill the heap.
-static const size_t kMaxHandles = 1000000;  // Use arbitrary large amount for now.
-static void FillHeap(Thread* self, ClassLinker* class_linker,
-                     std::unique_ptr<StackHandleScope<kMaxHandles>>* hsp,
-                     std::vector<MutableHandle<mirror::Object>>* handles)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
-
-  hsp->reset(new StackHandleScope<kMaxHandles>(self));
-  // Class java.lang.Object.
-  Handle<mirror::Class> c((*hsp)->NewHandle(class_linker->FindSystemClass(self,
-                                                                       "Ljava/lang/Object;")));
-  // Array helps to fill memory faster.
-  Handle<mirror::Class> ca((*hsp)->NewHandle(class_linker->FindSystemClass(self,
-                                                                        "[Ljava/lang/Object;")));
-
-  // Start allocating with 128K
-  size_t length = 128 * KB / 4;
-  while (length > 10) {
-    MutableHandle<mirror::Object> h((*hsp)->NewHandle<mirror::Object>(
-        mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), length / 4)));
-    if (self->IsExceptionPending() || h == nullptr) {
-      self->ClearException();
-
-      // Try a smaller length
-      length = length / 8;
-      // Use at most half the reported free space.
-      size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
-      if (length * 8 > mem) {
-        length = mem / 8;
-      }
-    } else {
-      handles->push_back(h);
-    }
-  }
-
-  // Allocate simple objects till it fails.
-  while (!self->IsExceptionPending()) {
-    MutableHandle<mirror::Object> h = (*hsp)->NewHandle<mirror::Object>(c->AllocObject(self));
-    if (!self->IsExceptionPending() && h != nullptr) {
-      handles->push_back(h);
-    }
-  }
-  self->ClearException();
-}
-
 // Check that an exception can be thrown correctly.
 // This test is potentially racy, but the timeout is long enough that it should work.
 
@@ -304,16 +255,12 @@
   test->complete_barrier_ = std::unique_ptr<Barrier>(new Barrier(3));
   test->completed_ = false;
 
-  // Fill the heap.
-  std::unique_ptr<StackHandleScope<kMaxHandles>> hsp;
-  std::vector<MutableHandle<mirror::Object>> handles;
-
   // Our job: Fill the heap, then try Wait.
-  FillHeap(soa.Self(), class_linker, &hsp, &handles);
+  {
+    VariableSizedHandleScope vhs(soa.Self());
+    test->FillHeap(soa.Self(), class_linker, &vhs);
 
-  // Now release everything.
-  for (MutableHandle<mirror::Object>& h : handles) {
-    h.Assign(nullptr);
+    // Now release everything.
   }
 
   // Need to drop the mutator lock to allow barriers.
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 740b7dd..883de38 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -711,6 +711,29 @@
       DCHECK(c1 != nullptr && !c1->IsPrimitive());
       DCHECK(c2 != nullptr && !c2->IsPrimitive());
       mirror::Class* join_class = ClassJoin(c1, c2);
+      if (UNLIKELY(join_class == nullptr)) {
+        // Internal error joining the classes (e.g., OOME). Report an unresolved reference type.
+        // We cannot report an unresolved merge type, as that will attempt to merge the resolved
+        // components, leaving us in an infinite loop.
+        // We do not want to report the originating exception, as that would require a fast path
+        // out all the way to VerifyClass. Instead attempt to continue on without a detailed type.
+        Thread* self = Thread::Current();
+        self->AssertPendingException();
+        self->ClearException();
+
+        // When compiling on the host, we rather want to abort to ensure determinism for preopting.
+        // (In that case, it is likely a misconfiguration of dex2oat.)
+        if (!kIsTargetBuild && Runtime::Current()->IsAotCompiler()) {
+          LOG(FATAL) << "Could not create class join of "
+                     << c1->PrettyClass()
+                     << " & "
+                     << c2->PrettyClass();
+          UNREACHABLE();
+        }
+
+        return reg_types->MakeUnresolvedReference();
+      }
+
       // Record the dependency that both `c1` and `c2` are assignable to `join_class`.
       // The `verifier` is null during unit tests.
       if (verifier != nullptr) {
@@ -753,10 +776,18 @@
       DCHECK(result->IsObjectClass());
       return result;
     }
+    Thread* self = Thread::Current();
     ObjPtr<mirror::Class> common_elem = ClassJoin(s_ct, t_ct);
+    if (UNLIKELY(common_elem == nullptr)) {
+      self->AssertPendingException();
+      return nullptr;
+    }
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-    mirror::Class* array_class = class_linker->FindArrayClass(Thread::Current(), &common_elem);
-    DCHECK(array_class != nullptr);
+    mirror::Class* array_class = class_linker->FindArrayClass(self, &common_elem);
+    if (UNLIKELY(array_class == nullptr)) {
+      self->AssertPendingException();
+      return nullptr;
+    }
     return array_class;
   } else {
     size_t s_depth = s->Depth();
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index 6c01a79..c5d8ff5 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -355,6 +355,10 @@
    * the perversion of Object being assignable to an interface type (note, however, that we don't
    * allow assignment of Object or Interface to any concrete class and are therefore type safe).
    *
+   * Note: This may return null in case of internal errors, e.g., OOME when a new class would have
+   *       to be created but there is no heap space. The exception will stay pending, and it is
+   *       the job of the caller to handle it.
+   *
    * [1] Java bytecode verification: algorithms and formalizations, Xavier Leroy
    */
   static mirror::Class* ClassJoin(mirror::Class* s, mirror::Class* t)
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 93286ea..0c00868 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -222,6 +222,11 @@
   }
 }
 
+const RegType& RegTypeCache::MakeUnresolvedReference() {
+  // The descriptor is intentionally invalid so nothing else will match this type.
+  return AddEntry(new (&arena_) UnresolvedReferenceType(AddString("a"), entries_.size()));
+}
+
 const RegType* RegTypeCache::FindClass(mirror::Class* klass, bool precise) const {
   DCHECK(klass != nullptr);
   if (klass->IsPrimitive()) {
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index 37f8a1f..c9bf6a9 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -97,6 +97,10 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   const RegType& FromUnresolvedSuperClass(const RegType& child)
       REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Note: this should not be used outside of RegType::ClassJoin!
+  const RegType& MakeUnresolvedReference() REQUIRES_SHARED(Locks::mutator_lock_);
+
   const ConstantType& Zero() REQUIRES_SHARED(Locks::mutator_lock_) {
     return FromCat1Const(0, true);
   }
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index b0ea6c8..1aa0966 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -22,6 +22,7 @@
 #include "base/casts.h"
 #include "base/scoped_arena_allocator.h"
 #include "common_runtime_test.h"
+#include "compiler_callbacks.h"
 #include "reg_type_cache-inl.h"
 #include "reg_type-inl.h"
 #include "scoped_thread_state_change-inl.h"
@@ -677,5 +678,59 @@
   EXPECT_FALSE(imprecise_const.Equals(precise_const));
 }
 
+class RegTypeOOMTest : public RegTypeTest {
+ protected:
+  void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
+    SetUpRuntimeOptionsForFillHeap(options);
+
+    // We must not appear to be a compiler, or we'll abort on the host.
+    callbacks_.reset();
+  }
+};
+
+TEST_F(RegTypeOOMTest, ClassJoinOOM) {
+  // TODO: Figure out why FillHeap isn't good enough under CMS.
+  TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS();
+
+  // Tests that we don't abort with OOMs.
+
+  ArenaStack stack(Runtime::Current()->GetArenaPool());
+  ScopedArenaAllocator allocator(&stack);
+  ScopedObjectAccess soa(Thread::Current());
+
+  // We cannot allow moving GC. Otherwise we'd have to ensure the reg types are updated (reference
+  // reg types store a class pointer in a GCRoot, which is normally updated through active verifiers
+  // being registered with their thread), which is unnecessarily complex.
+  Runtime::Current()->GetHeap()->IncrementDisableMovingGC(soa.Self());
+
+  // We merge nested array of primitive wrappers. These have a join type of an array of Number of
+  // the same depth. We start with depth five, as we want at least two newly created classes to
+  // test recursion (it's just more likely that nobody uses such deep arrays in runtime bringup).
+  constexpr const char* kIntArrayFive = "[[[[[Ljava/lang/Integer;";
+  constexpr const char* kFloatArrayFive = "[[[[[Ljava/lang/Float;";
+  constexpr const char* kNumberArrayFour = "[[[[Ljava/lang/Number;";
+  constexpr const char* kNumberArrayFive = "[[[[[Ljava/lang/Number;";
+
+  RegTypeCache cache(true, allocator);
+  const RegType& int_array_array = cache.From(nullptr, kIntArrayFive, false);
+  ASSERT_TRUE(int_array_array.HasClass());
+  const RegType& float_array_array = cache.From(nullptr, kFloatArrayFive, false);
+  ASSERT_TRUE(float_array_array.HasClass());
+
+  // Check assumptions: the joined classes don't exist, yet.
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFour, nullptr) == nullptr);
+  ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFive, nullptr) == nullptr);
+
+  // Fill the heap.
+  VariableSizedHandleScope hs(soa.Self());
+  FillHeap(soa.Self(), class_linker, &hs);
+
+  const RegType& join_type = int_array_array.Merge(float_array_array, &cache, nullptr);
+  ASSERT_TRUE(join_type.IsUnresolvedReference());
+
+  Runtime::Current()->GetHeap()->DecrementDisableMovingGC(soa.Self());
+}
+
 }  // namespace verifier
 }  // namespace art
diff --git a/test/066-mismatched-super/expected.txt b/test/066-mismatched-super/expected.txt
index 09c0596..f5b15ca 100644
--- a/test/066-mismatched-super/expected.txt
+++ b/test/066-mismatched-super/expected.txt
@@ -1 +1,2 @@
 Got expected ICCE
+Got expected VerifyError
diff --git a/test/066-mismatched-super/info.txt b/test/066-mismatched-super/info.txt
index 7865ffc..2b70e06 100644
--- a/test/066-mismatched-super/info.txt
+++ b/test/066-mismatched-super/info.txt
@@ -1,2 +1,5 @@
-This tests what happens when class A extends abstract class B, but somebody
-turns B into an interface without rebuilding A.
+This tests two cases:
+1. What happens when class A extends abstract class B, but somebody
+   turns B into an interface without rebuilding A.
+2. What happens when class A extends a class B, but somebody
+   turns B into a final class without rebuilding A.
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src/ExtendsFinal.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/066-mismatched-super/src/ExtendsFinal.java
index 023e409..2f53b3b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src/ExtendsFinal.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,5 @@
  * limitations under the License.
  */
 
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
-    public static void main() {
-        Base base = new Base();
-    }
+public class ExtendsFinal extends Final {
 }
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src/Final.java
similarity index 65%
rename from test/066-mismatched-super/src/Indirect.java
rename to test/066-mismatched-super/src/Final.java
index 023e409..a44d096 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src/Final.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,5 @@
  * limitations under the License.
  */
 
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
-    public static void main() {
-        Base base = new Base();
-    }
+public /* final */ class Final {
 }
diff --git a/test/066-mismatched-super/src/Main.java b/test/066-mismatched-super/src/Main.java
index 55d0bab..6ae1198 100644
--- a/test/066-mismatched-super/src/Main.java
+++ b/test/066-mismatched-super/src/Main.java
@@ -20,10 +20,16 @@
 public class Main {
     public static void main(String[] args) {
         try {
-            Indirect.main();
+            Base base = new Base();
             System.out.println("Succeeded unexpectedly");
         } catch (IncompatibleClassChangeError icce) {
             System.out.println("Got expected ICCE");
         }
+        try {
+            ExtendsFinal ef = new ExtendsFinal();
+            System.out.println("Succeeded unexpectedly");
+        } catch (VerifyError ve) {
+            System.out.println("Got expected VerifyError");
+        }
     }
 }
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src2/Final.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/066-mismatched-super/src2/Final.java
index 023e409..766da9b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src2/Final.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,5 @@
  * limitations under the License.
  */
 
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
-    public static void main() {
-        Base base = new Base();
-    }
+public final class Final {
 }
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index baf7600..e8b6f1c 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -303,7 +303,8 @@
         }
     },
     'art-gtest-valgrind32': {
-        'make' : 'valgrind-test-art-host32',
+      # Disabled: x86 valgrind does not understand SSE4.x
+      # 'make' : 'valgrind-test-art-host32',
         'env': {
             'ART_USE_READ_BARRIER' : 'false'
         }
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index cc55b7a..8262910 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -110,7 +110,7 @@
   private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
     doc.section("Fields");
     AhatInstance base = inst.getBaseline();
-    printFields(doc, query, INSTANCE_FIELDS_ID, !base.isPlaceHolder(),
+    printFields(doc, query, INSTANCE_FIELDS_ID, inst != base && !base.isPlaceHolder(),
         inst.asClassInstance().getInstanceFields(),
         base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
   }
@@ -211,7 +211,7 @@
 
     doc.section("Static Fields");
     AhatInstance base = clsobj.getBaseline();
-    printFields(doc, query, STATIC_FIELDS_ID, !base.isPlaceHolder(),
+    printFields(doc, query, STATIC_FIELDS_ID, clsobj != base && !base.isPlaceHolder(),
         clsobj.getStaticFieldValues(),
         base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
   }