Merge "Delete logspam."
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index 03e68ae..1880726 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -73,8 +73,21 @@
 HOST_CORE_IMG_LOCATION := $(HOST_OUT_JAVA_LIBRARIES)/core.art
 TARGET_CORE_IMG_LOCATION := $(ART_TARGET_TEST_OUT)/core.art
 
-# Jar files for core.art.
-TEST_CORE_JARS := core-oj core-libart core-simple conscrypt okhttp bouncycastle
+# Modules to compile for core.art.
+CORE_IMG_JARS := core-oj core-libart core-simple okhttp bouncycastle
+HOST_CORE_IMG_JARS   := $(addsuffix -hostdex,$(CORE_IMG_JARS))
+TARGET_CORE_IMG_JARS := $(addsuffix -testdex,$(CORE_IMG_JARS))
+HOST_CORE_IMG_DEX_LOCATIONS   := $(foreach jar,$(HOST_CORE_IMG_JARS),  $(HOST_OUT_JAVA_LIBRARIES)/$(jar).jar)
+ifeq ($(ART_TEST_ANDROID_ROOT),)
+TARGET_CORE_IMG_DEX_LOCATIONS := $(foreach jar,$(TARGET_CORE_IMG_JARS),/$(DEXPREOPT_BOOT_JAR_DIR)/$(jar).jar)
+else
+TARGET_CORE_IMG_DEX_LOCATIONS := $(foreach jar,$(TARGET_CORE_IMG_JARS),$(ART_TEST_ANDROID_ROOT)/$(jar).jar)
+endif
+HOST_CORE_IMG_DEX_FILES   := $(foreach jar,$(HOST_CORE_IMG_JARS),  $(call intermediates-dir-for,JAVA_LIBRARIES,$(jar),t,COMMON)/javalib.jar)
+TARGET_CORE_IMG_DEX_FILES := $(foreach jar,$(TARGET_CORE_IMG_JARS),$(call intermediates-dir-for,JAVA_LIBRARIES,$(jar), ,COMMON)/javalib.jar)
+
+# Jar files for the boot class path for testing. Must start with CORE_IMG_JARS.
+TEST_CORE_JARS := $(CORE_IMG_JARS) conscrypt
 HOST_TEST_CORE_JARS   := $(addsuffix -hostdex,$(TEST_CORE_JARS))
 TARGET_TEST_CORE_JARS := $(addsuffix -testdex,$(TEST_CORE_JARS))
 HOST_CORE_DEX_LOCATIONS   := $(foreach jar,$(HOST_TEST_CORE_JARS),  $(HOST_OUT_JAVA_LIBRARIES)/$(jar).jar)
@@ -83,7 +96,6 @@
 else
 TARGET_CORE_DEX_LOCATIONS := $(foreach jar,$(TARGET_TEST_CORE_JARS),$(ART_TEST_ANDROID_ROOT)/framework/$(jar).jar)
 endif
-
 HOST_CORE_DEX_FILES   := $(foreach jar,$(HOST_TEST_CORE_JARS),  $(call intermediates-dir-for,JAVA_LIBRARIES,$(jar),t,COMMON)/javalib.jar)
 TARGET_CORE_DEX_FILES := $(foreach jar,$(TARGET_TEST_CORE_JARS),$(call intermediates-dir-for,JAVA_LIBRARIES,$(jar), ,COMMON)/javalib.jar)
 
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 6885946..11ebc2f 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -515,7 +515,8 @@
     $$($(3)ART_HOST_OUT_SHARED_LIBRARIES)/libopenjdkd$$(ART_HOST_SHLIB_EXTENSION) \
     $$(gtest_exe) \
     $$(ART_GTEST_$(1)_HOST_DEPS) \
-    $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_HOST_GTEST_$(file)_DEX))
+    $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_HOST_GTEST_$(file)_DEX)) \
+    $(HOST_OUT_EXECUTABLES)/timeout_dumper
 
   ART_TEST_HOST_GTEST_DEPENDENCIES += $$(gtest_deps)
 
@@ -528,7 +529,9 @@
 $$(gtest_output): NAME := $$(gtest_rule)
 ifeq (,$(SANITIZE_HOST))
 $$(gtest_output): $$(gtest_exe) $$(gtest_deps)
-	$(hide) ($$(call ART_TEST_SKIP,$$(NAME)) && $$< --gtest_output=xml:$$@ && \
+	$(hide) ($$(call ART_TEST_SKIP,$$(NAME)) && \
+		timeout -k 120s -s SIGRTMIN+2 2400s $(HOST_OUT_EXECUTABLES)/timeout_dumper \
+			$$< --gtest_output=xml:$$@ && \
 		$$(call ART_TEST_PASSED,$$(NAME))) || $$(call ART_TEST_FAILED,$$(NAME))
 else
 # Note: envsetup currently exports ASAN_OPTIONS=detect_leaks=0 to suppress leak detection, as some
@@ -540,7 +543,9 @@
 # under ASAN.
 $$(gtest_output): $$(gtest_exe) $$(gtest_deps)
 	$(hide) ($$(call ART_TEST_SKIP,$$(NAME)) && set -o pipefail && \
-		ASAN_OPTIONS=detect_leaks=1 $$< --gtest_output=xml:$$@ 2>&1 | tee $$<.tmp.out >&2 && \
+		ASAN_OPTIONS=detect_leaks=1 timeout -k 120s -s SIGRTMIN+2 3600s \
+			$(HOST_OUT_EXECUTABLES)/timeout_dumper \
+				$$< --gtest_output=xml:$$@ 2>&1 | tee $$<.tmp.out >&2 && \
 		{ $$(call ART_TEST_PASSED,$$(NAME)) ; rm $$<.tmp.out ; }) || \
 		( grep -q AddressSanitizer $$<.tmp.out && export ANDROID_BUILD_TOP=`pwd` && \
 			{ echo "ABI: 'x86_64'" | cat - $$<.tmp.out | development/scripts/stack | tail -n 3000 ; } ; \
diff --git a/build/Android.oat.mk b/build/Android.oat.mk
index e2adac1..2ad1143 100644
--- a/build/Android.oat.mk
+++ b/build/Android.oat.mk
@@ -39,8 +39,6 @@
 # Use dex2oat debug version for better error reporting
 # $(1): compiler - optimizing, interpreter or interp-ac (interpreter-access-checks).
 # $(2): 2ND_ or undefined, 2ND_ for 32-bit host builds.
-# NB depending on HOST_CORE_DEX_LOCATIONS so we are sure to have the dex files in frameworks for
-# run-test --no-image
 define create-core-oat-host-rules
   core_compile_options :=
   core_image_name :=
@@ -80,13 +78,15 @@
 $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
 $$(core_image_name): PRIVATE_CORE_IMG_NAME := $$(core_image_name)
 $$(core_image_name): PRIVATE_CORE_OAT_NAME := $$(core_oat_name)
-$$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency)
+$$(core_image_name): $$(HOST_CORE_IMG_DEX_LOCATIONS) $$(core_dex2oat_dependency)
 	@echo "host dex2oat: $$@"
 	@mkdir -p $$(dir $$@)
 	$$(hide) ANDROID_LOG_TAGS="*:e" $$(DEX2OAT) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
 	  --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \
-	  --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(HOST_CORE_DEX_FILES)) \
-	  $$(addprefix --dex-location=,$$(HOST_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
+	  --image-classes=$$(PRELOADED_CLASSES) \
+	  $$(addprefix --dex-file=,$$(HOST_CORE_IMG_DEX_FILES)) \
+	  $$(addprefix --dex-location=,$$(HOST_CORE_IMG_DEX_LOCATIONS)) \
+	  --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
 	  --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
 	  --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(2)ART_HOST_ARCH) \
 	  $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
@@ -169,13 +169,15 @@
 $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
 $$(core_image_name): PRIVATE_CORE_IMG_NAME := $$(core_image_name)
 $$(core_image_name): PRIVATE_CORE_OAT_NAME := $$(core_oat_name)
-$$(core_image_name): $$(TARGET_CORE_DEX_FILES) $$(core_dex2oat_dependency)
+$$(core_image_name): $$(TARGET_CORE_IMG_DEX_FILES) $$(core_dex2oat_dependency)
 	@echo "target dex2oat: $$@"
 	@mkdir -p $$(dir $$@)
 	$$(hide) $$(DEX2OAT) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
 	  --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \
-	  --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(TARGET_CORE_DEX_FILES)) \
-	  $$(addprefix --dex-location=,$$(TARGET_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
+	  --image-classes=$$(PRELOADED_CLASSES) \
+	  $$(addprefix --dex-file=,$$(TARGET_CORE_IMG_DEX_FILES)) \
+	  $$(addprefix --dex-location=,$$(TARGET_CORE_IMG_DEX_LOCATIONS)) \
+	  --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
 	  --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
 	  --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(2)TARGET_ARCH) \
 	  --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \
diff --git a/build/apex/ld.config.txt b/build/apex/ld.config.txt
index ac4d1eb..fddb17e 100644
--- a/build/apex/ld.config.txt
+++ b/build/apex/ld.config.txt
@@ -1 +1,24 @@
-# TODO: Write me.
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Bionic loader config file for the Runtime APEX.
+#
+# There are no versioned APEX paths here - this APEX module does not support
+# having several versions mounted.
+
+dir.runtime = /apex/com.android.runtime/bin/
+
+[runtime]
+additional.namespaces = platform
+
+# Keep in sync with runtime namespace in /system/etc/ld.config.txt.
+namespace.default.isolated = true
+namespace.default.search.paths = /apex/com.android.runtime/${LIB}
+namespace.default.links = platform
+# TODO(b/119867084): Restrict fallback to platform namespace to PALette library.
+namespace.default.link.platform.allow_all_shared_libs = true
+
+# Keep in sync with default namespace in /system/etc/ld.config.txt.
+namespace.platform.isolated = true
+namespace.platform.search.paths = /system/${LIB}
+namespace.platform.links = default
+namespace.platform.link.default.shared_libs = libc.so:libdl.so:libm.so
diff --git a/build/apex/runtests.sh b/build/apex/runtests.sh
index c19c7bd..924c44b 100755
--- a/build/apex/runtests.sh
+++ b/build/apex/runtests.sh
@@ -33,6 +33,11 @@
 
    sudo apt-get install libguestfs-tools
 "
+which tree > /dev/null || die "This script requires the 'tree' tool.
+On Debian-based systems, this can be installed with:
+
+   sudo apt-get install tree
+"
 [[ -n "$ANDROID_PRODUCT_OUT" ]] \
   || die "You need to source and lunch before you can use this script."
 
@@ -66,8 +71,16 @@
   shift
 done
 
-work_dir=$(mktemp -d)
-mount_point="$work_dir/image"
+
+# build_apex APEX_MODULE
+# ----------------------
+# Build APEX package APEX_MODULE.
+function build_apex {
+  if $build_apex_p; then
+    local apex_module=$1
+    say "Building package $apex_module" && make "$apex_module" || die "Cannot build $apex_module"
+  fi
+}
 
 function check_binary {
   [[ -x "$mount_point/bin/$1" ]] || die "Cannot find binary '$1' in mounted image"
@@ -91,36 +104,22 @@
     || die "Cannot find library '$1' in mounted image"
 }
 
-function build_apex {
-  if $build_apex_p; then
-    say "Building package $1" && make "$1" || die "Cannot build $1"
-  fi
-}
-
-function check_contents {
-
+# Check contents of APEX payload located in `$mount_point`.
+function check_release_contents {
   # Check that the mounted image contains a manifest.
   [[ -f "$mount_point/apex_manifest.json" ]] || die "no manifest"
 
   # Check that the mounted image contains ART base binaries.
   check_multilib_binary dalvikvm
-  # TODO: Does not work yet.
+  # TODO: Does not work yet (b/119942078).
   : check_binary_symlink dalvikvm
   check_binary dex2oat
   check_binary dexoptanalyzer
   check_binary profman
 
-  # Check that the mounted image contains ART tools binaries.
-  check_binary dexdiag
-  check_binary dexdump
-  check_binary dexlist
   # oatdump is only in device apex's due to build rules
-  # check_binary oatdump
-
-  # Check that the mounted image contains ART debug binaries.
-  check_binary dex2oatd
-  check_binary dexoptanalyzerd
-  check_binary profmand
+  # TODO: Check for it when it is also built for host.
+  : check_binary oatdump
 
   # Check that the mounted image contains ART libraries.
   check_library libart-compiler.so
@@ -135,20 +134,6 @@
   check_library libdexfile.so
   check_library libprofile.so
 
-  # Check that the mounted image contains ART debug libraries.
-  check_library libartd-compiler.so
-  check_library libartd.so
-  check_library libopenjdkd.so
-  check_library libopenjdkjvmd.so
-  check_library libopenjdkjvmtid.so
-  check_library libadbconnectiond.so
-  # TODO: Should we check for these libraries too, even if they are not explicitly
-  # listed as dependencies in the Android Runtime APEX module rule?
-  check_library libdexfiled.so
-  check_library libartbased.so
-  check_library libartd-dexlayout.so
-  check_library libprofiled.so
-
   # TODO: Should we check for other libraries, such as:
   #
   #   libbacktrace.so
@@ -164,105 +149,197 @@
   # ?
 }
 
+# Check debug contents of APEX payload located in `$mount_point`.
+function check_debug_contents {
+  # Check that the mounted image contains ART tools binaries.
+  check_binary dexdiag
+  check_binary dexdump
+  check_binary dexlist
 
-# *****************************************
-# * Testing for com.android.runtime.debug *
-# *****************************************
+  # Check that the mounted image contains ART debug binaries.
+  check_binary dex2oatd
+  check_binary dexoptanalyzerd
+  check_binary profmand
 
-# Garbage collection.
-function finish_device_debug {
-  # Don't fail early during cleanup.
-  set +e
+  # Check that the mounted image contains ART debug libraries.
+  check_library libartd-compiler.so
+  check_library libartd.so
+  check_library libopenjdkd.so
+  check_library libopenjdkjvmd.so
+  check_library libopenjdkjvmtid.so
+  check_library libadbconnectiond.so
+  # TODO: Should we check for these libraries too, even if they are not explicitly
+  # listed as dependencies in the Android Runtime APEX module rule?
+  check_library libdexfiled.so
+  check_library libartbased.so
+  check_library libartd-dexlayout.so
+  check_library libprofiled.so
+}
+
+# Testing target (device) APEX packages.
+# ======================================
+
+# Clean-up.
+function cleanup_target {
   guestunmount "$mount_point"
   rm -rf "$work_dir"
 }
 
-trap finish_device_debug EXIT
+# Garbage collection.
+function finish_target {
+  # Don't fail early during cleanup.
+  set +e
+  cleanup_target
+}
 
-# TODO: Also exercise the Release Runtime APEX (`com.android.runtime.release`).
+# setup_target_apex APEX_MODULE MOUNT_POINT
+# -----------------------------------------
+# Extract image from target APEX_MODULE and mount it in MOUNT_POINT.
+function setup_target_apex {
+  local apex_module=$1
+  local mount_point=$2
+  local system_apexdir="$ANDROID_PRODUCT_OUT/system/apex"
+  local apex_package="$system_apexdir/$apex_module.apex"
+
+  say "Extracting and mounting image"
+
+  # Extract the payload from the Android Runtime APEX.
+  local image_filename="apex_payload.img"
+  unzip -q "$apex_package" "$image_filename" -d "$work_dir"
+  mkdir "$mount_point"
+  local image_file="$work_dir/$image_filename"
+
+  # Check filesystems in the image.
+  local image_filesystems="$work_dir/image_filesystems"
+  virt-filesystems -a "$image_file" >"$image_filesystems"
+  # We expect a single partition (/dev/sda) in the image.
+  local partition="/dev/sda"
+  echo "$partition" | cmp "$image_filesystems" -
+
+  # Mount the image from the Android Runtime APEX.
+  guestmount -a "$image_file" -m "$partition" "$mount_point"
+
+  # List the contents of the mounted image (optional).
+  $list_image_files_p \
+    && say "Listing image files" && ls -ld "$mount_point" && tree -ap "$mount_point"
+}
+
+# Testing release APEX package (com.android.runtime.release).
+# -----------------------------------------------------------
+
+apex_module="com.android.runtime.release"
+
+work_dir=$(mktemp -d)
+mount_point="$work_dir/image"
+
+trap finish_target EXIT
+
+# Build the APEX package (optional).
+build_apex "$apex_module"
+
+# Set up APEX package.
+setup_target_apex "$apex_module" "$mount_point"
+
+# Run tests on APEX package.
+say "Checking APEX package $apex_module"
+check_release_contents
+
+# Clean up.
+trap - EXIT
+cleanup_target
+
+say "$apex_module tests passed"
+
+# Testing debug APEX package (com.android.runtime.debug).
+# -------------------------------------------------------
+
 apex_module="com.android.runtime.debug"
 
-# Build the Android Runtime APEX package (optional).
-build_apex $apex_module
+work_dir=$(mktemp -d)
+mount_point="$work_dir/image"
 
-system_apexdir="$ANDROID_PRODUCT_OUT/system/apex"
-apex_package="$system_apexdir/$apex_module.apex"
+trap finish_target EXIT
 
-say "Extracting and mounting image"
+# Build the APEX package (optional).
+build_apex "$apex_module"
 
-# Extract the payload from the Android Runtime APEX.
-image_filename="apex_payload.img"
-unzip -q "$apex_package" "$image_filename" -d "$work_dir"
-mkdir "$mount_point"
-image_file="$work_dir/$image_filename"
+# Set up APEX package.
+setup_target_apex "$apex_module" "$mount_point"
 
-# Check filesystems in the image.
-image_filesystems="$work_dir/image_filesystems"
-virt-filesystems -a "$image_file" >"$image_filesystems"
-# We expect a single partition (/dev/sda) in the image.
-partition="/dev/sda"
-echo "$partition" | cmp "$image_filesystems" -
-
-# Mount the image from the Android Runtime APEX.
-guestmount -a "$image_file" -m "$partition" "$mount_point"
-
-# List the contents of the mounted image (optional).
-$list_image_files_p && say "Listing image files" && ls -ld "$mount_point" && tree -ap "$mount_point"
-
-say "Running tests"
-
-check_contents
-
-# Check for files pulled in from device-only oatdump.
+# Run tests on APEX package.
+say "Checking APEX package $apex_module"
+check_release_contents
+check_debug_contents
+# Check for files pulled in from debug target-only oatdump.
 check_binary oatdump
 check_library libart-disassembler.so
 
-# Cleanup
+# Clean up.
 trap - EXIT
-guestunmount "$mount_point"
-rm -rf "$work_dir"
+cleanup_target
 
-say "$apex_module Tests passed"
+say "$apex_module tests passed"
 
-# ****************************************
-# * Testing for com.android.runtime.host *
-# ****************************************
+
+# Testing host APEX package (com.android.runtime.host).
+# =====================================================
+
+# Clean-up.
+function cleanup_host {
+  rm -rf "$work_dir"
+}
 
 # Garbage collection.
 function finish_host {
   # Don't fail early during cleanup.
   set +e
-  rm -rf "$work_dir"
+  cleanup_host
 }
 
+# setup_host_apex APEX_MODULE MOUNT_POINT
+# ---------------------------------------
+# Extract Zip file from host APEX_MODULE and extract it in MOUNT_POINT.
+function setup_host_apex {
+  local apex_module=$1
+  local mount_point=$2
+  local system_apexdir="$ANDROID_HOST_OUT/apex"
+  local apex_package="$system_apexdir/$apex_module.zipapex"
+
+  say "Extracting payload"
+
+  # Extract the payload from the Android Runtime APEX.
+  local image_filename="apex_payload.zip"
+  unzip -q "$apex_package" "$image_filename" -d "$work_dir"
+  mkdir "$mount_point"
+  local image_file="$work_dir/$image_filename"
+
+  # Unzipping the payload
+  unzip -q "$image_file" -d "$mount_point"
+}
+
+apex_module="com.android.runtime.host"
+
 work_dir=$(mktemp -d)
 mount_point="$work_dir/zip"
 
 trap finish_host EXIT
 
-apex_module="com.android.runtime.host"
+# Build the APEX package (optional).
+build_apex "$apex_module"
 
-# Build the Android Runtime APEX package (optional).
-build_apex $apex_module
+# Set up APEX package.
+setup_host_apex "$apex_module" "$mount_point"
 
-system_apexdir="$ANDROID_HOST_OUT/apex"
-apex_package="$system_apexdir/$apex_module.zipapex"
+# Run tests on APEX package.
+say "Checking APEX package $apex_module"
+check_release_contents
+check_debug_contents
 
-say "Extracting payload"
+# Clean up.
+trap - EXIT
+cleanup_host
 
-# Extract the payload from the Android Runtime APEX.
-image_filename="apex_payload.zip"
-unzip -q "$apex_package" "$image_filename" -d "$work_dir"
-mkdir "$mount_point"
-image_file="$work_dir/$image_filename"
+say "$apex_module tests passed"
 
-# Unzipping the payload
-unzip -q "$image_file" -d "$mount_point"
 
-say "Running tests"
-
-check_contents
-
-say "$apex_module Tests passed"
-
-say "Tests passed"
+say "All Android Runtime APEX tests passed"
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 9b8bb3e..e57bbfa 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -126,11 +126,11 @@
 }
 
 extern "C" bool jit_compile_method(
-    void* handle, ArtMethod* method, Thread* self, bool osr)
+    void* handle, ArtMethod* method, Thread* self, bool baseline, bool osr)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   auto* jit_compiler = reinterpret_cast<JitCompiler*>(handle);
   DCHECK(jit_compiler != nullptr);
-  return jit_compiler->CompileMethod(self, method, osr);
+  return jit_compiler->CompileMethod(self, method, baseline, osr);
 }
 
 extern "C" void jit_types_loaded(void* handle, mirror::Class** types, size_t count)
@@ -181,7 +181,7 @@
   }
 }
 
-bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method, bool osr) {
+bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method, bool baseline, bool osr) {
   SCOPED_TRACE << "JIT compiling " << method->PrettyMethod();
 
   DCHECK(!method->IsProxyMethod());
@@ -198,7 +198,7 @@
     TimingLogger::ScopedTiming t2("Compiling", &logger);
     JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache();
     success = compiler_driver_->GetCompiler()->JitCompile(
-        self, code_cache, method, /* baseline= */ false, osr, jit_logger_.get());
+        self, code_cache, method, baseline, osr, jit_logger_.get());
   }
 
   // Trim maps to reduce memory usage.
diff --git a/compiler/jit/jit_compiler.h b/compiler/jit/jit_compiler.h
index d201611..29d2761 100644
--- a/compiler/jit/jit_compiler.h
+++ b/compiler/jit/jit_compiler.h
@@ -37,7 +37,7 @@
   virtual ~JitCompiler();
 
   // Compilation entrypoint. Returns whether the compilation succeeded.
-  bool CompileMethod(Thread* self, ArtMethod* method, bool osr)
+  bool CompileMethod(Thread* self, ArtMethod* method, bool baseline, bool osr)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   const CompilerOptions& GetCompilerOptions() const {
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 92b9543..bd4304c 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -1300,15 +1300,15 @@
   EXPECT_TRUE(env->IsInstanceOf(JniCompilerTest::jobj_, klass));
   EXPECT_TRUE(env->IsSameObject(JniCompilerTest::jobj_, obj1));
   EXPECT_TRUE(env->IsSameObject(JniCompilerTest::jobj_, obj2));
-  EXPECT_EQ(0x12345678ABCDEF88ll, val1);
-  EXPECT_EQ(0x7FEDCBA987654321ll, val2);
+  EXPECT_EQ(0x12345678ABCDEF88LL, val1);
+  EXPECT_EQ(0x7FEDCBA987654321LL, val2);
   return 42;
 }
 
 void JniCompilerTest::GetTextImpl() {
   SetUpForTest(true, "getText", "(JLjava/lang/Object;JLjava/lang/Object;)I",
                CURRENT_JNI_WRAPPER(my_gettext));
-  jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 0x12345678ABCDEF88ll, jobj_,
+  jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 0x12345678ABCDEF88LL, jobj_,
                                           INT64_C(0x7FEDCBA987654321), jobj_);
   EXPECT_EQ(result, 42);
 }
diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h
index 5bd11226..50b13c8 100644
--- a/compiler/optimizing/intrinsics.h
+++ b/compiler/optimizing/intrinsics.h
@@ -243,7 +243,8 @@
 // compilation.
 #define UNREACHABLE_INTRINSIC(Arch, Name)                                \
 void IntrinsicLocationsBuilder ## Arch::Visit ## Name(HInvoke* invoke) { \
-  if (!codegen_->GetCompilerOptions().IsBaseline()) {                    \
+  if (Runtime::Current()->IsAotCompiler() &&                             \
+      !codegen_->GetCompilerOptions().IsBaseline()) {                    \
     LOG(FATAL) << "Unreachable: intrinsic " << invoke->GetIntrinsic()    \
                << " should have been converted to HIR";                  \
   }                                                                      \
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index a5bba9b..872fab3 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -624,7 +624,6 @@
   explicit Dex2Oat(TimingLogger* timings) :
       compiler_kind_(Compiler::kOptimizing),
       // Take the default set of instruction features from the build.
-      boot_image_checksum_(0),
       key_value_store_(nullptr),
       verification_results_(nullptr),
       runtime_(nullptr),
@@ -1437,17 +1436,22 @@
 
     if (!IsBootImage()) {
       // When compiling an app, create the runtime early to retrieve
-      // the image location key needed for the oat header.
+      // the boot image checksums needed for the oat header.
       if (!CreateRuntime(std::move(runtime_options))) {
         return dex2oat::ReturnCode::kCreateRuntime;
       }
 
       if (CompilerFilter::DependsOnImageChecksum(compiler_options_->GetCompilerFilter())) {
         TimingLogger::ScopedTiming t3("Loading image checksum", timings_);
-        std::vector<ImageSpace*> image_spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
-        boot_image_checksum_ = image_spaces[0]->GetImageHeader().GetImageChecksum();
-      } else {
-        boot_image_checksum_ = 0u;
+        Runtime* runtime = Runtime::Current();
+        key_value_store_->Put(OatHeader::kBootClassPathKey,
+                              android::base::Join(runtime->GetBootClassPathLocations(), ':'));
+        std::vector<ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces();
+        const std::vector<const DexFile*>& bcp_dex_files =
+            runtime->GetClassLinker()->GetBootClassPath();
+        key_value_store_->Put(
+            OatHeader::kBootClassPathChecksumsKey,
+            gc::space::ImageSpace::GetBootClassPathChecksums(image_spaces, bcp_dex_files));
       }
 
       // Open dex files for class path.
@@ -2015,7 +2019,7 @@
           elf_writer->EndDataBimgRelRo(data_bimg_rel_ro);
         }
 
-        if (!oat_writer->WriteHeader(elf_writer->GetStream(), boot_image_checksum_)) {
+        if (!oat_writer->WriteHeader(elf_writer->GetStream())) {
           LOG(ERROR) << "Failed to write oat header to the ELF file " << oat_file->GetPath();
           return false;
         }
@@ -2646,7 +2650,6 @@
   std::unique_ptr<CompilerOptions> compiler_options_;
   Compiler::Kind compiler_kind_;
 
-  uint32_t boot_image_checksum_;
   std::unique_ptr<SafeMap<std::string, std::string> > key_value_store_;
 
   std::unique_ptr<VerificationResults> verification_results_;
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index bd8cf5a..fa0a3d4 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -326,8 +326,7 @@
           elf_writer->EndDataBimgRelRo(data_bimg_rel_ro);
         }
 
-        bool header_ok = oat_writer->WriteHeader(elf_writer->GetStream(),
-                                                 /*boot_image_checksum=*/ 0u);
+        bool header_ok = oat_writer->WriteHeader(elf_writer->GetStream());
         ASSERT_TRUE(header_ok);
 
         writer->UpdateOatFileHeader(i, oat_writer->GetOatHeader());
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 61d105f..e4e4b13 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -32,6 +32,7 @@
 #include "base/enums.h"
 #include "base/globals.h"
 #include "base/logging.h"  // For VLOG.
+#include "base/stl_util.h"
 #include "base/unix_file/fd_file.h"
 #include "class_linker-inl.h"
 #include "class_root.h"
@@ -153,6 +154,26 @@
       : nullptr;
 }
 
+bool ImageWriter::IsImageObject(ObjPtr<mirror::Object> obj) const {
+  // For boot image, we keep all objects remaining after the GC in PrepareImageAddressSpace().
+  if (compiler_options_.IsBootImage()) {
+    return true;
+  }
+  // Objects already in the boot image do not belong to the image being written.
+  if (IsInBootImage(obj.Ptr())) {
+    return false;
+  }
+  // DexCaches for the boot class path components that are not a part of the boot image
+  // cannot be garbage collected in PrepareImageAddressSpace() but we do not want to
+  // include them in the app image. So make sure we include only the app DexCaches.
+  if (obj->IsDexCache() &&
+      !ContainsElement(compiler_options_.GetDexFilesForOatFile(),
+                       obj->AsDexCache()->GetDexFile())) {
+    return false;
+  }
+  return true;
+}
+
 // Return true if an object is already in an image space.
 bool ImageWriter::IsInBootImage(const void* obj) const {
   gc::Heap* const heap = Runtime::Current()->GetHeap();
@@ -437,7 +458,7 @@
    */
   heap->VisitObjects([this, &visitor](ObjPtr<mirror::Object> object)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (!IsInBootImage(object.Ptr())) {
+    if (IsImageObject(object)) {
       visitor.SetObject(object);
 
       if (object->IsDexCache()) {
@@ -680,7 +701,7 @@
     ObjPtr<mirror::ClassLoader> class_loader = GetAppClassLoader();
     std::vector<ObjPtr<mirror::DexCache>> dex_caches = FindDexCaches(self);
     for (ObjPtr<mirror::DexCache> dex_cache : dex_caches) {
-      if (IsInBootImage(dex_cache.Ptr())) {
+      if (!IsImageObject(dex_cache)) {
         continue;  // Boot image DexCache is not written to the app image.
       }
       PreloadDexCache(dex_cache, class_loader);
@@ -989,7 +1010,7 @@
   for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) {
     ObjPtr<mirror::DexCache> dex_cache =
         ObjPtr<mirror::DexCache>::DownCast(self->DecodeJObject(data.weak_root));
-    if (dex_cache == nullptr || IsInBootImage(dex_cache.Ptr())) {
+    if (dex_cache == nullptr || !IsImageObject(dex_cache)) {
       continue;
     }
     const DexFile* dex_file = dex_cache->GetDexFile();
@@ -1758,7 +1779,8 @@
   for (ObjPtr<mirror::DexCache> dex_cache : dex_caches) {
     // Pass the class loader associated with the DexCache. This can either be
     // the app's `class_loader` or `nullptr` if boot class loader.
-    PruneDexCache(dex_cache, IsInBootImage(dex_cache.Ptr()) ? nullptr : GetAppClassLoader());
+    bool is_app_image_dex_cache = compiler_options_.IsAppImage() && IsImageObject(dex_cache);
+    PruneDexCache(dex_cache, is_app_image_dex_cache ? GetAppClassLoader() : nullptr);
   }
 
   // Drop the array class cache in the ClassLinker, as these are roots holding those classes live.
@@ -1856,7 +1878,7 @@
         continue;
       }
       const DexFile* dex_file = dex_cache->GetDexFile();
-      if (!IsInBootImage(dex_cache.Ptr())) {
+      if (IsImageObject(dex_cache)) {
         dex_cache_count += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u;
       }
     }
@@ -1875,7 +1897,7 @@
         continue;
       }
       const DexFile* dex_file = dex_cache->GetDexFile();
-      if (!IsInBootImage(dex_cache.Ptr())) {
+      if (IsImageObject(dex_cache)) {
         non_image_dex_caches += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u;
       }
     }
@@ -1889,7 +1911,7 @@
         continue;
       }
       const DexFile* dex_file = dex_cache->GetDexFile();
-      if (!IsInBootImage(dex_cache.Ptr()) &&
+      if (IsImageObject(dex_cache) &&
           image_dex_files.find(dex_file) != image_dex_files.end()) {
         dex_caches->Set<false>(i, dex_cache.Ptr());
         ++i;
@@ -1942,7 +1964,7 @@
 mirror::Object* ImageWriter::TryAssignBinSlot(WorkStack& work_stack,
                                               mirror::Object* obj,
                                               size_t oat_index) {
-  if (obj == nullptr || IsInBootImage(obj)) {
+  if (obj == nullptr || !IsImageObject(obj)) {
     // Object is null or already in the image, there is no work to do.
     return obj;
   }
@@ -2373,7 +2395,7 @@
   {
     auto ensure_bin_slots_assigned = [&](mirror::Object* obj)
         REQUIRES_SHARED(Locks::mutator_lock_) {
-      if (!Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(obj)) {
+      if (IsImageObject(obj)) {
         CHECK(IsImageBinSlotAssigned(obj)) << mirror::Object::PrettyTypeOf(obj) << " " << obj;
       }
     };
@@ -2444,7 +2466,7 @@
   {
     auto unbin_objects_into_offset = [&](mirror::Object* obj)
         REQUIRES_SHARED(Locks::mutator_lock_) {
-      if (!IsInBootImage(obj)) {
+      if (IsImageObject(obj)) {
         UnbinObjectsIntoOffset(obj);
       }
     };
@@ -2909,7 +2931,7 @@
 }
 
 void ImageWriter::CopyAndFixupObject(Object* obj) {
-  if (IsInBootImage(obj)) {
+  if (!IsImageObject(obj)) {
     return;
   }
   size_t offset = GetImageOffset(obj);
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 33bacf8..b680265 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -674,7 +674,12 @@
   template <typename T>
   T* NativeCopyLocation(T* obj) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Return true of obj is inside of the boot image space. This may only return true if we are
+  // Return true if `obj` belongs to the image we're writing.
+  // For a boot image, this is true for all objects.
+  // For an app image, boot image objects and boot class path dex caches are excluded.
+  bool IsImageObject(ObjPtr<mirror::Object> obj) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Return true if `obj` is inside of the boot image space. This may only return true if we are
   // compiling an app image.
   bool IsInBootImage(const void* obj) const;
 
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index d045698..e2a9ac2 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -2808,11 +2808,9 @@
   return true;
 }
 
-bool OatWriter::WriteHeader(OutputStream* out, uint32_t boot_image_checksum) {
+bool OatWriter::WriteHeader(OutputStream* out) {
   CHECK(write_state_ == WriteState::kWriteHeader);
 
-  oat_header_->SetBootImageChecksum(boot_image_checksum);
-
   // Update checksum with header data.
   DCHECK_EQ(oat_header_->GetChecksum(), 0u);  // For checksum calculation.
   const uint8_t* header_begin = reinterpret_cast<const uint8_t*>(oat_header_.get());
diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h
index 9cd2fd0..cc0e83a 100644
--- a/dex2oat/linker/oat_writer.h
+++ b/dex2oat/linker/oat_writer.h
@@ -198,7 +198,7 @@
   // Check the size of the written oat file.
   bool CheckOatSize(OutputStream* out, size_t file_offset, size_t relative_offset);
   // Write the oat header. This finalizes the oat file.
-  bool WriteHeader(OutputStream* out, uint32_t boot_image_checksum);
+  bool WriteHeader(OutputStream* out);
 
   // Returns whether the oat file has an associated image.
   bool HasImage() const {
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index 5de1540..ecf9db8 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -240,7 +240,7 @@
       elf_writer->EndDataBimgRelRo(data_bimg_rel_ro);
     }
 
-    if (!oat_writer.WriteHeader(elf_writer->GetStream(), /*boot_image_checksum=*/ 42u)) {
+    if (!oat_writer.WriteHeader(elf_writer->GetStream())) {
       return false;
     }
 
@@ -396,6 +396,7 @@
 
   ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex");
   SafeMap<std::string, std::string> key_value_store;
+  key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey");
   bool success = WriteElf(tmp_vdex.GetFile(),
                           tmp_oat.GetFile(),
                           class_linker->GetBootClassPath(),
@@ -418,7 +419,8 @@
   const OatHeader& oat_header = oat_file->GetOatHeader();
   ASSERT_TRUE(oat_header.IsValid());
   ASSERT_EQ(class_linker->GetBootClassPath().size(), oat_header.GetDexFileCount());  // core
-  ASSERT_EQ(42u, oat_header.GetBootImageChecksum());
+  ASSERT_TRUE(oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey) != nullptr);
+  ASSERT_STREQ("testkey", oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey));
 
   ASSERT_TRUE(java_lang_dex_file_ != nullptr);
   const DexFile& dex_file = *java_lang_dex_file_;
@@ -464,7 +466,7 @@
 TEST_F(OatTest, OatHeaderSizeCheck) {
   // If this test is failing and you have to update these constants,
   // it is time to update OatHeader::kOatVersion
-  EXPECT_EQ(68U, sizeof(OatHeader));
+  EXPECT_EQ(64U, sizeof(OatHeader));
   EXPECT_EQ(4U, sizeof(OatMethodOffsets));
   EXPECT_EQ(8U, sizeof(OatQuickMethodHeader));
   EXPECT_EQ(166 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index 58d12a1..c3fb5fd 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -177,9 +177,6 @@
     header_libs: [
         "libnativehelper_header_only",
     ],
-    include_dirs: [
-        "external/icu/icu4c/source/common",
-    ],
 }
 
 art_cc_test {
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index 278203d..46724ee 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -26,7 +26,6 @@
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "android-base/unique_fd.h"
-#include <unicode/uvernum.h>
 
 #include "art_field-inl.h"
 #include "base/file_utils.h"
@@ -329,15 +328,18 @@
 }
 
 std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames() {
-  // Note: This must match the TEST_CORE_JARS in Android.common_path.mk
+  // Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
   // because that's what we use for compiling the core.art image.
+  // It may contain additional modules from TEST_CORE_JARS.
   static const char* const kLibcoreModules[] = {
+      // CORE_IMG_JARS modules.
       "core-oj",
       "core-libart",
       "core-simple",
-      "conscrypt",
       "okhttp",
       "bouncycastle",
+      // Additional modules.
+      "conscrypt",
   };
 
   std::vector<std::string> result;
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 1c74a92..d2a5bb8 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -473,9 +473,6 @@
                            GetQuickToInterpreterBridgeOffset);
 #undef DUMP_OAT_HEADER_OFFSET
 
-    os << "BOOT IMAGE CHECKSUM:\n";
-    os << StringPrintf("0x%08x\n\n", oat_header.GetBootImageChecksum());
-
     // Print the key-value store.
     {
       os << "KEY VALUE STORE:\n";
diff --git a/runtime/Android.bp b/runtime/Android.bp
index b03ef60..60f1af1 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -553,9 +553,6 @@
     header_libs: [
         "libnativehelper_header_only",
     ],
-    include_dirs: [
-        "external/icu/icu4c/source/common",
-    ],
 }
 
 art_cc_test {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index d33541c..f6a3e32 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1037,20 +1037,15 @@
   runtime->SetSentinel(heap->AllocNonMovableObject<true>(
       self, java_lang_Object, java_lang_Object->GetObjectSize(), VoidFunctor()));
 
-  const std::vector<std::string>& boot_class_path = runtime->GetBootClassPath();
-  if (boot_class_path.size() != spaces.size()) {
-    *error_msg = StringPrintf("Boot class path has %zu components but there are %zu image spaces.",
-                              boot_class_path.size(),
-                              spaces.size());
-    return false;
-  }
+  const std::vector<std::string>& boot_class_path_locations = runtime->GetBootClassPathLocations();
+  CHECK_LE(spaces.size(), boot_class_path_locations.size());
   for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
     // Boot class loader, use a null handle.
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     if (!AddImageSpace(spaces[i],
                        ScopedNullHandle<mirror::ClassLoader>(),
                        /*dex_elements=*/ nullptr,
-                       /*dex_location=*/ boot_class_path[i].c_str(),
+                       /*dex_location=*/ boot_class_path_locations[i].c_str(),
                        /*out*/&dex_files,
                        error_msg)) {
       return false;
@@ -1069,6 +1064,15 @@
   return true;
 }
 
+void ClassLinker::AddExtraBootDexFiles(
+    Thread* self,
+    std::vector<std::unique_ptr<const DexFile>>&& additional_dex_files) {
+  for (std::unique_ptr<const DexFile>& dex_file : additional_dex_files) {
+    AppendToBootClassPath(self, *dex_file);
+    boot_dex_files_.push_back(std::move(dex_file));
+  }
+}
+
 bool ClassLinker::IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
                                     ObjPtr<mirror::ClassLoader> class_loader) {
   return class_loader == nullptr ||
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index d3eab7c..d0a7c9b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -127,6 +127,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
+  // Add boot class path dex files that were not included in the boot image.
+  // ClassLinker takes ownership of these dex files.
+  void AddExtraBootDexFiles(Thread* self,
+                            std::vector<std::unique_ptr<const DexFile>>&& additional_dex_files)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Add an image space to the class linker, may fix up classloader fields and dex cache fields.
   // The dex files that were newly opened for the space are placed in the out argument
   // out_dex_files. Returns true if the operation succeeded.
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index a101976..a20baa0 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -24,7 +24,6 @@
 #include "nativehelper/scoped_local_ref.h"
 
 #include "android-base/stringprintf.h"
-#include <unicode/uvernum.h>
 
 #include "art_field-inl.h"
 #include "base/file_utils.h"
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index b46c933..7f697d1 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -116,19 +116,19 @@
   ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
   EXPECT_EQ(filter, odex_file->GetCompilerFilter());
 
-  std::unique_ptr<ImageHeader> image_header(
-          gc::space::ImageSpace::ReadImageHeader(image_location.c_str(),
-                                                 kRuntimeISA,
-                                                 &error_msg));
-  ASSERT_TRUE(image_header != nullptr) << error_msg;
+  std::string boot_image_checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
+      Runtime::Current()->GetBootClassPath(), image_location, kRuntimeISA, &error_msg);
+  ASSERT_FALSE(boot_image_checksums.empty()) << error_msg;
+
   const OatHeader& oat_header = odex_file->GetOatHeader();
-  uint32_t boot_image_checksum = image_header->GetImageChecksum();
 
   if (CompilerFilter::DependsOnImageChecksum(filter)) {
+    const char* checksums = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
+    ASSERT_TRUE(checksums != nullptr);
     if (with_alternate_image) {
-      EXPECT_NE(boot_image_checksum, oat_header.GetBootImageChecksum());
+      EXPECT_NE(boot_image_checksums, checksums);
     } else {
-      EXPECT_EQ(boot_image_checksum, oat_header.GetBootImageChecksum());
+      EXPECT_EQ(boot_image_checksums, checksums);
     }
   }
 }
diff --git a/runtime/gc/allocator/dlmalloc.cc b/runtime/gc/allocator/dlmalloc.cc
index e508d5f..11ad8a8 100644
--- a/runtime/gc/allocator/dlmalloc.cc
+++ b/runtime/gc/allocator/dlmalloc.cc
@@ -38,6 +38,7 @@
 #pragma GCC diagnostic ignored "-Wempty-body"
 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
 #pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
+#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
 #include "../../../external/dlmalloc/malloc.c"
 // Note: malloc.c uses a DEBUG define to drive debug code. This interferes with the DEBUG severity
 //       of libbase, so undefine it now.
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 7736568..53aa9ba 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -860,6 +860,21 @@
   ConcurrentCopying* const collector_;
 };
 
+template <bool kNoUnEvac>
+void ConcurrentCopying::ScanDirtyObject(mirror::Object* obj) {
+  Scan<kNoUnEvac>(obj);
+  // Set the read-barrier state of a reference-type object to gray if its
+  // referent is not marked yet. This is to ensure that if GetReferent() is
+  // called, it triggers the read-barrier to process the referent before use.
+  if (UNLIKELY((obj->GetClass<kVerifyNone, kWithoutReadBarrier>()->IsTypeOfReferenceClass()))) {
+    mirror::Object* referent =
+        obj->AsReference<kVerifyNone, kWithoutReadBarrier>()->GetReferent<kWithoutReadBarrier>();
+    if (referent != nullptr && !IsInToSpace(referent)) {
+      obj->AtomicSetReadBarrierState(ReadBarrier::NonGrayState(), ReadBarrier::GrayState());
+    }
+  }
+}
+
 // Concurrently mark roots that are guarded by read barriers and process the mark stack.
 void ConcurrentCopying::MarkingPhase() {
   TimingLogger::ScopedTiming split("MarkingPhase", GetTimings());
@@ -924,7 +939,7 @@
                 LOG(FATAL) << "Scanning " << obj << " not in unevac space";
               }
             }
-            Scan<true>(obj);
+            ScanDirtyObject</*kNoUnEvac*/ true>(obj);
           },
           accounting::CardTable::kCardDirty - 1);
     }
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 237e070..e251fbc 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -161,6 +161,13 @@
   template <bool kNoUnEvac>
   void Scan(mirror::Object* to_ref) REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_);
+  // Scan the reference fields of object 'obj' in the dirty cards during
+  // card-table scan. In addition to visiting the references, it also sets the
+  // read-barrier state to gray for Reference-type objects to ensure that
+  // GetReferent() called on these objects calls the read-barrier on the referent.
+  template <bool kNoUnEvac>
+  void ScanDirtyObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!mark_stack_lock_);
   // Process a field.
   template <bool kNoUnEvac>
   void Process(mirror::Object* obj, MemberOffset offset)
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 46ff7dc..2ef3d92 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -90,13 +90,14 @@
   Thread* self = Thread::Current();
   uint64_t start_time = NanoTime();
   uint64_t thread_cpu_start_time = ThreadCpuNanoTime();
-  GetHeap()->CalculateWeightedAllocatedBytes();
+  GetHeap()->CalculatePreGcWeightedAllocatedBytes();
   Iteration* current_iteration = GetCurrentIteration();
   current_iteration->Reset(gc_cause, clear_soft_references);
   // Note transaction mode is single-threaded and there's no asynchronous GC and this flag doesn't
   // change in the middle of a GC.
   is_transaction_active_ = Runtime::Current()->IsActiveTransaction();
   RunPhases();  // Run all the GC phases.
+  GetHeap()->CalculatePostGcWeightedAllocatedBytes();
   // Add the current timings to the cumulative timings.
   cumulative_timings_.AddLogger(*GetTimings());
   // Update cumulative statistics with how many bytes the GC iteration freed.
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 9e1ba35..1c09b5c 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -214,7 +214,7 @@
   if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
     // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
     // That's fine.
-    CheckConcurrentGC(self, new_num_bytes_allocated, &obj);
+    CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj);
   }
   VerifyObject(obj);
   self->VerifyStack();
@@ -254,8 +254,8 @@
                                            size_t* bytes_allocated,
                                            size_t* usable_size,
                                            size_t* bytes_tl_bulk_allocated) {
-  if (allocator_type != kAllocatorTypeTLAB &&
-      allocator_type != kAllocatorTypeRegionTLAB &&
+  if (allocator_type != kAllocatorTypeRegionTLAB &&
+      allocator_type != kAllocatorTypeTLAB &&
       allocator_type != kAllocatorTypeRosAlloc &&
       UNLIKELY(IsOutOfMemoryOnAllocation(allocator_type, alloc_size, kGrow))) {
     return nullptr;
@@ -396,30 +396,46 @@
 inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type,
                                             size_t alloc_size,
                                             bool grow) {
-  size_t new_footprint = num_bytes_allocated_.load(std::memory_order_relaxed) + alloc_size;
-  if (UNLIKELY(new_footprint > max_allowed_footprint_)) {
-    if (UNLIKELY(new_footprint > growth_limit_)) {
+  size_t old_target = target_footprint_.load(std::memory_order_relaxed);
+  while (true) {
+    size_t old_allocated = num_bytes_allocated_.load(std::memory_order_relaxed);
+    size_t new_footprint = old_allocated + alloc_size;
+    // Tests against heap limits are inherently approximate, since multiple allocations may
+    // race, and this is not atomic with the allocation.
+    if (UNLIKELY(new_footprint <= old_target)) {
+      return false;
+    } else if (UNLIKELY(new_footprint > growth_limit_)) {
       return true;
     }
-    if (!AllocatorMayHaveConcurrentGC(allocator_type) || !IsGcConcurrent()) {
-      if (!grow) {
+    // We are between target_footprint_ and growth_limit_ .
+    if (AllocatorMayHaveConcurrentGC(allocator_type) && IsGcConcurrent()) {
+      return false;
+    } else {
+      if (grow) {
+        if (target_footprint_.compare_exchange_weak(/*inout ref*/old_target, new_footprint,
+                                                    std::memory_order_relaxed)) {
+          VlogHeapGrowth(old_target, new_footprint, alloc_size);
+          return false;
+        }  // else try again.
+      } else {
         return true;
       }
-      // TODO: Grow for allocation is racy, fix it.
-      VlogHeapGrowth(max_allowed_footprint_, new_footprint, alloc_size);
-      max_allowed_footprint_ = new_footprint;
     }
   }
-  return false;
 }
 
-// Request a GC if new_num_bytes_allocated is sufficiently large.
-// A call with new_num_bytes_allocated == 0 is a fast no-op.
-inline void Heap::CheckConcurrentGC(Thread* self,
+inline bool Heap::ShouldConcurrentGCForJava(size_t new_num_bytes_allocated) {
+  // For a Java allocation, we only check whether the number of Java allocated bytes excceeds a
+  // threshold. By not considering native allocation here, we (a) ensure that Java heap bounds are
+  // maintained, and (b) reduce the cost of the check here.
+  return new_num_bytes_allocated >= concurrent_start_bytes_;
+}
+
+inline void Heap::CheckConcurrentGCForJava(Thread* self,
                                     size_t new_num_bytes_allocated,
                                     ObjPtr<mirror::Object>* obj) {
-  if (UNLIKELY(new_num_bytes_allocated >= concurrent_start_bytes_)) {
-    RequestConcurrentGCAndSaveObject(self, false, obj);
+  if (UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
+    RequestConcurrentGCAndSaveObject(self, false /* force_full */, obj);
   }
 }
 
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index bfb1019..77254ce 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -17,6 +17,7 @@
 #include "heap.h"
 
 #include <limits>
+#include <malloc.h>  // For mallinfo()
 #include <memory>
 #include <vector>
 
@@ -187,7 +188,7 @@
            bool low_memory_mode,
            size_t long_pause_log_threshold,
            size_t long_gc_log_threshold,
-           bool ignore_max_footprint,
+           bool ignore_target_footprint,
            bool use_tlab,
            bool verify_pre_gc_heap,
            bool verify_pre_sweeping_heap,
@@ -214,9 +215,11 @@
       long_pause_log_threshold_(long_pause_log_threshold),
       long_gc_log_threshold_(long_gc_log_threshold),
       process_cpu_start_time_ns_(ProcessCpuNanoTime()),
-      last_process_cpu_time_ns_(process_cpu_start_time_ns_),
-      weighted_allocated_bytes_(0.0),
-      ignore_max_footprint_(ignore_max_footprint),
+      pre_gc_last_process_cpu_time_ns_(process_cpu_start_time_ns_),
+      post_gc_last_process_cpu_time_ns_(process_cpu_start_time_ns_),
+      pre_gc_weighted_allocated_bytes_(0.0),
+      post_gc_weighted_allocated_bytes_(0.0),
+      ignore_target_footprint_(ignore_target_footprint),
       zygote_creation_lock_("zygote creation lock", kZygoteCreationLock),
       zygote_space_(nullptr),
       large_object_threshold_(large_object_threshold),
@@ -229,13 +232,14 @@
       next_gc_type_(collector::kGcTypePartial),
       capacity_(capacity),
       growth_limit_(growth_limit),
-      max_allowed_footprint_(initial_size),
+      target_footprint_(initial_size),
       concurrent_start_bytes_(std::numeric_limits<size_t>::max()),
       total_bytes_freed_ever_(0),
       total_objects_freed_ever_(0),
       num_bytes_allocated_(0),
-      new_native_bytes_allocated_(0),
+      native_bytes_registered_(0),
       old_native_bytes_allocated_(0),
+      native_objects_notified_(0),
       num_bytes_freed_revoke_(0),
       verify_missing_card_marks_(false),
       verify_system_weaks_(false),
@@ -614,11 +618,11 @@
   task_processor_.reset(new TaskProcessor());
   reference_processor_.reset(new ReferenceProcessor());
   pending_task_lock_ = new Mutex("Pending task lock");
-  if (ignore_max_footprint_) {
+  if (ignore_target_footprint_) {
     SetIdealFootprint(std::numeric_limits<size_t>::max());
     concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
   }
-  CHECK_NE(max_allowed_footprint_, 0U);
+  CHECK_NE(target_footprint_.load(std::memory_order_relaxed), 0U);
   // Create our garbage collectors.
   for (size_t i = 0; i < 2; ++i) {
     const bool concurrent = i != 0;
@@ -1070,12 +1074,25 @@
   }
 }
 
-void Heap::CalculateWeightedAllocatedBytes() {
-  uint64_t current_process_cpu_time = ProcessCpuNanoTime();
+double Heap::CalculateGcWeightedAllocatedBytes(uint64_t gc_last_process_cpu_time_ns,
+                                               uint64_t current_process_cpu_time) const {
   uint64_t bytes_allocated = GetBytesAllocated();
-  double weight = current_process_cpu_time - last_process_cpu_time_ns_;
-  weighted_allocated_bytes_ += weight * bytes_allocated;
-  last_process_cpu_time_ns_ = current_process_cpu_time;
+  double weight = current_process_cpu_time - gc_last_process_cpu_time_ns;
+  return weight * bytes_allocated;
+}
+
+void Heap::CalculatePreGcWeightedAllocatedBytes() {
+  uint64_t current_process_cpu_time = ProcessCpuNanoTime();
+  pre_gc_weighted_allocated_bytes_ +=
+    CalculateGcWeightedAllocatedBytes(pre_gc_last_process_cpu_time_ns_, current_process_cpu_time);
+  pre_gc_last_process_cpu_time_ns_ = current_process_cpu_time;
+}
+
+void Heap::CalculatePostGcWeightedAllocatedBytes() {
+  uint64_t current_process_cpu_time = ProcessCpuNanoTime();
+  post_gc_weighted_allocated_bytes_ +=
+    CalculateGcWeightedAllocatedBytes(post_gc_last_process_cpu_time_ns_, current_process_cpu_time);
+  post_gc_last_process_cpu_time_ns_ = current_process_cpu_time;
 }
 
 uint64_t Heap::GetTotalGcCpuTime() {
@@ -1143,10 +1160,11 @@
     rosalloc_space_->DumpStats(os);
   }
 
-  os << "Registered native bytes allocated: "
-     << (old_native_bytes_allocated_.load(std::memory_order_relaxed) +
-         new_native_bytes_allocated_.load(std::memory_order_relaxed))
-     << "\n";
+  os << "Native bytes total: " << GetNativeBytes()
+     << " registered: " << native_bytes_registered_.load(std::memory_order_relaxed) << "\n";
+
+  os << "Total native bytes at last GC: "
+     << old_native_bytes_allocated_.load(std::memory_order_relaxed) << "\n";
 
   BaseMutex::DumpAll(os);
 }
@@ -1157,8 +1175,12 @@
   }
 
   process_cpu_start_time_ns_ = ProcessCpuNanoTime();
-  last_process_cpu_time_ns_ = process_cpu_start_time_ns_;
-  weighted_allocated_bytes_ = 0u;
+
+  pre_gc_last_process_cpu_time_ns_ = process_cpu_start_time_ns_;
+  pre_gc_weighted_allocated_bytes_ = 0u;
+
+  post_gc_last_process_cpu_time_ns_ = process_cpu_start_time_ns_;
+  post_gc_weighted_allocated_bytes_ = 0u;
 
   total_bytes_freed_ever_ = 0;
   total_objects_freed_ever_ = 0;
@@ -1318,7 +1340,8 @@
   size_t total_bytes_free = GetFreeMemory();
   oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
       << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM,"
-      << " max allowed footprint " << max_allowed_footprint_ << ", growth limit "
+      << " target footprint " << target_footprint_.load(std::memory_order_relaxed)
+      << ", growth limit "
       << growth_limit_;
   // If the allocation failed due to fragmentation, print out the largest continuous allocation.
   if (total_bytes_free >= byte_count) {
@@ -1853,7 +1876,7 @@
 }
 
 void Heap::SetTargetHeapUtilization(float target) {
-  DCHECK_GT(target, 0.0f);  // asserted in Java code
+  DCHECK_GT(target, 0.1f);  // asserted in Java code
   DCHECK_LT(target, 1.0f);
   target_utilization_ = target;
 }
@@ -2267,8 +2290,8 @@
     }
     if (IsGcConcurrent()) {
       concurrent_start_bytes_ =
-          std::max(max_allowed_footprint_, kMinConcurrentRemainingBytes) -
-          kMinConcurrentRemainingBytes;
+          UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
+                             kMinConcurrentRemainingBytes);
     } else {
       concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
     }
@@ -2597,6 +2620,39 @@
   ATRACE_INT("Heap size (KB)", heap_size / KB);
 }
 
+size_t Heap::GetNativeBytes() {
+  size_t malloc_bytes;
+  size_t mmapped_bytes;
+#if defined(__BIONIC__) || defined(__GLIBC__)
+  struct mallinfo mi = mallinfo();
+  // In spite of the documentation, the jemalloc version of this call seems to do what we want,
+  // and it is thread-safe.
+  if (sizeof(size_t) > sizeof(mi.uordblks) && sizeof(size_t) > sizeof(mi.hblkhd)) {
+    // Shouldn't happen, but glibc declares uordblks as int.
+    // Avoiding sign extension gets us correct behavior for another 2 GB.
+    malloc_bytes = (unsigned int)mi.uordblks;
+    mmapped_bytes = (unsigned int)mi.hblkhd;
+  } else {
+    malloc_bytes = mi.uordblks;
+    mmapped_bytes = mi.hblkhd;
+  }
+  // From the spec, we clearly have mmapped_bytes <= malloc_bytes. Reality is sometimes
+  // dramatically different. (b/119580449) If so, fudge it.
+  if (mmapped_bytes > malloc_bytes) {
+    malloc_bytes = mmapped_bytes;
+  }
+#else
+  // We should hit this case only in contexts in which GC triggering is not critical. Effectively
+  // disable GC triggering based on malloc().
+  malloc_bytes = 1000;
+#endif
+  return malloc_bytes + native_bytes_registered_.load(std::memory_order_relaxed);
+  // An alternative would be to get RSS from /proc/self/statm. Empirically, that's no
+  // more expensive, and it would allow us to count memory allocated by means other than malloc.
+  // However it would change as pages are unmapped and remapped due to memory pressure, among
+  // other things. It seems risky to trigger GCs as a result of such changes.
+}
+
 collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
                                                GcCause gc_cause,
                                                bool clear_soft_references) {
@@ -2647,16 +2703,7 @@
     ++runtime->GetStats()->gc_for_alloc_count;
     ++self->GetStats()->gc_for_alloc_count;
   }
-  const uint64_t bytes_allocated_before_gc = GetBytesAllocated();
-
-  if (gc_type == NonStickyGcType()) {
-    // Move all bytes from new_native_bytes_allocated_ to
-    // old_native_bytes_allocated_ now that GC has been triggered, resetting
-    // new_native_bytes_allocated_ to zero in the process.
-    old_native_bytes_allocated_.fetch_add(
-        new_native_bytes_allocated_.exchange(0, std::memory_order_relaxed),
-        std::memory_order_relaxed);
-  }
+  const size_t bytes_allocated_before_gc = GetBytesAllocated();
 
   DCHECK_LT(gc_type, collector::kGcTypeMax);
   DCHECK_NE(gc_type, collector::kGcTypeNone);
@@ -2728,6 +2775,9 @@
   FinishGC(self, gc_type);
   // Inform DDMS that a GC completed.
   Dbg::GcDidFinish();
+
+  old_native_bytes_allocated_.store(GetNativeBytes());
+
   // Unload native libraries for class unloading. We do this after calling FinishGC to prevent
   // deadlocks in case the JNI_OnUnload function does allocations.
   {
@@ -3502,16 +3552,17 @@
 }
 
 size_t Heap::GetPercentFree() {
-  return static_cast<size_t>(100.0f * static_cast<float>(GetFreeMemory()) / max_allowed_footprint_);
+  return static_cast<size_t>(100.0f * static_cast<float>(
+      GetFreeMemory()) / target_footprint_.load(std::memory_order_relaxed));
 }
 
-void Heap::SetIdealFootprint(size_t max_allowed_footprint) {
-  if (max_allowed_footprint > GetMaxMemory()) {
-    VLOG(gc) << "Clamp target GC heap from " << PrettySize(max_allowed_footprint) << " to "
+void Heap::SetIdealFootprint(size_t target_footprint) {
+  if (target_footprint > GetMaxMemory()) {
+    VLOG(gc) << "Clamp target GC heap from " << PrettySize(target_footprint) << " to "
              << PrettySize(GetMaxMemory());
-    max_allowed_footprint = GetMaxMemory();
+    target_footprint = GetMaxMemory();
   }
-  max_allowed_footprint_ = max_allowed_footprint;
+  target_footprint_.store(target_footprint, std::memory_order_relaxed);
 }
 
 bool Heap::IsMovableObject(ObjPtr<mirror::Object> obj) const {
@@ -3544,10 +3595,10 @@
 }
 
 void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran,
-                              uint64_t bytes_allocated_before_gc) {
+                              size_t bytes_allocated_before_gc) {
   // We know what our utilization is at this moment.
   // This doesn't actually resize any memory. It just lets the heap grow more when necessary.
-  const uint64_t bytes_allocated = GetBytesAllocated();
+  const size_t bytes_allocated = GetBytesAllocated();
   // Trace the new heap size after the GC is finished.
   TraceHeapSize(bytes_allocated);
   uint64_t target_size;
@@ -3555,16 +3606,18 @@
   // Use the multiplier to grow more for foreground.
   const double multiplier = HeapGrowthMultiplier();  // Use the multiplier to grow more for
   // foreground.
-  const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier);
-  const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier);
+  const size_t adjusted_min_free = static_cast<size_t>(min_free_ * multiplier);
+  const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
   if (gc_type != collector::kGcTypeSticky) {
     // Grow the heap for non sticky GC.
-    ssize_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;
-    CHECK_GE(delta, 0) << "bytes_allocated=" << bytes_allocated
-                       << " target_utilization_=" << target_utilization_;
+    uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
+    DCHECK_LE(delta, std::numeric_limits<size_t>::max()) << "bytes_allocated=" << bytes_allocated
+        << " target_utilization_=" << target_utilization_;
     target_size = bytes_allocated + delta * multiplier;
-    target_size = std::min(target_size, bytes_allocated + adjusted_max_free);
-    target_size = std::max(target_size, bytes_allocated + adjusted_min_free);
+    target_size = std::min(target_size,
+                           static_cast<uint64_t>(bytes_allocated + adjusted_max_free));
+    target_size = std::max(target_size,
+                           static_cast<uint64_t>(bytes_allocated + adjusted_min_free));
     next_gc_type_ = collector::kGcTypeSticky;
   } else {
     collector::GcType non_sticky_gc_type = NonStickyGcType();
@@ -3581,22 +3634,24 @@
     // We also check that the bytes allocated aren't over the footprint limit in order to prevent a
     // pathological case where dead objects which aren't reclaimed by sticky could get accumulated
     // if the sticky GC throughput always remained >= the full/partial throughput.
+    size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
     if (current_gc_iteration_.GetEstimatedThroughput() * kStickyGcThroughputAdjustment >=
         non_sticky_collector->GetEstimatedMeanThroughput() &&
         non_sticky_collector->NumberOfIterations() > 0 &&
-        bytes_allocated <= max_allowed_footprint_) {
+        bytes_allocated <= target_footprint) {
       next_gc_type_ = collector::kGcTypeSticky;
     } else {
       next_gc_type_ = non_sticky_gc_type;
     }
     // If we have freed enough memory, shrink the heap back down.
-    if (bytes_allocated + adjusted_max_free < max_allowed_footprint_) {
+    if (bytes_allocated + adjusted_max_free < target_footprint) {
       target_size = bytes_allocated + adjusted_max_free;
     } else {
-      target_size = std::max(bytes_allocated, static_cast<uint64_t>(max_allowed_footprint_));
+      target_size = std::max(bytes_allocated, target_footprint);
     }
   }
-  if (!ignore_max_footprint_) {
+  CHECK_LE(target_size, std::numeric_limits<size_t>::max());
+  if (!ignore_target_footprint_) {
     SetIdealFootprint(target_size);
     if (IsGcConcurrent()) {
       const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() +
@@ -3605,26 +3660,25 @@
       // Bytes allocated will shrink by freed_bytes after the GC runs, so if we want to figure out
       // how many bytes were allocated during the GC we need to add freed_bytes back on.
       CHECK_GE(bytes_allocated + freed_bytes, bytes_allocated_before_gc);
-      const uint64_t bytes_allocated_during_gc = bytes_allocated + freed_bytes -
+      const size_t bytes_allocated_during_gc = bytes_allocated + freed_bytes -
           bytes_allocated_before_gc;
       // Calculate when to perform the next ConcurrentGC.
       // Estimate how many remaining bytes we will have when we need to start the next GC.
       size_t remaining_bytes = bytes_allocated_during_gc;
       remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);
       remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);
-      if (UNLIKELY(remaining_bytes > max_allowed_footprint_)) {
+      size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
+      if (UNLIKELY(remaining_bytes > target_footprint)) {
         // A never going to happen situation that from the estimated allocation rate we will exceed
         // the applications entire footprint with the given estimated allocation rate. Schedule
         // another GC nearly straight away.
-        remaining_bytes = kMinConcurrentRemainingBytes;
+        remaining_bytes = std::min(kMinConcurrentRemainingBytes, target_footprint);
       }
-      DCHECK_LE(remaining_bytes, max_allowed_footprint_);
-      DCHECK_LE(max_allowed_footprint_, GetMaxMemory());
+      DCHECK_LE(target_footprint_.load(std::memory_order_relaxed), GetMaxMemory());
       // Start a concurrent GC when we get close to the estimated remaining bytes. When the
       // allocation rate is very high, remaining_bytes could tell us that we should start a GC
       // right away.
-      concurrent_start_bytes_ = std::max(max_allowed_footprint_ - remaining_bytes,
-                                         static_cast<size_t>(bytes_allocated));
+      concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
     }
   }
 }
@@ -3652,11 +3706,11 @@
 }
 
 void Heap::ClearGrowthLimit() {
-  if (max_allowed_footprint_ == growth_limit_ && growth_limit_ < capacity_) {
-    max_allowed_footprint_ = capacity_;
+  if (target_footprint_.load(std::memory_order_relaxed) == growth_limit_
+      && growth_limit_ < capacity_) {
+    target_footprint_.store(capacity_, std::memory_order_relaxed);
     concurrent_start_bytes_ =
-         std::max(max_allowed_footprint_, kMinConcurrentRemainingBytes) -
-         kMinConcurrentRemainingBytes;
+        UnsignedDifference(capacity_, kMinConcurrentRemainingBytes);
   }
   growth_limit_ = capacity_;
   ScopedObjectAccess soa(Thread::Current());
@@ -3896,40 +3950,101 @@
                             static_cast<jlong>(timeout));
 }
 
-void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) {
-  size_t old_value = new_native_bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
+// For GC triggering purposes, we count old (pre-last-GC) and new native allocations as
+// different fractions of Java allocations.
+// For now, we essentially do not count old native allocations at all, so that we can preserve the
+// existing behavior of not limiting native heap size. If we seriously considered it, we would
+// have to adjust collection thresholds when we encounter large amounts of old native memory,
+// and handle native out-of-memory situations.
 
-  if (old_value > NativeAllocationGcWatermark() * HeapGrowthMultiplier() &&
-             !IsGCRequestPending()) {
-    // Trigger another GC because there have been enough native bytes
-    // allocated since the last GC.
+static constexpr size_t kOldNativeDiscountFactor = 65536;  // Approximately infinite for now.
+static constexpr size_t kNewNativeDiscountFactor = 2;
+
+// If weighted java + native memory use exceeds our target by kStopForNativeFactor, and
+// newly allocated memory exceeds kHugeNativeAlloc, we wait for GC to complete to avoid
+// running out of memory.
+static constexpr float kStopForNativeFactor = 2.0;
+static constexpr size_t kHugeNativeAllocs = 200*1024*1024;
+
+// Return the ratio of the weighted native + java allocated bytes to its target value.
+// A return value > 1.0 means we should collect. Significantly larger values mean we're falling
+// behind.
+inline float Heap::NativeMemoryOverTarget(size_t current_native_bytes) {
+  // Collection check for native allocation. Does not enforce Java heap bounds.
+  // With adj_start_bytes defined below, effectively checks
+  // <java bytes allocd> + c1*<old native allocd> + c2*<new native allocd) >= adj_start_bytes,
+  // where c3 > 1, and currently c1 and c2 are 1 divided by the values defined above.
+  size_t old_native_bytes = old_native_bytes_allocated_.load(std::memory_order_relaxed);
+  if (old_native_bytes > current_native_bytes) {
+    // Net decrease; skip the check, but update old value.
+    // It's OK to lose an update if two stores race.
+    old_native_bytes_allocated_.store(current_native_bytes, std::memory_order_relaxed);
+    return 0.0;
+  } else {
+    size_t new_native_bytes = UnsignedDifference(current_native_bytes, old_native_bytes);
+    size_t weighted_native_bytes = new_native_bytes / kNewNativeDiscountFactor
+        + old_native_bytes / kOldNativeDiscountFactor;
+    size_t adj_start_bytes = concurrent_start_bytes_
+        + NativeAllocationGcWatermark() / kNewNativeDiscountFactor;
+    return static_cast<float>(GetBytesAllocated() + weighted_native_bytes)
+         / static_cast<float>(adj_start_bytes);
+  }
+}
+
+inline void Heap::CheckConcurrentGCForNative(Thread* self) {
+  size_t current_native_bytes = GetNativeBytes();
+  float gc_urgency = NativeMemoryOverTarget(current_native_bytes);
+  if (UNLIKELY(gc_urgency >= 1.0)) {
     if (IsGcConcurrent()) {
-      RequestConcurrentGC(ThreadForEnv(env), kGcCauseForNativeAlloc, /*force_full=*/true);
+      RequestConcurrentGC(self, kGcCauseForNativeAlloc, /*force_full=*/true);
+      if (gc_urgency > kStopForNativeFactor
+          && current_native_bytes > kHugeNativeAllocs) {
+        // We're in danger of running out of memory due to rampant native allocation.
+        if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
+          LOG(INFO) << "Stopping for native allocation, urgency: " << gc_urgency;
+        }
+        WaitForGcToComplete(kGcCauseForAlloc, self);
+      }
     } else {
       CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false);
     }
   }
 }
 
-void Heap::RegisterNativeFree(JNIEnv*, size_t bytes) {
-  // Take the bytes freed out of new_native_bytes_allocated_ first. If
-  // new_native_bytes_allocated_ reaches zero, take the remaining bytes freed
-  // out of old_native_bytes_allocated_ to ensure all freed bytes are
-  // accounted for.
-  size_t allocated;
-  size_t new_freed_bytes;
-  do {
-    allocated = new_native_bytes_allocated_.load(std::memory_order_relaxed);
-    new_freed_bytes = std::min(allocated, bytes);
-  } while (!new_native_bytes_allocated_.CompareAndSetWeakRelaxed(allocated,
-                                                                   allocated - new_freed_bytes));
-  if (new_freed_bytes < bytes) {
-    old_native_bytes_allocated_.fetch_sub(bytes - new_freed_bytes, std::memory_order_relaxed);
+// About kNotifyNativeInterval allocations have occurred. Check whether we should garbage collect.
+void Heap::NotifyNativeAllocations(JNIEnv* env) {
+  native_objects_notified_.fetch_add(kNotifyNativeInterval, std::memory_order_relaxed);
+  CheckConcurrentGCForNative(ThreadForEnv(env));
+}
+
+// Register a native allocation with an explicit size.
+// This should only be done for large allocations of non-malloc memory, which we wouldn't
+// otherwise see.
+void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) {
+  native_bytes_registered_.fetch_add(bytes, std::memory_order_relaxed);
+  uint32_t objects_notified =
+      native_objects_notified_.fetch_add(1, std::memory_order_relaxed);
+  if (objects_notified % kNotifyNativeInterval == kNotifyNativeInterval - 1
+      || bytes > kCheckImmediatelyThreshold) {
+    CheckConcurrentGCForNative(ThreadForEnv(env));
   }
 }
 
+void Heap::RegisterNativeFree(JNIEnv*, size_t bytes) {
+  size_t allocated;
+  size_t new_freed_bytes;
+  do {
+    allocated = native_bytes_registered_.load(std::memory_order_relaxed);
+    new_freed_bytes = std::min(allocated, bytes);
+    // We should not be registering more free than allocated bytes.
+    // But correctly keep going in non-debug builds.
+    DCHECK_EQ(new_freed_bytes, bytes);
+  } while (!native_bytes_registered_.CompareAndSetWeakRelaxed(allocated,
+                                                              allocated - new_freed_bytes));
+}
+
 size_t Heap::GetTotalMemory() const {
-  return std::max(max_allowed_footprint_, GetBytesAllocated());
+  return std::max(target_footprint_.load(std::memory_order_relaxed), GetBytesAllocated());
 }
 
 void Heap::AddModUnionTable(accounting::ModUnionTable* mod_union_table) {
@@ -4231,8 +4346,8 @@
   return verification_.get();
 }
 
-void Heap::VlogHeapGrowth(size_t max_allowed_footprint, size_t new_footprint, size_t alloc_size) {
-  VLOG(heap) << "Growing heap from " << PrettySize(max_allowed_footprint) << " to "
+void Heap::VlogHeapGrowth(size_t old_footprint, size_t new_footprint, size_t alloc_size) {
+  VLOG(heap) << "Growing heap from " << PrettySize(old_footprint) << " to "
              << PrettySize(new_footprint) << " for a " << PrettySize(alloc_size) << " allocation";
 }
 
@@ -4243,20 +4358,21 @@
     gc::Heap* heap = Runtime::Current()->GetHeap();
     // Trigger a GC, if not already done. The first GC after fork, whenever it
     // takes place, will adjust the thresholds to normal levels.
-    if (heap->max_allowed_footprint_ == heap->growth_limit_) {
+    if (heap->target_footprint_.load(std::memory_order_relaxed) == heap->growth_limit_) {
       heap->RequestConcurrentGC(self, kGcCauseBackground, false);
     }
   }
 };
 
 void Heap::PostForkChildAction(Thread* self) {
-  // Temporarily increase max_allowed_footprint_ and concurrent_start_bytes_ to
+  // Temporarily increase target_footprint_ and concurrent_start_bytes_ to
   // max values to avoid GC during app launch.
   if (collector_type_ == kCollectorTypeCC && !IsLowMemoryMode()) {
-    // Set max_allowed_footprint_ to the largest allowed value.
+    // Set target_footprint_ to the largest allowed value.
     SetIdealFootprint(growth_limit_);
     // Set concurrent_start_bytes_ to half of the heap size.
-    concurrent_start_bytes_ = std::max(max_allowed_footprint_ / 2, GetBytesAllocated());
+    size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
+    concurrent_start_bytes_ = std::max(target_footprint / 2, GetBytesAllocated());
 
     GetTaskProcessor()->AddTask(
         self, new TriggerPostForkCCGcTask(NanoTime() + MsToNs(kPostForkMaxHeapDurationMS)));
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 57c7376..de65f023 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -126,7 +126,6 @@
 
 class Heap {
  public:
-  // If true, measure the total allocation time.
   static constexpr size_t kDefaultStartingSize = kPageSize;
   static constexpr size_t kDefaultInitialSize = 2 * MB;
   static constexpr size_t kDefaultMaximumSize = 256 * MB;
@@ -155,6 +154,16 @@
   // Used so that we don't overflow the allocation time atomic integer.
   static constexpr size_t kTimeAdjust = 1024;
 
+  // Client should call NotifyNativeAllocation every kNotifyNativeInterval allocations.
+  // Should be chosen so that time_to_call_mallinfo / kNotifyNativeInterval is on the same order
+  // as object allocation time. time_to_call_mallinfo seems to be on the order of 1 usec.
+  static constexpr uint32_t kNotifyNativeInterval = 32;
+
+  // RegisterNativeAllocation checks immediately whether GC is needed if size exceeds the
+  // following. kCheckImmediatelyThreshold * kNotifyNativeInterval should be small enough to
+  // make it safe to allocate that many bytes between checks.
+  static constexpr size_t kCheckImmediatelyThreshold = 300000;
+
   // How often we allow heap trimming to happen (nanoseconds).
   static constexpr uint64_t kHeapTrimWait = MsToNs(5000);
   // How long we wait after a transition request to perform a collector transition (nanoseconds).
@@ -187,7 +196,7 @@
        bool low_memory_mode,
        size_t long_pause_threshold,
        size_t long_gc_threshold,
-       bool ignore_max_footprint,
+       bool ignore_target_footprint,
        bool use_tlab,
        bool verify_pre_gc_heap,
        bool verify_pre_sweeping_heap,
@@ -269,10 +278,22 @@
   void CheckPreconditionsForAllocObject(ObjPtr<mirror::Class> c, size_t byte_count)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Inform the garbage collector of a non-malloc allocated native memory that might become
+  // reclaimable in the future as a result of Java garbage collection.
   void RegisterNativeAllocation(JNIEnv* env, size_t bytes)
       REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
   void RegisterNativeFree(JNIEnv* env, size_t bytes);
 
+  // Notify the garbage collector of malloc allocations that might be reclaimable
+  // as a result of Java garbage collection. Each such call represents approximately
+  // kNotifyNativeInterval such allocations.
+  void NotifyNativeAllocations(JNIEnv* env)
+      REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+
+  uint32_t GetNotifyNativeInterval() {
+    return kNotifyNativeInterval;
+  }
+
   // Change the allocator, updates entrypoints.
   void ChangeAllocator(AllocatorType allocator)
       REQUIRES(Locks::mutator_lock_, !Locks::runtime_shutdown_lock_);
@@ -397,11 +418,16 @@
     REQUIRES(!Locks::heap_bitmap_lock_)
     REQUIRES(Locks::mutator_lock_);
 
-  double GetWeightedAllocatedBytes() const {
-    return weighted_allocated_bytes_;
+  double GetPreGcWeightedAllocatedBytes() const {
+    return pre_gc_weighted_allocated_bytes_;
   }
 
-  void CalculateWeightedAllocatedBytes();
+  double GetPostGcWeightedAllocatedBytes() const {
+    return post_gc_weighted_allocated_bytes_;
+  }
+
+  void CalculatePreGcWeightedAllocatedBytes();
+  void CalculatePostGcWeightedAllocatedBytes();
   uint64_t GetTotalGcCpuTime();
 
   uint64_t GetProcessCpuStartTime() const {
@@ -531,21 +557,20 @@
 
   // Returns approximately how much free memory we have until the next GC happens.
   size_t GetFreeMemoryUntilGC() const {
-    return max_allowed_footprint_ - GetBytesAllocated();
+    return UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
+                              GetBytesAllocated());
   }
 
   // Returns approximately how much free memory we have until the next OOME happens.
   size_t GetFreeMemoryUntilOOME() const {
-    return growth_limit_ - GetBytesAllocated();
+    return UnsignedDifference(growth_limit_, GetBytesAllocated());
   }
 
   // Returns how much free memory we have until we need to grow the heap to perform an allocation.
   // Similar to GetFreeMemoryUntilGC. Implements java.lang.Runtime.freeMemory.
   size_t GetFreeMemory() const {
-    size_t byte_allocated = num_bytes_allocated_.load(std::memory_order_relaxed);
-    size_t total_memory = GetTotalMemory();
-    // Make sure we don't get a negative number.
-    return total_memory - std::min(total_memory, byte_allocated);
+    return UnsignedDifference(GetTotalMemory(),
+                              num_bytes_allocated_.load(std::memory_order_relaxed));
   }
 
   // Get the space that corresponds to an object's address. Current implementation searches all
@@ -858,6 +883,9 @@
       REQUIRES(!*gc_complete_lock_);
   void FinishGC(Thread* self, collector::GcType gc_type) REQUIRES(!*gc_complete_lock_);
 
+  double CalculateGcWeightedAllocatedBytes(uint64_t gc_last_process_cpu_time_ns,
+                                           uint64_t current_process_cpu_time) const;
+
   // Create a mem map with a preferred base address.
   static MemMap MapAnonymousPreferredAddress(const char* name,
                                              uint8_t* request_begin,
@@ -869,12 +897,16 @@
     return main_space_backup_ != nullptr;
   }
 
+  static ALWAYS_INLINE size_t UnsignedDifference(size_t x, size_t y) {
+    return x > y ? x - y : 0;
+  }
+
   static ALWAYS_INLINE bool AllocatorHasAllocationStack(AllocatorType allocator_type) {
     return
+        allocator_type != kAllocatorTypeRegionTLAB &&
         allocator_type != kAllocatorTypeBumpPointer &&
         allocator_type != kAllocatorTypeTLAB &&
-        allocator_type != kAllocatorTypeRegion &&
-        allocator_type != kAllocatorTypeRegionTLAB;
+        allocator_type != kAllocatorTypeRegion;
   }
   static ALWAYS_INLINE bool AllocatorMayHaveConcurrentGC(AllocatorType allocator_type) {
     if (kUseReadBarrier) {
@@ -882,24 +914,30 @@
       return true;
     }
     return
-        allocator_type != kAllocatorTypeBumpPointer &&
-        allocator_type != kAllocatorTypeTLAB;
+        allocator_type != kAllocatorTypeTLAB &&
+        allocator_type != kAllocatorTypeBumpPointer;
   }
   static bool IsMovingGc(CollectorType collector_type) {
     return
+        collector_type == kCollectorTypeCC ||
         collector_type == kCollectorTypeSS ||
         collector_type == kCollectorTypeGSS ||
-        collector_type == kCollectorTypeCC ||
         collector_type == kCollectorTypeCCBackground ||
         collector_type == kCollectorTypeHomogeneousSpaceCompact;
   }
   bool ShouldAllocLargeObject(ObjPtr<mirror::Class> c, size_t byte_count) const
       REQUIRES_SHARED(Locks::mutator_lock_);
-  ALWAYS_INLINE void CheckConcurrentGC(Thread* self,
-                                       size_t new_num_bytes_allocated,
-                                       ObjPtr<mirror::Object>* obj)
+
+  // Checks whether we should garbage collect:
+  ALWAYS_INLINE bool ShouldConcurrentGCForJava(size_t new_num_bytes_allocated);
+  float NativeMemoryOverTarget(size_t current_native_bytes);
+  ALWAYS_INLINE void CheckConcurrentGCForJava(Thread* self,
+                                              size_t new_num_bytes_allocated,
+                                              ObjPtr<mirror::Object>* obj)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
+  void CheckConcurrentGCForNative(Thread* self)
+      REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
 
   accounting::ObjectStack* GetMarkStack() {
     return mark_stack_.get();
@@ -960,6 +998,11 @@
   void ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Are we out of memory, and thus should force a GC or fail?
+  // For concurrent collectors, out of memory is defined by growth_limit_.
+  // For nonconcurrent collectors it is defined by target_footprint_ unless grow is
+  // set. If grow is set, the limit is growth_limit_ and we adjust target_footprint_
+  // to accomodate the allocation.
   ALWAYS_INLINE bool IsOutOfMemoryOnAllocation(AllocatorType allocator_type,
                                                size_t alloc_size,
                                                bool grow);
@@ -1023,7 +1066,7 @@
   // collection. bytes_allocated_before_gc is used to measure bytes / second for the period which
   // the GC was run.
   void GrowForUtilization(collector::GarbageCollector* collector_ran,
-                          uint64_t bytes_allocated_before_gc = 0);
+                          size_t bytes_allocated_before_gc = 0);
 
   size_t GetPercentFree();
 
@@ -1057,8 +1100,8 @@
   // What kind of concurrency behavior is the runtime after? Currently true for concurrent mark
   // sweep GC, false for other GC types.
   bool IsGcConcurrent() const ALWAYS_INLINE {
-    return collector_type_ == kCollectorTypeCMS ||
-        collector_type_ == kCollectorTypeCC ||
+    return collector_type_ == kCollectorTypeCC ||
+        collector_type_ == kCollectorTypeCMS ||
         collector_type_ == kCollectorTypeCCBackground;
   }
 
@@ -1087,15 +1130,19 @@
     return HasZygoteSpace() ? collector::kGcTypePartial : collector::kGcTypeFull;
   }
 
-  // How large new_native_bytes_allocated_ can grow before we trigger a new
-  // GC.
+  // Return the amount of space we allow for native memory when deciding whether to
+  // collect. We collect when a weighted sum of Java memory plus native memory exceeds
+  // the similarly weighted sum of the Java heap size target and this value.
   ALWAYS_INLINE size_t NativeAllocationGcWatermark() const {
-    // Reuse max_free_ for the native allocation gc watermark, so that the
-    // native heap is treated in the same way as the Java heap in the case
-    // where the gc watermark update would exceed max_free_. Using max_free_
-    // instead of the target utilization means the watermark doesn't depend on
-    // the current number of registered native allocations.
-    return max_free_;
+    // It probably makes most sense to use a constant multiple of target_footprint_ .
+    // This is a good indication of the live data size, together with the
+    // intended space-time trade-off, as expressed by SetTargetHeapUtilization.
+    // For a fixed target utilization, the amount of GC effort per native
+    // allocated byte remains roughly constant as the Java heap size changes.
+    // But we previously triggered on max_free_ native allocation which is often much
+    // smaller. To avoid unexpected growth, we partially keep that limit in place for now.
+    // TODO: Consider HeapGrowthMultiplier(). Maybe.
+    return std::min(target_footprint_.load(std::memory_order_relaxed), 2 * max_free_);
   }
 
   ALWAYS_INLINE void IncrementNumberOfBytesFreedRevoke(size_t freed_bytes_revoke);
@@ -1105,6 +1152,11 @@
   // Remove a vlog code from heap-inl.h which is transitively included in half the world.
   static void VlogHeapGrowth(size_t max_allowed_footprint, size_t new_footprint, size_t alloc_size);
 
+  // Return our best approximation of the number of bytes of native memory that
+  // are currently in use, and could possibly be reclaimed as an indirect result
+  // of a garbage collection.
+  size_t GetNativeBytes();
+
   // All-known continuous spaces, where objects lie within fixed bounds.
   std::vector<space::ContinuousSpace*> continuous_spaces_ GUARDED_BY(Locks::mutator_lock_);
 
@@ -1175,15 +1227,18 @@
   // Starting time of the new process; meant to be used for measuring total process CPU time.
   uint64_t process_cpu_start_time_ns_;
 
-  // Last time GC started; meant to be used to measure the duration between two GCs.
-  uint64_t last_process_cpu_time_ns_;
+  // Last time (before and after) GC started; meant to be used to measure the
+  // duration between two GCs.
+  uint64_t pre_gc_last_process_cpu_time_ns_;
+  uint64_t post_gc_last_process_cpu_time_ns_;
 
-  // allocated_bytes * (current_process_cpu_time - last_process_cpu_time)
-  double weighted_allocated_bytes_;
+  // allocated_bytes * (current_process_cpu_time - [pre|post]_gc_last_process_cpu_time)
+  double pre_gc_weighted_allocated_bytes_;
+  double post_gc_weighted_allocated_bytes_;
 
-  // If we ignore the max footprint it lets the heap grow until it hits the heap capacity, this is
-  // useful for benchmarking since it reduces time spent in GC to a low %.
-  const bool ignore_max_footprint_;
+  // If we ignore the target footprint it lets the heap grow until it hits the heap capacity, this
+  // is useful for benchmarking since it reduces time spent in GC to a low %.
+  const bool ignore_target_footprint_;
 
   // Lock which guards zygote space creation.
   Mutex zygote_creation_lock_;
@@ -1232,14 +1287,18 @@
 
   // The size the heap is limited to. This is initially smaller than capacity, but for largeHeap
   // programs it is "cleared" making it the same as capacity.
+  // Only weakly enforced for simultaneous allocations.
   size_t growth_limit_;
 
-  // When the number of bytes allocated exceeds the footprint TryAllocate returns null indicating
-  // a GC should be triggered.
-  size_t max_allowed_footprint_;
+  // Target size (as in maximum allocatable bytes) for the heap. Weakly enforced as a limit for
+  // non-concurrent GC. Used as a guideline for computing concurrent_start_bytes_ in the
+  // concurrent GC case.
+  Atomic<size_t> target_footprint_;
 
   // When num_bytes_allocated_ exceeds this amount then a concurrent GC should be requested so that
   // it completes ahead of an allocation failing.
+  // A multiple of this is also used to determine when to trigger a GC in response to native
+  // allocation.
   size_t concurrent_start_bytes_;
 
   // Since the heap was created, how many bytes have been freed.
@@ -1252,19 +1311,18 @@
   // TLABS in their entirety, even if they have not yet been parceled out.
   Atomic<size_t> num_bytes_allocated_;
 
-  // Number of registered native bytes allocated since the last time GC was
-  // triggered. Adjusted after each RegisterNativeAllocation and
-  // RegisterNativeFree. Used to determine when to trigger GC for native
-  // allocations.
-  // See the REDESIGN section of go/understanding-register-native-allocation.
-  Atomic<size_t> new_native_bytes_allocated_;
+  // Number of registered native bytes allocated. Adjusted after each RegisterNativeAllocation and
+  // RegisterNativeFree. Used to  help determine when to trigger GC for native allocations. Should
+  // not include bytes allocated through the system malloc, since those are implicitly included.
+  Atomic<size_t> native_bytes_registered_;
 
-  // Number of registered native bytes allocated prior to the last time GC was
-  // triggered, for debugging purposes. The current number of registered
-  // native bytes is determined by taking the sum of
-  // old_native_bytes_allocated_ and new_native_bytes_allocated_.
+  // Approximately the smallest value of GetNativeBytes() we've seen since the last GC.
   Atomic<size_t> old_native_bytes_allocated_;
 
+  // Total number of native objects of which we were notified since the beginning of time, mod 2^32.
+  // Allows us to check for GC only roughly every kNotifyNativeInterval allocations.
+  Atomic<uint32_t> native_objects_notified_;
+
   // Number of bytes freed by thread local buffer revokes. This will
   // cancel out the ahead-of-time bulk counting of bytes allocated in
   // rosalloc thread-local buffers.  It is temporarily accumulated
@@ -1349,10 +1407,10 @@
 
   // Minimum free guarantees that you always have at least min_free_ free bytes after growing for
   // utilization, regardless of target utilization ratio.
-  size_t min_free_;
+  const size_t min_free_;
 
   // The ideal maximum free size, when we grow the heap for utilization.
-  size_t max_free_;
+  const size_t max_free_;
 
   // Target ideal heap utilization ratio.
   double target_utilization_;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 4c2074d..3f1d879 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -28,6 +28,7 @@
 
 #include "art_field-inl.h"
 #include "art_method-inl.h"
+#include "base/array_ref.h"
 #include "base/bit_memory_region.h"
 #include "base/callee_save_type.h"
 #include "base/enums.h"
@@ -59,6 +60,7 @@
 namespace gc {
 namespace space {
 
+using android::base::StringAppendF;
 using android::base::StringPrintf;
 
 Atomic<uint32_t> ImageSpace::bitmap_index_(0);
@@ -654,6 +656,22 @@
   const CodeVisitor code_visitor_;
 };
 
+template <typename ReferenceVisitor>
+class ImageSpace::ClassTableVisitor final {
+ public:
+  explicit ClassTableVisitor(const ReferenceVisitor& reference_visitor)
+      : reference_visitor_(reference_visitor) {}
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(root->AsMirrorPtr() != nullptr);
+    root->Assign(reference_visitor_(root->AsMirrorPtr()));
+  }
+
+ private:
+  ReferenceVisitor reference_visitor_;
+};
+
 // Helper class encapsulating loading, so we can access private ImageSpace members (this is a
 // nested class), but not declare functions in the header.
 class ImageSpace::Loader {
@@ -701,11 +719,22 @@
 
       TimingLogger::ScopedTiming timing("RelocateImage", &logger);
       ImageHeader* image_header = reinterpret_cast<ImageHeader*>(space->GetMemMap()->Begin());
-      if (!RelocateInPlace(*image_header,
-                           space->GetMemMap()->Begin(),
-                           space->GetLiveBitmap(),
-                           oat_file,
-                           error_msg)) {
+      const PointerSize pointer_size = image_header->GetPointerSize();
+      bool result;
+      if (pointer_size == PointerSize::k64) {
+        result = RelocateInPlace<PointerSize::k64>(*image_header,
+                                                   space->GetMemMap()->Begin(),
+                                                   space->GetLiveBitmap(),
+                                                   oat_file,
+                                                   error_msg);
+      } else {
+        result = RelocateInPlace<PointerSize::k32>(*image_header,
+                                                   space->GetMemMap()->Begin(),
+                                                   space->GetLiveBitmap(),
+                                                   oat_file,
+                                                   error_msg);
+      }
+      if (!result) {
         return nullptr;
       }
       Runtime* runtime = Runtime::Current();
@@ -1089,11 +1118,8 @@
   class FixupObjectVisitor : public FixupVisitor {
    public:
     template<typename... Args>
-    explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited,
-                                const PointerSize pointer_size,
-                                Args... args)
+    explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited, Args... args)
         : FixupVisitor(args...),
-          pointer_size_(pointer_size),
           visited_(visited) {}
 
     // Fix up separately since we also need to fix up method entrypoints.
@@ -1105,39 +1131,14 @@
 
     ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj,
                                   MemberOffset offset,
-
                                   bool is_static ATTRIBUTE_UNUSED) const
         NO_THREAD_SAFETY_ANALYSIS {
-      // There could be overlap between ranges, we must avoid visiting the same reference twice.
-      // Avoid the class field since we already fixed it up in FixupClassVisitor.
-      if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) {
-        // Space is not yet added to the heap, don't do a read barrier.
-        mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
-            offset);
-        // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the
-        // image.
-        obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref));
-      }
-    }
-
-    // Visit a pointer array and forward corresponding native data. Ignores pointer arrays in the
-    // boot image. Uses the bitmap to ensure the same array is not visited multiple times.
-    template <typename Visitor>
-    void UpdatePointerArrayContents(mirror::PointerArray* array, const Visitor& visitor) const
-        NO_THREAD_SAFETY_ANALYSIS {
-      DCHECK(array != nullptr);
-      DCHECK(visitor.IsInAppImage(array));
-      // The bit for the array contents is different than the bit for the array. Since we may have
-      // already visited the array as a long / int array from walking the bitmap without knowing it
-      // was a pointer array.
-      static_assert(kObjectAlignment == 8u, "array bit may be in another object");
-      mirror::Object* const contents_bit = reinterpret_cast<mirror::Object*>(
-          reinterpret_cast<uintptr_t>(array) + kObjectAlignment);
-      // If the bit is not set then the contents have not yet been updated.
-      if (!visited_->Test(contents_bit)) {
-        array->Fixup<kVerifyNone>(array, pointer_size_, visitor);
-        visited_->Set(contents_bit);
-      }
+      // Space is not yet added to the heap, don't do a read barrier.
+      mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
+          offset);
+      // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the
+      // image.
+      obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref));
     }
 
     // java.lang.ref.Reference visitor.
@@ -1152,81 +1153,16 @@
 
     void operator()(mirror::Object* obj) const
         NO_THREAD_SAFETY_ANALYSIS {
-      if (visited_->Test(obj)) {
-        // Already visited.
-        return;
-      }
-      visited_->Set(obj);
-
-      // Handle class specially first since we need it to be updated to properly visit the rest of
-      // the instance fields.
-      {
-        mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
-        DCHECK(klass != nullptr) << "Null class in image";
-        // No AsClass since our fields aren't quite fixed up yet.
-        mirror::Class* new_klass = down_cast<mirror::Class*>(ForwardObject(klass));
-        if (klass != new_klass) {
-          obj->SetClass<kVerifyNone>(new_klass);
-        }
-        if (new_klass != klass && IsInAppImage(new_klass)) {
-          // Make sure the klass contents are fixed up since we depend on it to walk the fields.
-          operator()(new_klass);
-        }
-      }
-
-      if (obj->IsClass()) {
-        mirror::Class* klass = obj->AsClass<kVerifyNone>();
-        // Fixup super class before visiting instance fields which require
-        // information from their super class to calculate offsets.
-        mirror::Class* super_class =
-            klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>().Ptr();
-        if (super_class != nullptr) {
-          mirror::Class* new_super_class = down_cast<mirror::Class*>(ForwardObject(super_class));
-          if (new_super_class != super_class && IsInAppImage(new_super_class)) {
-            // Recursively fix all dependencies.
-            operator()(new_super_class);
-          }
-        }
-      }
-
-      obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>(
-          *this,
-          *this);
-      // Note that this code relies on no circular dependencies.
-      // We want to use our own class loader and not the one in the image.
-      if (obj->IsClass<kVerifyNone>()) {
-        mirror::Class* as_klass = obj->AsClass<kVerifyNone>();
-        FixupObjectAdapter visitor(boot_image_, app_image_, app_oat_);
-        as_klass->FixupNativePointers<kVerifyNone>(as_klass, pointer_size_, visitor);
-        // Deal with the pointer arrays. Use the helper function since multiple classes can reference
-        // the same arrays.
-        mirror::PointerArray* const vtable = as_klass->GetVTable<kVerifyNone, kWithoutReadBarrier>();
-        if (vtable != nullptr && IsInAppImage(vtable)) {
-          operator()(vtable);
-          UpdatePointerArrayContents(vtable, visitor);
-        }
-        mirror::IfTable* iftable = as_klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>();
-        // Ensure iftable arrays are fixed up since we need GetMethodArray to return the valid
-        // contents.
-        if (IsInAppImage(iftable)) {
-          operator()(iftable);
-          for (int32_t i = 0, count = iftable->Count(); i < count; ++i) {
-            if (iftable->GetMethodArrayCount<kVerifyNone, kWithoutReadBarrier>(i) > 0) {
-              mirror::PointerArray* methods =
-                  iftable->GetMethodArray<kVerifyNone, kWithoutReadBarrier>(i);
-              if (visitor.IsInAppImage(methods)) {
-                operator()(methods);
-                DCHECK(methods != nullptr);
-                UpdatePointerArrayContents(methods, visitor);
-              }
-            }
-          }
-        }
+      if (!visited_->Set(obj)) {
+        // Not already visited.
+        obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>(
+            *this,
+            *this);
+        CHECK(!obj->IsClass());
       }
     }
 
    private:
-    const PointerSize pointer_size_;
     gc::accounting::ContinuousSpaceBitmap* const visited_;
   };
 
@@ -1306,6 +1242,7 @@
   // Relocate an image space mapped at target_base which possibly used to be at a different base
   // address. In place means modifying a single ImageSpace in place rather than relocating from
   // one ImageSpace to another.
+  template <PointerSize kPointerSize>
   static bool RelocateInPlace(ImageHeader& image_header,
                               uint8_t* target_base,
                               accounting::ContinuousSpaceBitmap* bitmap,
@@ -1317,7 +1254,6 @@
     uint32_t boot_image_end = 0;
     uint32_t boot_oat_begin = 0;
     uint32_t boot_oat_end = 0;
-    const PointerSize pointer_size = image_header.GetPointerSize();
     gc::Heap* const heap = Runtime::Current()->GetHeap();
     heap->GetBootImagesSize(&boot_image_begin, &boot_image_end, &boot_oat_begin, &boot_oat_end);
     if (boot_image_begin == boot_image_end) {
@@ -1359,11 +1295,8 @@
       return true;
     }
     ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
-    // Need to update the image to be at the target base.
-    const ImageSection& objects_section = image_header.GetObjectsSection();
-    uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset());
-    uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End());
     FixupObjectAdapter fixup_adapter(boot_image, app_image, app_oat);
+    PatchObjectVisitor<kPointerSize, FixupObjectAdapter> patch_object_visitor(fixup_adapter);
     if (fixup_image) {
       // Two pass approach, fix up all classes first, then fix up non class-objects.
       // The visited bitmap is used to ensure that pointer arrays are not forwarded twice.
@@ -1371,16 +1304,64 @@
           gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap",
                                                         target_base,
                                                         image_header.GetImageSize()));
-      FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(),
-                                              pointer_size,
-                                              boot_image,
-                                              app_image,
-                                              app_oat);
-      TimingLogger::ScopedTiming timing("Fixup classes", &logger);
-      // Fixup objects may read fields in the boot image, use the mutator lock here for sanity. Though
-      // its probably not required.
+      FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(), boot_image, app_image, app_oat);
+      {
+        TimingLogger::ScopedTiming timing("Fixup classes", &logger);
+        const auto& class_table_section = image_header.GetClassTableSection();
+        if (class_table_section.Size() > 0u) {
+          ScopedObjectAccess soa(Thread::Current());
+          ClassTableVisitor class_table_visitor(fixup_adapter);
+          size_t read_count = 0u;
+          const uint8_t* data = target_base + class_table_section.Offset();
+          // We avoid making a copy of the data since we want modifications to be propagated to the
+          // memory map.
+          ClassTable::ClassSet temp_set(data, /*make_copy_of_data=*/ false, &read_count);
+          for (ClassTable::TableSlot& slot : temp_set) {
+            slot.VisitRoot(class_table_visitor);
+            mirror::Class* klass = slot.Read<kWithoutReadBarrier>();
+            if (!fixup_adapter.IsInAppImage(klass)) {
+              continue;
+            }
+            const bool already_marked = visited_bitmap->Set(klass);
+            CHECK(!already_marked) << "App image class already visited";
+            patch_object_visitor.VisitClass(klass);
+            // Then patch the non-embedded vtable and iftable.
+            mirror::PointerArray* vtable = klass->GetVTable<kVerifyNone, kWithoutReadBarrier>();
+            if (vtable != nullptr &&
+                fixup_object_visitor.IsInAppImage(vtable) &&
+                !visited_bitmap->Set(vtable)) {
+              patch_object_visitor.VisitPointerArray(vtable);
+            }
+            auto* iftable = klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>();
+            if (iftable != nullptr && fixup_object_visitor.IsInAppImage(iftable)) {
+              // Avoid processing the fields of iftable since we will process them later anyways
+              // below.
+              int32_t ifcount = klass->GetIfTableCount<kVerifyNone>();
+              for (int32_t i = 0; i != ifcount; ++i) {
+                mirror::PointerArray* unpatched_ifarray =
+                    iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i);
+                if (unpatched_ifarray != nullptr) {
+                  // The iftable has not been patched, so we need to explicitly adjust the pointer.
+                  mirror::PointerArray* ifarray = fixup_adapter(unpatched_ifarray);
+                  if (fixup_object_visitor.IsInAppImage(ifarray) &&
+                      !visited_bitmap->Set(ifarray)) {
+                    patch_object_visitor.VisitPointerArray(ifarray);
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+
+      // Fixup objects may read fields in the boot image, use the mutator lock here for sanity.
+      // Though its probably not required.
+      TimingLogger::ScopedTiming timing("Fixup cobjects", &logger);
       ScopedObjectAccess soa(Thread::Current());
-      timing.NewTiming("Fixup objects");
+      // Need to update the image to be at the target base.
+      const ImageSection& objects_section = image_header.GetObjectsSection();
+      uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset());
+      uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End());
       bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor);
       // Fixup image roots.
       CHECK(app_image.InSource(reinterpret_cast<uintptr_t>(
@@ -1392,96 +1373,19 @@
           AsObjectArray<mirror::DexCache, kVerifyNone>();
       for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) {
         mirror::DexCache* dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i);
-        // Fix up dex cache pointers.
-        mirror::StringDexCacheType* strings = dex_cache->GetStrings();
-        if (strings != nullptr) {
-          mirror::StringDexCacheType* new_strings = fixup_adapter.ForwardObject(strings);
-          if (strings != new_strings) {
-            dex_cache->SetStrings(new_strings);
-          }
-          dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter);
-        }
-        mirror::TypeDexCacheType* types = dex_cache->GetResolvedTypes();
-        if (types != nullptr) {
-          mirror::TypeDexCacheType* new_types = fixup_adapter.ForwardObject(types);
-          if (types != new_types) {
-            dex_cache->SetResolvedTypes(new_types);
-          }
-          dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter);
-        }
-        mirror::MethodDexCacheType* methods = dex_cache->GetResolvedMethods();
-        if (methods != nullptr) {
-          mirror::MethodDexCacheType* new_methods = fixup_adapter.ForwardObject(methods);
-          if (methods != new_methods) {
-            dex_cache->SetResolvedMethods(new_methods);
-          }
-          for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) {
-            auto pair = mirror::DexCache::GetNativePairPtrSize(new_methods, j, pointer_size);
-            ArtMethod* orig = pair.object;
-            ArtMethod* copy = fixup_adapter.ForwardObject(orig);
-            if (orig != copy) {
-              pair.object = copy;
-              mirror::DexCache::SetNativePairPtrSize(new_methods, j, pair, pointer_size);
-            }
-          }
-        }
-        mirror::FieldDexCacheType* fields = dex_cache->GetResolvedFields();
-        if (fields != nullptr) {
-          mirror::FieldDexCacheType* new_fields = fixup_adapter.ForwardObject(fields);
-          if (fields != new_fields) {
-            dex_cache->SetResolvedFields(new_fields);
-          }
-          for (size_t j = 0, num = dex_cache->NumResolvedFields(); j != num; ++j) {
-            mirror::FieldDexCachePair orig =
-                mirror::DexCache::GetNativePairPtrSize(new_fields, j, pointer_size);
-            mirror::FieldDexCachePair copy(fixup_adapter.ForwardObject(orig.object), orig.index);
-            if (orig.object != copy.object) {
-              mirror::DexCache::SetNativePairPtrSize(new_fields, j, copy, pointer_size);
-            }
-          }
-        }
-
-        mirror::MethodTypeDexCacheType* method_types = dex_cache->GetResolvedMethodTypes();
-        if (method_types != nullptr) {
-          mirror::MethodTypeDexCacheType* new_method_types =
-              fixup_adapter.ForwardObject(method_types);
-          if (method_types != new_method_types) {
-            dex_cache->SetResolvedMethodTypes(new_method_types);
-          }
-          dex_cache->FixupResolvedMethodTypes<kWithoutReadBarrier>(new_method_types, fixup_adapter);
-        }
-        GcRoot<mirror::CallSite>* call_sites = dex_cache->GetResolvedCallSites();
-        if (call_sites != nullptr) {
-          GcRoot<mirror::CallSite>* new_call_sites = fixup_adapter.ForwardObject(call_sites);
-          if (call_sites != new_call_sites) {
-            dex_cache->SetResolvedCallSites(new_call_sites);
-          }
-          dex_cache->FixupResolvedCallSites<kWithoutReadBarrier>(new_call_sites, fixup_adapter);
-        }
-
-        GcRoot<mirror::String>* preresolved_strings = dex_cache->GetPreResolvedStrings();
-        if (preresolved_strings != nullptr) {
-          GcRoot<mirror::String>* new_array = fixup_adapter.ForwardObject(preresolved_strings);
-          if (preresolved_strings != new_array) {
-            dex_cache->SetPreResolvedStrings(new_array);
-          }
-          const size_t num_preresolved_strings = dex_cache->NumPreResolvedStrings();
-          for (size_t j = 0; j < num_preresolved_strings; ++j) {
-            new_array[j] = GcRoot<mirror::String>(
-                fixup_adapter(new_array[j].Read<kWithoutReadBarrier>()));
-          }
-        }
+        CHECK(dex_cache != nullptr);
+        patch_object_visitor.VisitDexCacheArrays(dex_cache);
       }
     }
     {
       // Only touches objects in the app image, no need for mutator lock.
       TimingLogger::ScopedTiming timing("Fixup methods", &logger);
       FixupArtMethodVisitor method_visitor(fixup_image,
-                                           pointer_size,
+                                           kPointerSize,
                                            boot_image,
                                            app_image,
                                            app_oat);
-      image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size);
+      image_header.VisitPackedArtMethods(&method_visitor, target_base, kPointerSize);
     }
     if (fixup_image) {
       {
@@ -1492,26 +1396,14 @@
       }
       {
         TimingLogger::ScopedTiming timing("Fixup imt", &logger);
-        image_header.VisitPackedImTables(fixup_adapter, target_base, pointer_size);
+        image_header.VisitPackedImTables(fixup_adapter, target_base, kPointerSize);
       }
       {
         TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger);
-        image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size);
+        image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, kPointerSize);
       }
       // In the app image case, the image methods are actually in the boot image.
       image_header.RelocateImageMethods(boot_image.Delta());
-      const auto& class_table_section = image_header.GetClassTableSection();
-      if (class_table_section.Size() > 0u) {
-        // Note that we require that ReadFromMemory does not make an internal copy of the elements.
-        // This also relies on visit roots not doing any verification which could fail after we update
-        // the roots to be the image addresses.
-        ScopedObjectAccess soa(Thread::Current());
-        WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
-        ClassTable temp_table;
-        temp_table.ReadFromMemory(target_base + class_table_section.Offset());
-        FixupRootVisitor root_visitor(boot_image, app_image, app_oat);
-        temp_table.VisitRoots(root_visitor);
-      }
       // Fix up the intern table.
       const auto& intern_table_section = image_header.GetInternedStringsSection();
       if (intern_table_section.Size() > 0u) {
@@ -1654,8 +1546,10 @@
       *error_msg = StringPrintf("Cannot read header of %s", filename.c_str());
       return false;
     }
-    if (system_hdr.GetComponentCount() != boot_class_path_.size()) {
-      *error_msg = StringPrintf("Unexpected component count in %s, received %u, expected %zu",
+    if (system_hdr.GetComponentCount() == 0u ||
+        system_hdr.GetComponentCount() > boot_class_path_.size()) {
+      *error_msg = StringPrintf("Unexpected component count in %s, received %u, "
+                                    "expected non-zero and <= %zu",
                                 filename.c_str(),
                                 system_hdr.GetComponentCount(),
                                 boot_class_path_.size());
@@ -1672,10 +1566,12 @@
       return false;
     }
 
+    ArrayRef<const std::string> provided_locations(boot_class_path_locations_.data(),
+                                                   system_hdr.GetComponentCount());
     std::vector<std::string> locations =
-        ExpandMultiImageLocations(boot_class_path_locations_, image_location_);
+        ExpandMultiImageLocations(provided_locations, image_location_);
     std::vector<std::string> filenames =
-        ExpandMultiImageLocations(boot_class_path_locations_, filename);
+        ExpandMultiImageLocations(provided_locations, filename);
     DCHECK_EQ(locations.size(), filenames.size());
     std::vector<std::unique_ptr<ImageSpace>> spaces;
     spaces.reserve(locations.size());
@@ -1694,7 +1590,7 @@
     }
     for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
       std::string expected_boot_class_path =
-          (i == 0u) ? android::base::Join(boot_class_path_locations_, ':') : std::string();
+          (i == 0u) ? android::base::Join(provided_locations, ':') : std::string();
       if (!OpenOatFile(spaces[i].get(),
                        boot_class_path_[i],
                        expected_boot_class_path,
@@ -1766,22 +1662,6 @@
     BitMemoryRegion visited_objects_;
   };
 
-  template <typename ReferenceVisitor>
-  class ClassTableVisitor final {
-   public:
-    explicit ClassTableVisitor(const ReferenceVisitor& reference_visitor)
-        : reference_visitor_(reference_visitor) {}
-
-    void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
-        REQUIRES_SHARED(Locks::mutator_lock_) {
-      DCHECK(root->AsMirrorPtr() != nullptr);
-      root->Assign(reference_visitor_(root->AsMirrorPtr()));
-    }
-
-   private:
-    ReferenceVisitor reference_visitor_;
-  };
-
   template <PointerSize kPointerSize>
   static void DoRelocateSpaces(const std::vector<std::unique_ptr<ImageSpace>>& spaces,
                                uint32_t diff) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -2444,9 +2324,113 @@
   return true;
 }
 
+std::string ImageSpace::GetBootClassPathChecksums(const std::vector<std::string>& boot_class_path,
+                                                  const std::string& image_location,
+                                                  InstructionSet image_isa,
+                                                  /*out*/std::string* error_msg) {
+  std::string system_filename;
+  bool has_system = false;
+  std::string cache_filename;
+  bool has_cache = false;
+  bool dalvik_cache_exists = false;
+  bool is_global_cache = false;
+  if (!FindImageFilename(image_location.c_str(),
+                         image_isa,
+                         &system_filename,
+                         &has_system,
+                         &cache_filename,
+                         &dalvik_cache_exists,
+                         &has_cache,
+                         &is_global_cache)) {
+    *error_msg = StringPrintf("Unable to find image file for %s and %s",
+                              image_location.c_str(),
+                              GetInstructionSetString(image_isa));
+    return std::string();
+  }
+
+  DCHECK(has_system || has_cache);
+  const std::string& filename = has_system ? system_filename : cache_filename;
+  std::unique_ptr<ImageHeader> header = ReadSpecificImageHeader(filename.c_str(), error_msg);
+  if (header == nullptr) {
+    return std::string();
+  }
+  if (header->GetComponentCount() == 0u || header->GetComponentCount() > boot_class_path.size()) {
+    *error_msg = StringPrintf("Unexpected component count in %s, received %u, "
+                                  "expected non-zero and <= %zu",
+                              filename.c_str(),
+                              header->GetComponentCount(),
+                              boot_class_path.size());
+    return std::string();
+  }
+
+  std::string boot_image_checksum =
+      StringPrintf("i;%d/%08x", header->GetComponentCount(), header->GetImageChecksum());
+  ArrayRef<const std::string> boot_class_path_tail =
+      ArrayRef<const std::string>(boot_class_path).SubArray(header->GetComponentCount());
+  for (const std::string& bcp_filename : boot_class_path_tail) {
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    const ArtDexFileLoader dex_file_loader;
+    if (!dex_file_loader.Open(bcp_filename.c_str(),
+                              bcp_filename,  // The location does not matter here.
+                              /*verify=*/ false,
+                              /*verify_checksum=*/ false,
+                              error_msg,
+                              &dex_files)) {
+      return std::string();
+    }
+    DCHECK(!dex_files.empty());
+    StringAppendF(&boot_image_checksum, ":d");
+    for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
+      StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum());
+    }
+  }
+  return boot_image_checksum;
+}
+
+std::string ImageSpace::GetBootClassPathChecksums(
+    const std::vector<ImageSpace*>& image_spaces,
+    const std::vector<const DexFile*>& boot_class_path) {
+  DCHECK(!image_spaces.empty());
+  const ImageHeader& primary_header = image_spaces.front()->GetImageHeader();
+  uint32_t component_count = primary_header.GetComponentCount();
+  DCHECK_EQ(component_count, image_spaces.size());
+  std::string boot_image_checksum =
+      StringPrintf("i;%d/%08x", component_count, primary_header.GetImageChecksum());
+  size_t pos = 0u;
+  for (const ImageSpace* space : image_spaces) {
+    size_t num_dex_files = space->oat_file_non_owned_->GetOatDexFiles().size();
+    if (kIsDebugBuild) {
+      CHECK_NE(num_dex_files, 0u);
+      CHECK_LE(space->oat_file_non_owned_->GetOatDexFiles().size(), boot_class_path.size() - pos);
+      for (size_t i = 0; i != num_dex_files; ++i) {
+        CHECK_EQ(space->oat_file_non_owned_->GetOatDexFiles()[i]->GetDexFileLocation(),
+                 boot_class_path[pos + i]->GetLocation());
+      }
+    }
+    pos += num_dex_files;
+  }
+  ArrayRef<const DexFile* const> boot_class_path_tail =
+      ArrayRef<const DexFile* const>(boot_class_path).SubArray(pos);
+  DCHECK(boot_class_path_tail.empty() ||
+         !DexFileLoader::IsMultiDexLocation(boot_class_path_tail.front()->GetLocation().c_str()));
+  for (const DexFile* dex_file : boot_class_path_tail) {
+    if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) {
+      StringAppendF(&boot_image_checksum, ":d");
+    }
+    StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum());
+  }
+  return boot_image_checksum;
+}
+
 std::vector<std::string> ImageSpace::ExpandMultiImageLocations(
     const std::vector<std::string>& dex_locations,
     const std::string& image_location) {
+  return ExpandMultiImageLocations(ArrayRef<const std::string>(dex_locations), image_location);
+}
+
+std::vector<std::string> ImageSpace::ExpandMultiImageLocations(
+    ArrayRef<const std::string> dex_locations,
+    const std::string& image_location) {
   DCHECK(!dex_locations.empty());
 
   // Find the path.
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index dbc12d1..14e364a 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -24,6 +24,8 @@
 
 namespace art {
 
+template <typename T> class ArrayRef;
+class DexFile;
 class OatFile;
 
 namespace gc {
@@ -124,6 +126,19 @@
                                 bool* has_data,
                                 bool *is_global_cache);
 
+  // Returns the checksums for the boot image and extra boot class path dex files,
+  // based on the boot class path, image location and ISA (may differ from the ISA of an
+  // initialized Runtime). The boot image and dex files do not need to be loaded in memory.
+  static std::string GetBootClassPathChecksums(const std::vector<std::string>& boot_class_path,
+                                               const std::string& image_location,
+                                               InstructionSet image_isa,
+                                               /*out*/std::string* error_msg);
+
+  // Returns the checksums for the boot image and extra boot class path dex files,
+  // based on the boot image and boot class path dex files loaded in memory.
+  static std::string GetBootClassPathChecksums(const std::vector<ImageSpace*>& image_spaces,
+                                               const std::vector<const DexFile*>& boot_class_path);
+
   // Expand a single image location to multi-image locations based on the dex locations.
   static std::vector<std::string> ExpandMultiImageLocations(
       const std::vector<std::string>& dex_locations,
@@ -188,7 +203,14 @@
   friend class Space;
 
  private:
+  // Internal overload that takes ArrayRef<> instead of vector<>.
+  static std::vector<std::string> ExpandMultiImageLocations(
+      ArrayRef<const std::string> dex_locations,
+      const std::string& image_location);
+
   class BootImageLoader;
+  template <typename ReferenceVisitor>
+  class ClassTableVisitor;
   class Loader;
   template <typename PatchObjectVisitor>
   class PatchArtFieldVisitor;
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index e43d771..43f32b9 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -58,7 +58,7 @@
 void* Jit::jit_compiler_handle_ = nullptr;
 void* (*Jit::jit_load_)(void) = nullptr;
 void (*Jit::jit_unload_)(void*) = nullptr;
-bool (*Jit::jit_compile_method_)(void*, ArtMethod*, Thread*, bool) = nullptr;
+bool (*Jit::jit_compile_method_)(void*, ArtMethod*, Thread*, bool, bool) = nullptr;
 void (*Jit::jit_types_loaded_)(void*, mirror::Class**, size_t count) = nullptr;
 bool (*Jit::jit_generate_debug_info_)(void*) = nullptr;
 void (*Jit::jit_update_options_)(void*) = nullptr;
@@ -242,7 +242,7 @@
   return true;
 }
 
-bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) {
+bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool baseline, bool osr) {
   DCHECK(Runtime::Current()->UseJitCompilation());
   DCHECK(!method->IsRuntimeMethod());
 
@@ -272,7 +272,7 @@
   VLOG(jit) << "Compiling method "
             << ArtMethod::PrettyMethod(method_to_compile)
             << " osr=" << std::boolalpha << osr;
-  bool success = jit_compile_method_(jit_compiler_handle_, method_to_compile, self, osr);
+  bool success = jit_compile_method_(jit_compiler_handle_, method_to_compile, self, baseline, osr);
   code_cache_->DoneCompiling(method_to_compile, self, osr);
   if (!success) {
     VLOG(jit) << "Failed to compile method "
@@ -549,6 +549,7 @@
   enum class TaskKind {
     kAllocateProfile,
     kCompile,
+    kCompileBaseline,
     kCompileOsr,
   };
 
@@ -568,10 +569,12 @@
     ScopedObjectAccess soa(self);
     switch (kind_) {
       case TaskKind::kCompile:
+      case TaskKind::kCompileBaseline:
       case TaskKind::kCompileOsr: {
         Runtime::Current()->GetJit()->CompileMethod(
             method_,
             self,
+            /* baseline= */ (kind_ == TaskKind::kCompileBaseline),
             /* osr= */ (kind_ == TaskKind::kCompileOsr));
         break;
       }
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 7ce5f07..485537c 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -161,7 +161,7 @@
   // Create JIT itself.
   static Jit* Create(JitCodeCache* code_cache, JitOptions* options);
 
-  bool CompileMethod(ArtMethod* method, Thread* self, bool osr)
+  bool CompileMethod(ArtMethod* method, Thread* self, bool baseline, bool osr)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   const JitCodeCache* GetCodeCache() const {
@@ -304,7 +304,7 @@
   static void* jit_compiler_handle_;
   static void* (*jit_load_)(void);
   static void (*jit_unload_)(void*);
-  static bool (*jit_compile_method_)(void*, ArtMethod*, Thread*, bool);
+  static bool (*jit_compile_method_)(void*, ArtMethod*, Thread*, bool, bool);
   static void (*jit_types_loaded_)(void*, mirror::Class**, size_t count);
   static void (*jit_update_options_)(void*);
   static bool (*jit_generate_debug_info_)(void*);
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 3e5003c..892d4cc 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -271,7 +271,7 @@
 #endif
 }
 
-static void VMRuntime_registerNativeAllocation(JNIEnv* env, jobject, jint bytes) {
+static void VMRuntime_registerNativeAllocationInternal(JNIEnv* env, jobject, jint bytes) {
   if (UNLIKELY(bytes < 0)) {
     ScopedObjectAccess soa(env);
     ThrowRuntimeException("allocation size negative %d", bytes);
@@ -280,11 +280,7 @@
   Runtime::Current()->GetHeap()->RegisterNativeAllocation(env, static_cast<size_t>(bytes));
 }
 
-static void VMRuntime_registerSensitiveThread(JNIEnv*, jobject) {
-  Runtime::Current()->RegisterSensitiveThread();
-}
-
-static void VMRuntime_registerNativeFree(JNIEnv* env, jobject, jint bytes) {
+static void VMRuntime_registerNativeFreeInternal(JNIEnv* env, jobject, jint bytes) {
   if (UNLIKELY(bytes < 0)) {
     ScopedObjectAccess soa(env);
     ThrowRuntimeException("allocation size negative %d", bytes);
@@ -293,6 +289,18 @@
   Runtime::Current()->GetHeap()->RegisterNativeFree(env, static_cast<size_t>(bytes));
 }
 
+static jint VMRuntime_getNotifyNativeInterval(JNIEnv*, jclass) {
+  return Runtime::Current()->GetHeap()->GetNotifyNativeInterval();
+}
+
+static void VMRuntime_notifyNativeAllocationsInternal(JNIEnv* env, jobject) {
+  Runtime::Current()->GetHeap()->NotifyNativeAllocations(env);
+}
+
+static void VMRuntime_registerSensitiveThread(JNIEnv*, jobject) {
+  Runtime::Current()->RegisterSensitiveThread();
+}
+
 static void VMRuntime_updateProcessState(JNIEnv*, jobject, jint process_state) {
   Runtime* runtime = Runtime::Current();
   runtime->UpdateProcessState(static_cast<ProcessState>(process_state));
@@ -710,9 +718,11 @@
   FAST_NATIVE_METHOD(VMRuntime, newUnpaddedArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
   NATIVE_METHOD(VMRuntime, properties, "()[Ljava/lang/String;"),
   NATIVE_METHOD(VMRuntime, setTargetSdkVersionNative, "(I)V"),
-  NATIVE_METHOD(VMRuntime, registerNativeAllocation, "(I)V"),
+  NATIVE_METHOD(VMRuntime, registerNativeAllocationInternal, "(I)V"),
+  NATIVE_METHOD(VMRuntime, registerNativeFreeInternal, "(I)V"),
+  NATIVE_METHOD(VMRuntime, getNotifyNativeInterval, "()I"),
+  NATIVE_METHOD(VMRuntime, notifyNativeAllocationsInternal, "()V"),
   NATIVE_METHOD(VMRuntime, registerSensitiveThread, "()V"),
-  NATIVE_METHOD(VMRuntime, registerNativeFree, "(I)V"),
   NATIVE_METHOD(VMRuntime, requestConcurrentGC, "()V"),
   NATIVE_METHOD(VMRuntime, requestHeapTrim, "()V"),
   NATIVE_METHOD(VMRuntime, runHeapTasks, "()V"),
diff --git a/runtime/oat.cc b/runtime/oat.cc
index e931b28..d7c968f 100644
--- a/runtime/oat.cc
+++ b/runtime/oat.cc
@@ -79,8 +79,7 @@
       quick_generic_jni_trampoline_offset_(0),
       quick_imt_conflict_trampoline_offset_(0),
       quick_resolution_trampoline_offset_(0),
-      quick_to_interpreter_bridge_offset_(0),
-      boot_image_checksum_(0) {
+      quick_to_interpreter_bridge_offset_(0) {
   // Don't want asserts in header as they would be checked in each file that includes it. But the
   // fields are private, so we check inside a method.
   static_assert(sizeof(magic_) == sizeof(kOatMagic),
@@ -316,16 +315,6 @@
   quick_to_interpreter_bridge_offset_ = offset;
 }
 
-uint32_t OatHeader::GetBootImageChecksum() const {
-  CHECK(IsValid());
-  return boot_image_checksum_;
-}
-
-void OatHeader::SetBootImageChecksum(uint32_t boot_image_checksum) {
-  CHECK(IsValid());
-  boot_image_checksum_ = boot_image_checksum;
-}
-
 uint32_t OatHeader::GetKeyValueStoreSize() const {
   CHECK(IsValid());
   return key_value_store_size_;
diff --git a/runtime/oat.h b/runtime/oat.h
index b09c81e..ded1489 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -31,8 +31,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Pass boot class path to LoadBootImage.
-  static constexpr uint8_t kOatVersion[] = { '1', '6', '5', '\0' };
+  // Last oat version changed reason: Partial boot image.
+  static constexpr uint8_t kOatVersion[] = { '1', '6', '6', '\0' };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
@@ -40,6 +40,7 @@
   static constexpr const char* kCompilerFilter = "compiler-filter";
   static constexpr const char* kClassPathKey = "classpath";
   static constexpr const char* kBootClassPathKey = "bootclasspath";
+  static constexpr const char* kBootClassPathChecksumsKey = "bootclasspath-checksums";
   static constexpr const char* kConcurrentCopying = "concurrent-copying";
   static constexpr const char* kCompilationReasonKey = "compilation-reason";
 
@@ -93,9 +94,6 @@
   InstructionSet GetInstructionSet() const;
   uint32_t GetInstructionSetFeaturesBitmap() const;
 
-  uint32_t GetBootImageChecksum() const;
-  void SetBootImageChecksum(uint32_t boot_image_checksum);
-
   uint32_t GetKeyValueStoreSize() const;
   const uint8_t* GetKeyValueStore() const;
   const char* GetStoreValueByKey(const char* key) const;
@@ -137,8 +135,6 @@
   uint32_t quick_resolution_trampoline_offset_;
   uint32_t quick_to_interpreter_bridge_offset_;
 
-  uint32_t boot_image_checksum_;
-
   uint32_t key_value_store_size_;
   uint8_t key_value_store_[0];  // note variable width data at end
 
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 6f32b98..8b81bb9 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -419,7 +419,7 @@
       // starts up.
       LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. "
         << "Allow oat file use. This is potentially dangerous.";
-    } else if (file.GetOatHeader().GetBootImageChecksum() != image_info->boot_image_checksum) {
+    } else if (!image_info->ValidateBootClassPathChecksums(file)) {
       VLOG(oat) << "Oat image checksum does not match image checksum.";
       return kOatBootImageOutOfDate;
     }
@@ -560,6 +560,13 @@
   return required_dex_checksums_found_ ? &cached_required_dex_checksums_ : nullptr;
 }
 
+bool OatFileAssistant::ImageInfo::ValidateBootClassPathChecksums(const OatFile& oat_file) const {
+  const char* oat_boot_class_path_checksums =
+      oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
+  return oat_boot_class_path_checksums != nullptr &&
+         oat_boot_class_path_checksums == boot_class_path_checksums;
+}
+
 std::unique_ptr<OatFileAssistant::ImageInfo>
 OatFileAssistant::ImageInfo::GetRuntimeImageInfo(InstructionSet isa, std::string* error_msg) {
   CHECK(error_msg != nullptr);
@@ -567,14 +574,11 @@
   Runtime* runtime = Runtime::Current();
   std::unique_ptr<ImageInfo> info(new ImageInfo());
   info->location = runtime->GetImageLocation();
-
-  std::unique_ptr<ImageHeader> image_header(
-      gc::space::ImageSpace::ReadImageHeader(info->location.c_str(), isa, error_msg));
-  if (image_header == nullptr) {
+  info->boot_class_path_checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
+      runtime->GetBootClassPath(), info->location, isa, error_msg);
+  if (info->boot_class_path_checksums.empty()) {
     return nullptr;
   }
-
-  info->boot_image_checksum = image_header->GetImageChecksum();
   return info;
 }
 
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 09c9d3b..def55b8 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -246,8 +246,10 @@
 
  private:
   struct ImageInfo {
-    uint32_t boot_image_checksum = 0;
+    bool ValidateBootClassPathChecksums(const OatFile& oat_file) const;
+
     std::string location;
+    std::string boot_class_path_checksums;
 
     static std::unique_ptr<ImageInfo> GetRuntimeImageInfo(InstructionSet isa,
                                                           std::string* error_msg);
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 69ef2fb..c5a8a23 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -320,7 +320,8 @@
   }
 
   if (dump_gc_performance_on_shutdown_) {
-    heap_->CalculateWeightedAllocatedBytes();
+    heap_->CalculatePreGcWeightedAllocatedBytes();
+    heap_->CalculatePostGcWeightedAllocatedBytes();
     uint64_t process_cpu_end_time = ProcessCpuNanoTime();
     ScopedLogSeverity sls(LogSeverity::INFO);
     // This can't be called from the Heap destructor below because it
@@ -335,9 +336,15 @@
         << " out of process CPU time " << PrettyDuration(process_cpu_time)
         << " (" << ratio << ")"
         << "\n";
-    double weighted_allocated_bytes = heap_->GetWeightedAllocatedBytes() / process_cpu_time;
-    LOG_STREAM(INFO) << "Weighted bytes allocated over CPU time: "
-        << " (" <<  PrettySize(weighted_allocated_bytes)  << ")"
+    double pre_gc_weighted_allocated_bytes =
+        heap_->GetPreGcWeightedAllocatedBytes() / process_cpu_time;
+    double post_gc_weighted_allocated_bytes =
+        heap_->GetPostGcWeightedAllocatedBytes() / process_cpu_time;
+
+    LOG_STREAM(INFO) << "Pre GC weighted bytes allocated over CPU time: "
+        << " (" <<  PrettySize(pre_gc_weighted_allocated_bytes)  << ")";
+    LOG_STREAM(INFO) << "Post GC weighted bytes allocated over CPU time: "
+        << " (" <<  PrettySize(post_gc_weighted_allocated_bytes)  << ")"
         << "\n";
   }
 
@@ -964,8 +971,8 @@
   VLOG(startup) << "Runtime::StartDaemonThreads exiting";
 }
 
-static size_t OpenDexFiles(const std::vector<std::string>& dex_filenames,
-                           const std::vector<std::string>& dex_locations,
+static size_t OpenDexFiles(ArrayRef<const std::string> dex_filenames,
+                           ArrayRef<const std::string> dex_locations,
                            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
   DCHECK(dex_files != nullptr) << "OpenDexFiles: out-param is nullptr";
   size_t failure_count = 0;
@@ -1422,6 +1429,21 @@
         GetInternTable()->AddImageStringsToTable(image_space, VoidFunctor());
       }
     }
+    if (heap_->GetBootImageSpaces().size() != GetBootClassPath().size()) {
+      // The boot image did not contain all boot class path components. Load the rest.
+      DCHECK_LT(heap_->GetBootImageSpaces().size(), GetBootClassPath().size());
+      size_t start = heap_->GetBootImageSpaces().size();
+      DCHECK_LT(start, GetBootClassPath().size());
+      std::vector<std::unique_ptr<const DexFile>> extra_boot_class_path;
+      if (runtime_options.Exists(Opt::BootClassPathDexList)) {
+        extra_boot_class_path.swap(*runtime_options.GetOrDefault(Opt::BootClassPathDexList));
+      } else {
+        OpenDexFiles(ArrayRef<const std::string>(GetBootClassPath()).SubArray(start),
+                     ArrayRef<const std::string>(GetBootClassPathLocations()).SubArray(start),
+                     &extra_boot_class_path);
+      }
+      class_linker_->AddExtraBootDexFiles(self, std::move(extra_boot_class_path));
+    }
     if (IsJavaDebuggable()) {
       // Now that we have loaded the boot image, deoptimize its methods if we are running
       // debuggable, as the code may have been compiled non-debuggable.
@@ -1432,7 +1454,9 @@
     if (runtime_options.Exists(Opt::BootClassPathDexList)) {
       boot_class_path.swap(*runtime_options.GetOrDefault(Opt::BootClassPathDexList));
     } else {
-      OpenDexFiles(GetBootClassPath(), GetBootClassPathLocations(), &boot_class_path);
+      OpenDexFiles(ArrayRef<const std::string>(GetBootClassPath()),
+                   ArrayRef<const std::string>(GetBootClassPathLocations()),
+                   &boot_class_path);
     }
     if (!class_linker_->InitWithoutImage(std::move(boot_class_path), &error_msg)) {
       LOG(ERROR) << "Could not initialize without image: " << error_msg;
diff --git a/test/175-alloc-big-bignums/expected.txt b/test/175-alloc-big-bignums/expected.txt
new file mode 100644
index 0000000..f75da10
--- /dev/null
+++ b/test/175-alloc-big-bignums/expected.txt
@@ -0,0 +1 @@
+Test complete
diff --git a/test/175-alloc-big-bignums/info.txt b/test/175-alloc-big-bignums/info.txt
new file mode 100644
index 0000000..8f6bcc3
--- /dev/null
+++ b/test/175-alloc-big-bignums/info.txt
@@ -0,0 +1,11 @@
+Allocate large numbers of huge BigIntegers in rapid succession. Most of the
+associated memory will be in the C++ heap. This makes sure that we trigger
+the garbage collector often enough to prevent us from running out of memory.
+
+The test allocates roughly 10GB of native memory, approximately 1MB of which
+will be live at any point. Basically all native memory deallocation is
+triggered by Java garbage collection.
+
+This test is a lot nastier than it looks. In particular, failure on target tends
+to exhaust device memory, and kill off all processes on the device, including the
+adb daemon :-( .
diff --git a/test/175-alloc-big-bignums/src/Main.java b/test/175-alloc-big-bignums/src/Main.java
new file mode 100644
index 0000000..5fbeb46
--- /dev/null
+++ b/test/175-alloc-big-bignums/src/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.math.BigInteger;
+
+// This is motivated by the assumption that BigInteger allocates malloc memory
+// underneath. That's true (in 2018) on Android.
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    final int nIters = 20_000;  // Presumed < 1_000_000.
+    final BigInteger big2_20 = BigInteger.valueOf(1024*1024); // 2^20
+    BigInteger huge = BigInteger.valueOf(1).shiftLeft(4_000_000);  // ~0.5MB
+    for (int i = 0; i < nIters; ++i) {  // 10 GB total
+      huge = huge.add(BigInteger.ONE);
+    }
+    if (huge.bitLength() != 4_000_001) {
+      System.out.println("Wrong answer length: " + huge.bitLength());
+    } else if (huge.mod(big2_20).compareTo(BigInteger.valueOf(nIters)) != 0) {
+      System.out.println("Wrong answer: ..." + huge.mod(big2_20));
+    } else {
+      System.out.println("Test complete");
+    }
+  }
+}
diff --git a/test/1934-jvmti-signal-thread/signal_threads.cc b/test/1934-jvmti-signal-thread/signal_threads.cc
index 726a7a86..dfb08c1 100644
--- a/test/1934-jvmti-signal-thread/signal_threads.cc
+++ b/test/1934-jvmti-signal-thread/signal_threads.cc
@@ -47,19 +47,19 @@
                             jvmti_env,
                             jvmti_env->Allocate(sizeof(NativeMonitor),
                                                 reinterpret_cast<unsigned char**>(&mon)))) {
-    return -1l;
+    return -1L;
   }
   if (JvmtiErrorToException(env,
                             jvmti_env,
                             jvmti_env->CreateRawMonitor("test-1934 start",
                                                         &mon->start_monitor))) {
-    return -1l;
+    return -1L;
   }
   if (JvmtiErrorToException(env,
                             jvmti_env,
                             jvmti_env->CreateRawMonitor("test-1934 continue",
                                                         &mon->continue_monitor))) {
-    return -1l;
+    return -1L;
   }
   mon->should_continue = false;
   mon->should_start = false;
@@ -92,7 +92,7 @@
   while (!mon->should_continue) {
     if (JvmtiErrorToException(env,
                               jvmti_env,
-                              jvmti_env->RawMonitorWait(mon->continue_monitor, -1l))) {
+                              jvmti_env->RawMonitorWait(mon->continue_monitor, -1L))) {
       JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
       return;
     }
@@ -112,7 +112,7 @@
   while (!mon->should_start) {
     if (JvmtiErrorToException(env,
                               jvmti_env,
-                              jvmti_env->RawMonitorWait(mon->start_monitor, -1l))) {
+                              jvmti_env->RawMonitorWait(mon->start_monitor, -1L))) {
       return;
     }
   }
diff --git a/test/566-polymorphic-inlining/polymorphic_inline.cc b/test/566-polymorphic-inlining/polymorphic_inline.cc
index 17ccd9a..00827cf 100644
--- a/test/566-polymorphic-inlining/polymorphic_inline.cc
+++ b/test/566-polymorphic-inlining/polymorphic_inline.cc
@@ -46,7 +46,7 @@
       usleep(1000);
     }
     // Will either ensure it's compiled or do the compilation itself.
-    jit->CompileMethod(method, soa.Self(), /* osr */ false);
+    jit->CompileMethod(method, soa.Self(), /*baseline=*/ false, /*osr=*/ false);
   }
 
   CodeInfo info(header);
diff --git a/test/570-checker-osr/osr.cc b/test/570-checker-osr/osr.cc
index b2b3634..dc0e94c 100644
--- a/test/570-checker-osr/osr.cc
+++ b/test/570-checker-osr/osr.cc
@@ -128,7 +128,7 @@
           // Sleep to yield to the compiler thread.
           usleep(1000);
           // Will either ensure it's compiled or do the compilation itself.
-          jit->CompileMethod(m, Thread::Current(), /* osr */ true);
+          jit->CompileMethod(m, Thread::Current(), /*baseline=*/ false, /*osr=*/ true);
         }
       });
 }
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 65127fc..55631a9 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -227,7 +227,7 @@
       // Make sure there is a profiling info, required by the compiler.
       ProfilingInfo::Create(self, method, /* retry_allocation */ true);
       // Will either ensure it's compiled or do the compilation itself.
-      jit->CompileMethod(method, self, /* osr */ false);
+      jit->CompileMethod(method, self, /*baseline=*/ false, /*osr=*/ false);
     }
   }
 }
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 4e5152b..4c31ee5 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -34,6 +34,8 @@
 HOST="n"
 BIONIC="n"
 CREATE_ANDROID_ROOT="n"
+USE_ZIPAPEX="n"
+ZIPAPEX_LOC=""
 INTERPRETER="n"
 JIT="n"
 INVOKE_WITH=""
@@ -220,6 +222,11 @@
         # host ones which are in a different location.
         CREATE_ANDROID_ROOT="y"
         shift
+    elif [ "x$1" = "x--runtime-zipapex" ]; then
+        shift
+        USE_ZIPAPEX="y"
+        ZIPAPEX_LOC="$1"
+        shift
     elif [ "x$1" = "x--no-prebuild" ]; then
         PREBUILD="n"
         shift
@@ -541,7 +548,10 @@
   exit
 fi
 
-bpath_modules="core-oj core-libart core-simple conscrypt okhttp bouncycastle"
+# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
+# because that's what we use for compiling the core.art image.
+# It may contain additional modules from TEST_CORE_JARS.
+bpath_modules="core-oj core-libart core-simple okhttp bouncycastle conscrypt"
 if [ "${HOST}" = "y" ]; then
     framework="${ANDROID_HOST_OUT}/framework"
     if [ "${ANDROID_HOST_OUT:0:${#ANDROID_BUILD_TOP}+1}" = "${ANDROID_BUILD_TOP}/" ]; then
@@ -703,6 +713,8 @@
     exit 1
 fi
 
+BIN_DIR=$ANDROID_ROOT/bin
+
 profman_cmdline="true"
 dex2oat_cmdline="true"
 vdex_cmdline="true"
@@ -712,6 +724,8 @@
 sync_cmdline="true"
 linkroot_cmdline="true"
 linkroot_overlay_cmdline="true"
+setupapex_cmdline="true"
+installapex_cmdline="true"
 
 linkdirs() {
   find "$1" -maxdepth 1 -mindepth 1 -type d | xargs -i ln -sf '{}' "$2"
@@ -726,10 +740,23 @@
   fi
 fi
 
+if [ "$USE_ZIPAPEX" = "y" ]; then
+  # TODO Currently this only works for linux_bionic zipapexes because those are
+  # stripped and so small enough that the ulimit doesn't kill us.
+  mkdir_locations="${mkdir_locations} $DEX_LOCATION/zipapex"
+  zip_options="-qq"
+  if [ "$DEV_MODE" = "y" ]; then
+    zip_options=""
+  fi
+  setupapex_cmdline="unzip -u ${zip_options} ${ZIPAPEX_LOC} apex_payload.zip -d ${DEX_LOCATION}"
+  installapex_cmdline="unzip -u ${zip_options} ${DEX_LOCATION}/apex_payload.zip -d ${DEX_LOCATION}/zipapex"
+  BIN_DIR=$DEX_LOCATION/zipapex/bin
+fi
+
 # PROFILE takes precedence over RANDOM_PROFILE, since PROFILE tests require a
 # specific profile to run properly.
 if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
-  profman_cmdline="${ANDROID_ROOT}/bin/profman  \
+  profman_cmdline="$BIN_DIR/profman  \
     --apk=$DEX_LOCATION/$TEST_NAME.jar \
     --dex-location=$DEX_LOCATION/$TEST_NAME.jar"
   if [ -f $DEX_LOCATION/$TEST_NAME-ex.jar ]; then
@@ -759,7 +786,7 @@
   if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
     dex2oat_binary=dex2oat
   fi
-  dex2oat_cmdline="$INVOKE_WITH $ANDROID_ROOT/bin/$dex2oat_binary \
+  dex2oat_cmdline="$INVOKE_WITH $BIN_DIR/$dex2oat_binary \
                       $COMPILE_FLAGS \
                       --boot-image=${BOOT_IMAGE} \
                       --dex-file=$DEX_LOCATION/$TEST_NAME.jar \
@@ -816,7 +843,7 @@
 # We set DumpNativeStackOnSigQuit to false to avoid stressing libunwind.
 # b/27185632
 # b/24664297
-dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ROOT/bin/$DALVIKVM \
+dalvikvm_cmdline="$INVOKE_WITH $GDB $BIN_DIR/$DALVIKVM \
                   $GDB_ARGS \
                   $FLAGS \
                   $DEX_VERIFY \
@@ -924,7 +951,7 @@
              rm -rf ${DEX_LOCATION}/dalvik-cache/ && \
              mkdir -p ${mkdir_locations} && \
              export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \
-             export PATH=$ANDROID_ROOT/bin:$PATH && \
+             export PATH=$BIN_DIR:$PATH && \
              $profman_cmdline && \
              $dex2oat_cmdline && \
              $dm_cmdline && \
@@ -966,8 +993,12 @@
     export ANDROID_ROOT="${ANDROID_ROOT}"
     export ANDROID_RUNTIME_ROOT="${ANDROID_RUNTIME_ROOT}"
     export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+    if [ "$USE_ZIPAPEX" = "y" ]; then
+      # Put the zipapex files in front of the ld-library-path
+      export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${LD_LIBRARY_PATH}"
+    fi
     export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
-    export PATH="$PATH:${ANDROID_ROOT}/bin"
+    export PATH="$PATH:$BIN_DIR"
 
     # Temporarily disable address space layout randomization (ASLR).
     # This is needed on the host so that the linker loads core.oat at the necessary address.
@@ -1006,7 +1037,7 @@
         echo EXPORT $var=${!var}
       done
       echo "$(declare -f linkdirs)"
-      echo "mkdir -p ${mkdir_locations} && $linkroot_cmdline && $linkroot_overlay_cmdline && $profman_cmdline && $dex2oat_cmdline && $dm_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline"
+      echo "mkdir -p ${mkdir_locations} && $setupapex_cmdline && $installapex_cmdline && $linkroot_cmdline && $linkroot_overlay_cmdline && $profman_cmdline && $dex2oat_cmdline && $dm_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline"
     fi
 
     cd $ANDROID_BUILD_TOP
@@ -1020,6 +1051,8 @@
     export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS
 
     mkdir -p ${mkdir_locations} || exit 1
+    $setupapex_cmdline || { echo "zipapex extraction failed." >&2 ; exit 2; }
+    $installapex_cmdline || { echo "zipapex install failed." >&2 ; exit 2; }
     $linkroot_cmdline || { echo "create symlink android-root failed." >&2 ; exit 2; }
     $linkroot_overlay_cmdline || { echo "overlay android-root failed." >&2 ; exit 2; }
     $profman_cmdline || { echo "Profman failed." >&2 ; exit 2; }
diff --git a/test/run-test b/test/run-test
index 2363152..83c726e 100755
--- a/test/run-test
+++ b/test/run-test
@@ -164,6 +164,15 @@
 image_suffix=""
 run_optimizing="false"
 
+# To cause tests to fail fast, limit the file sizes created by dx, dex2oat and
+# ART output to approximately 128MB. This should be more than sufficient
+# for any test while still catching cases of runaway output.
+# Set a hard limit to encourage ART developers to increase the ulimit here if
+# needed to support a test case rather than resetting the limit in the run
+# script for the particular test in question. Adjust this if needed for
+# particular configurations.
+file_ulimit=128000
+
 while true; do
     if [ "x$1" = "x--host" ]; then
         target_mode="no"
@@ -395,6 +404,18 @@
         DEX_LOCATION=$tmp_dir
         host_lib_root=$OUT_DIR/soong/host/linux_bionic-x86
         shift
+    elif [ "x$1" = "x--runtime-zipapex" ]; then
+        shift
+        # TODO Should we allow the java.library.path to search the zipapex too?
+        # Not needed at the moment and adding it will be complicated so for now
+        # we'll ignore this.
+        run_args="${run_args} --host --runtime-zipapex $1"
+        target_mode="no"
+        DEX_LOCATION=$tmp_dir
+        # apex_payload.zip is quite large we need a high enough ulimit to
+        # extract it. 512mb should be good enough.
+        file_ulimit=512000
+        shift
     elif [ "x$1" = "x--trace" ]; then
         trace="true"
         shift
@@ -722,6 +743,8 @@
              "files."
         echo "    --64                  Run the test in 64-bit mode"
         echo "    --bionic              Use the (host, 64-bit only) linux_bionic libc runtime"
+        echo "    --runtime-zipapex [file]"
+        echo "                          Use the given zipapex file to provide runtime binaries"
         echo "    --trace               Run with method tracing"
         echo "    --strace              Run with syscall tracing from strace."
         echo "    --stream              Run method tracing in streaming mode (requires --trace)"
@@ -823,13 +846,7 @@
 
 run_args="${run_args} --testlib ${testlib}"
 
-# To cause tests to fail fast, limit the file sizes created by dx, dex2oat and
-# ART output to approximately 128MB. This should be more than sufficient
-# for any test while still catching cases of runaway output.
-# Set a hard limit to encourage ART developers to increase the ulimit here if
-# needed to support a test case rather than resetting the limit in the run
-# script for the particular test in question.
-if ! ulimit -f 128000; then
+if ! ulimit -f ${file_ulimit}; then
   err_echo "ulimit file size setting failed"
 fi
 
diff --git a/test/testrunner/run_build_test_target.py b/test/testrunner/run_build_test_target.py
index b4a4ada..139d1af 100755
--- a/test/testrunner/run_build_test_target.py
+++ b/test/testrunner/run_build_test_target.py
@@ -108,7 +108,7 @@
   run_test_command = [os.path.join(env.ANDROID_BUILD_TOP,
                                    'art/test/testrunner/testrunner.py')]
   test_flags = target.get('run-test', [])
-  run_test_command += test_flags
+  run_test_command += list(map(lambda a: a.format(SOONG_OUT_DIR=env.SOONG_OUT_DIR), test_flags))
   # Let testrunner compute concurrency based on #cpus.
   # b/65822340
   # run_test_command += ['-j', str(n_threads)]
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index 077129f..bc22360 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -336,4 +336,12 @@
                      '--64',
                      '--no-build-dependencies'],
     },
+    'art-linux-bionic-x64-zipapex': {
+        'build': '{ANDROID_BUILD_TOP}/art/tools/build_linux_bionic_tests.sh {MAKE_OPTIONS} com.android.runtime.host',
+        'run-test': ['--run-test-option=--bionic',
+                     "--run-test-option='--runtime-zipapex {SOONG_OUT_DIR}/host/linux_bionic-x86/apex/com.android.runtime.host.zipapex'",
+                     '--host',
+                     '--64',
+                     '--no-build-dependencies'],
+    },
 }
diff --git a/tools/bootjars.sh b/tools/bootjars.sh
index ad6ee6b..9f22827 100755
--- a/tools/bootjars.sh
+++ b/tools/bootjars.sh
@@ -72,8 +72,10 @@
   # FIXME: The soong invocation we're using for getting the variables does not give us anything
   # defined in Android.common_path.mk, otherwise we would just use HOST-/TARGET_TEST_CORE_JARS.
 
-  # The core_jars_list must match the TEST_CORE_JARS variable in the Android.common_path.mk .
-  core_jars_list="core-oj core-libart core-simple conscrypt okhttp bouncycastle"
+  # Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
+  # because that's what we use for compiling the core.art image.
+  # It may contain additional modules from TEST_CORE_JARS.
+  core_jars_list="core-oj core-libart core-simple"
   core_jars_suffix=
   if [[ $mode == target ]]; then
     core_jars_suffix=-testdex
diff --git a/tools/build_linux_bionic.sh b/tools/build_linux_bionic.sh
new file mode 100755
index 0000000..94ccc41
--- /dev/null
+++ b/tools/build_linux_bionic.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This will build a target using linux_bionic. It can be called with normal make
+# flags.
+#
+# TODO This runs a 'm clean' prior to building the targets in order to ensure
+# that obsolete kati files don't mess up the build.
+
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+  pushd .
+else
+  pushd $ANDROID_BUILD_TOP
+fi
+
+if [ ! -d art ]; then
+  echo "Script needs to be run at the root of the android tree"
+  exit 1
+fi
+
+source build/envsetup.sh >&/dev/null # for get_build_var
+# Soong needs a bunch of variables set and will not run if they are missing.
+# The default values of these variables is only contained in make, so use
+# nothing to create the variables then remove all the other artifacts.
+build/soong/soong_ui.bash --make-mode nothing
+if [ $? != 0 ]; then
+  exit 1
+fi
+
+out_dir=$(get_build_var OUT_DIR)
+host_out=$(get_build_var HOST_OUT)
+
+# TODO(b/31559095) Figure out a better way to do this.
+#
+# There is no good way to force soong to generate host-bionic builds currently
+# so this is a hacky workaround.
+tmp_soong_var=$(mktemp --tmpdir soong.variables.bak.XXXXXX)
+
+cat $out_dir/soong/soong.variables > ${tmp_soong_var}
+build/soong/soong_ui.bash --make-mode clean
+mkdir -p $out_dir/soong
+
+python3 <<END - ${tmp_soong_var} ${out_dir}/soong/soong.variables
+import json
+import sys
+x = json.load(open(sys.argv[1]))
+x['Allow_missing_dependencies'] = True
+x['HostArch'] = 'x86_64'
+x['CrossHost'] = 'linux_bionic'
+x['CrossHostArch'] = 'x86_64'
+if 'CrossHostSecondaryArch' in x:
+  del x['CrossHostSecondaryArch']
+json.dump(x, open(sys.argv[2], mode='w'))
+END
+
+rm $tmp_soong_var
+
+build/soong/soong_ui.bash --make-mode --skip-make $@
diff --git a/tools/build_linux_bionic_tests.sh b/tools/build_linux_bionic_tests.sh
index 2b178f2..c532c90 100755
--- a/tools/build_linux_bionic_tests.sh
+++ b/tools/build_linux_bionic_tests.sh
@@ -81,6 +81,7 @@
   $soong_out/bin/hiddenapi
   $soong_out/bin/hprof-conv
   $soong_out/bin/timeout_dumper
+  $(find $host_out/apex -type f | sed "s:$host_out:$soong_out:g")
   $(find $host_out/lib64 -type f | sed "s:$host_out:$soong_out:g")
   $(find $host_out/nativetest64 -type f | sed "s:$host_out:$soong_out:g"))
 
diff --git a/tools/dist_linux_bionic.sh b/tools/dist_linux_bionic.sh
new file mode 100755
index 0000000..4c7ba1c
--- /dev/null
+++ b/tools/dist_linux_bionic.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+# Builds the given targets using linux-bionic and moves the output files to the
+# DIST_DIR. Takes normal make arguments.
+
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+  pushd .
+else
+  pushd $ANDROID_BUILD_TOP
+fi
+
+if [[ -z $DIST_DIR ]]; then
+  echo "DIST_DIR must be set!"
+  exit 1
+fi
+
+if [ ! -d art ]; then
+  echo "Script needs to be run at the root of the android tree"
+  exit 1
+fi
+
+source build/envsetup.sh >&/dev/null # for get_build_var
+out_dir=$(get_build_var OUT_DIR)
+
+./art/tools/build_linux_bionic.sh $@
+
+mkdir -p $DIST_DIR
+cp -R ${out_dir}/soong/host/* $DIST_DIR/
diff --git a/tools/luci/config/cr-buildbucket.cfg b/tools/luci/config/cr-buildbucket.cfg
index 29cca39..8df8433 100644
--- a/tools/luci/config/cr-buildbucket.cfg
+++ b/tools/luci/config/cr-buildbucket.cfg
@@ -27,8 +27,6 @@
   swarming {
     hostname: "chromium-swarm.appspot.com"
     builder_defaults {
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
       dimensions: "pool:luci.art.ci"
       service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       execution_timeout_secs: 10800  # 3h
diff --git a/tools/luci/config/luci-milo.cfg b/tools/luci/config/luci-milo.cfg
index ce22293..60e8404 100644
--- a/tools/luci/config/luci-milo.cfg
+++ b/tools/luci/config/luci-milo.cfg
@@ -6,6 +6,7 @@
   repo_url: "https://android.googlesource.com/platform/art"
   refs: "refs/heads/master"
   manifest_name: "REVISION"
+  include_experimental_builds: true
 
   builders {
     name: "buildbucket/luci.art.ci/angler-armv7-debug"
diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh
index f4a2dc1..c85a5ed 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -55,9 +55,10 @@
   done
 }
 
-# Note: This must match the TEST_CORE_JARS in Android.common_path.mk
+# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
 # because that's what we use for compiling the core.art image.
-BOOT_CLASSPATH_JARS="core-oj core-libart core-simple conscrypt okhttp bouncycastle"
+# It may contain additional modules from TEST_CORE_JARS.
+BOOT_CLASSPATH_JARS="core-oj core-libart core-simple okhttp bouncycastle conscrypt"
 
 vm_args=""
 art="$android_root/bin/art"
diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh
index 63f1fce..63fe81b 100755
--- a/tools/run-libcore-tests.sh
+++ b/tools/run-libcore-tests.sh
@@ -57,9 +57,10 @@
   done
 }
 
-# Note: This must match the TEST_CORE_JARS in Android.common_path.mk
+# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
 # because that's what we use for compiling the core.art image.
-BOOT_CLASSPATH_JARS="core-oj core-libart core-simple conscrypt okhttp bouncycastle"
+# It may contain additional modules from TEST_CORE_JARS.
+BOOT_CLASSPATH_JARS="core-oj core-libart core-simple okhttp bouncycastle conscrypt"
 
 DEPS="core-tests jsr166-tests mockito-target"