Merge "Revert "Allow boot image for a part of the boot classpath.""
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index cf35914..b6d6600 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -476,7 +476,6 @@
int rc = TEMP_FAILURE_RETRY(recvmsg(control_sock_, &msg, 0));
if (rc <= 0) {
- PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << control_sock_ << ")";
return android::base::unique_fd(-1);
} else {
VLOG(jdwp) << "Fds have been received from ADB!";
@@ -624,7 +623,6 @@
android::base::unique_fd new_fd(ReadFdFromAdb());
if (new_fd == -1) {
// Something went wrong. We need to retry getting the control socket.
- PLOG(ERROR) << "Something went wrong getting fds from adb. Retry!";
control_sock_.reset();
break;
} else if (adb_connection_socket_ != -1) {
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/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 b9fd868..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)
@@ -142,10 +142,11 @@
const ArrayRef<mirror::Class*> types_array(types, count);
std::vector<uint8_t> elf_file = debug::WriteDebugElfFileForClasses(
kRuntimeISA, compiler_options.GetInstructionSetFeatures(), types_array);
- MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
// We never free debug info for types, so we don't need to provide a handle
// (which would have been otherwise used as identifier to remove it later).
- AddNativeDebugInfoForJit(nullptr /* handle */, elf_file);
+ AddNativeDebugInfoForJit(Thread::Current(),
+ /*code_ptr=*/ nullptr,
+ elf_file);
}
}
@@ -180,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());
@@ -197,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 5bd1122..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/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 92aaa19..c9b4d36 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1470,13 +1470,14 @@
compiler_options.GetInstructionSetFeatures(),
mini_debug_info,
info);
- MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
- AddNativeDebugInfoForJit(reinterpret_cast<const void*>(info.code_address), elf_file);
+ AddNativeDebugInfoForJit(Thread::Current(),
+ reinterpret_cast<const void*>(info.code_address),
+ elf_file);
VLOG(jit)
<< "JIT mini-debug-info added for " << ArtMethod::PrettyMethod(method)
<< " size=" << PrettySize(elf_file.size())
- << " total_size=" << PrettySize(GetJitNativeDebugInfoMemUsage());
+ << " total_size=" << PrettySize(GetJitMiniDebugInfoMemUsage());
}
} // namespace art
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..f292303 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"
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 e31fe63..d33541c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3736,8 +3736,7 @@
data.weak_root = dex_cache_jweak;
data.dex_file = dex_cache->GetDexFile();
data.class_table = ClassTableForClassLoader(class_loader);
- AddNativeDebugInfoForDex(self, ArrayRef<const uint8_t>(data.dex_file->Begin(),
- data.dex_file->Size()));
+ AddNativeDebugInfoForDex(self, data.dex_file);
DCHECK(data.class_table != nullptr);
// Make sure to hold the dex cache live in the class table. This case happens for the boot class
// path dex caches without an image.
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/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..de65f02 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..5045888 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -654,6 +654,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 +717,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 +1116,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 +1129,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 +1151,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 +1240,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 +1252,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 +1293,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 +1302,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 +1371,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 +1394,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) {
@@ -1766,22 +1656,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_) {
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index dbc12d1..9049a53 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -189,6 +189,8 @@
private:
class BootImageLoader;
+ template <typename ReferenceVisitor>
+ class ClassTableVisitor;
class Loader;
template <typename PatchObjectVisitor>
class PatchArtFieldVisitor;
diff --git a/runtime/jit/debugger_interface.cc b/runtime/jit/debugger_interface.cc
index 6cd719a..853c0ca 100644
--- a/runtime/jit/debugger_interface.cc
+++ b/runtime/jit/debugger_interface.cc
@@ -21,12 +21,13 @@
#include "base/array_ref.h"
#include "base/mutex.h"
#include "base/time_utils.h"
+#include "dex/dex_file.h"
#include "thread-current-inl.h"
#include "thread.h"
#include <atomic>
-#include <unordered_map>
#include <cstddef>
+#include <map>
//
// Debug interface for native tools (gdb, lldb, libunwind, simpleperf).
@@ -126,14 +127,14 @@
void (*__jit_debug_register_code_ptr)() = __jit_debug_register_code;
// The root data structure describing of all JITed methods.
- JITDescriptor __jit_debug_descriptor {};
+ JITDescriptor __jit_debug_descriptor GUARDED_BY(*Locks::native_debug_interface_lock_) {};
// The following globals mirror the ones above, but are used to register dex files.
void __attribute__((noinline)) __dex_debug_register_code() {
__asm__("");
}
void (*__dex_debug_register_code_ptr)() = __dex_debug_register_code;
- JITDescriptor __dex_debug_descriptor {};
+ JITDescriptor __dex_debug_descriptor GUARDED_BY(*Locks::native_debug_interface_lock_) {};
}
// Mark the descriptor as "locked", so native tools know the data is being modified.
@@ -155,8 +156,17 @@
static JITCodeEntry* CreateJITCodeEntryInternal(
JITDescriptor& descriptor,
void (*register_code_ptr)(),
- const ArrayRef<const uint8_t>& symfile)
+ ArrayRef<const uint8_t> symfile,
+ bool copy_symfile)
REQUIRES(Locks::native_debug_interface_lock_) {
+ // Make a copy of the buffer to shrink it and to pass ownership to JITCodeEntry.
+ if (copy_symfile) {
+ uint8_t* copy = new uint8_t[symfile.size()];
+ CHECK(copy != nullptr);
+ memcpy(copy, symfile.data(), symfile.size());
+ symfile = ArrayRef<const uint8_t>(copy, symfile.size());
+ }
+
// Ensure the timestamp is monotonically increasing even in presence of low
// granularity system timer. This ensures each entry has unique timestamp.
uint64_t timestamp = std::max(descriptor.action_timestamp_ + 1, NanoTime());
@@ -188,9 +198,11 @@
static void DeleteJITCodeEntryInternal(
JITDescriptor& descriptor,
void (*register_code_ptr)(),
- JITCodeEntry* entry)
+ JITCodeEntry* entry,
+ bool free_symfile)
REQUIRES(Locks::native_debug_interface_lock_) {
CHECK(entry != nullptr);
+ const uint8_t* symfile = entry->symfile_addr_;
// Ensure the timestamp is monotonically increasing even in presence of low
// granularity system timer. This ensures each entry has unique timestamp.
@@ -221,83 +233,88 @@
memset(entry, 0, sizeof(*entry));
delete entry;
+ if (free_symfile) {
+ delete[] symfile;
+ }
}
-static std::unordered_map<const void*, JITCodeEntry*> __dex_debug_entries
- GUARDED_BY(Locks::native_debug_interface_lock_);
+static std::map<const DexFile*, JITCodeEntry*> g_dex_debug_entries
+ GUARDED_BY(*Locks::native_debug_interface_lock_);
-void AddNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile) {
- MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
- DCHECK(dexfile.data() != nullptr);
+void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
+ MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ DCHECK(dexfile != nullptr);
// This is just defensive check. The class linker should not register the dex file twice.
- if (__dex_debug_entries.count(dexfile.data()) == 0) {
+ if (g_dex_debug_entries.count(dexfile) == 0) {
+ const ArrayRef<const uint8_t> symfile(dexfile->Begin(), dexfile->Size());
JITCodeEntry* entry = CreateJITCodeEntryInternal(__dex_debug_descriptor,
__dex_debug_register_code_ptr,
- dexfile);
- __dex_debug_entries.emplace(dexfile.data(), entry);
+ symfile,
+ /*copy_symfile=*/ false);
+ g_dex_debug_entries.emplace(dexfile, entry);
}
}
-void RemoveNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile) {
- MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
- auto it = __dex_debug_entries.find(dexfile.data());
+void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
+ MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ auto it = g_dex_debug_entries.find(dexfile);
// We register dex files in the class linker and free them in DexFile_closeDexFile, but
// there might be cases where we load the dex file without using it in the class linker.
- if (it != __dex_debug_entries.end()) {
+ if (it != g_dex_debug_entries.end()) {
DeleteJITCodeEntryInternal(__dex_debug_descriptor,
__dex_debug_register_code_ptr,
- it->second);
- __dex_debug_entries.erase(it);
+ /*entry=*/ it->second,
+ /*free_symfile=*/ false);
+ g_dex_debug_entries.erase(it);
}
}
-static size_t __jit_debug_mem_usage
- GUARDED_BY(Locks::native_debug_interface_lock_) = 0;
-
// Mapping from handle to entry. Used to manage life-time of the entries.
-static std::unordered_map<const void*, JITCodeEntry*> __jit_debug_entries
- GUARDED_BY(Locks::native_debug_interface_lock_);
+static std::map<const void*, JITCodeEntry*> g_jit_debug_entries
+ GUARDED_BY(*Locks::native_debug_interface_lock_);
-void AddNativeDebugInfoForJit(const void* handle, const std::vector<uint8_t>& symfile) {
+void AddNativeDebugInfoForJit(Thread* self,
+ const void* code_ptr,
+ const std::vector<uint8_t>& symfile) {
+ MutexLock mu(self, *Locks::native_debug_interface_lock_);
DCHECK_NE(symfile.size(), 0u);
- // Make a copy of the buffer to shrink it and to pass ownership to JITCodeEntry.
- uint8_t* copy = new uint8_t[symfile.size()];
- CHECK(copy != nullptr);
- memcpy(copy, symfile.data(), symfile.size());
-
JITCodeEntry* entry = CreateJITCodeEntryInternal(
__jit_debug_descriptor,
__jit_debug_register_code_ptr,
- ArrayRef<const uint8_t>(copy, symfile.size()));
- __jit_debug_mem_usage += sizeof(JITCodeEntry) + entry->symfile_size_;
+ ArrayRef<const uint8_t>(symfile),
+ /*copy_symfile=*/ true);
- // We don't provide handle for type debug info, which means we cannot free it later.
+ // We don't provide code_ptr for type debug info, which means we cannot free it later.
// (this only happens when --generate-debug-info flag is enabled for the purpose
// of being debugged with gdb; it does not happen for debuggable apps by default).
- bool ok = handle == nullptr || __jit_debug_entries.emplace(handle, entry).second;
- DCHECK(ok) << "Native debug entry already exists for " << std::hex << handle;
-}
-
-void RemoveNativeDebugInfoForJit(const void* handle) {
- auto it = __jit_debug_entries.find(handle);
- // We generate JIT native debug info only if the right runtime flags are enabled,
- // but we try to remove it unconditionally whenever code is freed from JIT cache.
- if (it != __jit_debug_entries.end()) {
- JITCodeEntry* entry = it->second;
- const uint8_t* symfile_addr = entry->symfile_addr_;
- uint64_t symfile_size = entry->symfile_size_;
- DeleteJITCodeEntryInternal(__jit_debug_descriptor,
- __jit_debug_register_code_ptr,
- entry);
- __jit_debug_entries.erase(it);
- __jit_debug_mem_usage -= sizeof(JITCodeEntry) + symfile_size;
- delete[] symfile_addr;
+ if (code_ptr != nullptr) {
+ bool ok = g_jit_debug_entries.emplace(code_ptr, entry).second;
+ DCHECK(ok) << "Native debug entry already exists for " << std::hex << code_ptr;
}
}
-size_t GetJitNativeDebugInfoMemUsage() {
- return __jit_debug_mem_usage + __jit_debug_entries.size() * 2 * sizeof(void*);
+void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr) {
+ MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ auto it = g_jit_debug_entries.find(code_ptr);
+ // We generate JIT native debug info only if the right runtime flags are enabled,
+ // but we try to remove it unconditionally whenever code is freed from JIT cache.
+ if (it != g_jit_debug_entries.end()) {
+ DeleteJITCodeEntryInternal(__jit_debug_descriptor,
+ __jit_debug_register_code_ptr,
+ it->second,
+ /*free_symfile=*/ true);
+ g_jit_debug_entries.erase(it);
+ }
+}
+
+size_t GetJitMiniDebugInfoMemUsage() {
+ MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
+ size_t size = 0;
+ for (auto entry : g_jit_debug_entries) {
+ size += sizeof(JITCodeEntry) + entry.second->symfile_size_ + /*map entry*/ 4 * sizeof(void*);
+ }
+ return size;
}
} // namespace art
diff --git a/runtime/jit/debugger_interface.h b/runtime/jit/debugger_interface.h
index fb5e81b..4b0d011 100644
--- a/runtime/jit/debugger_interface.h
+++ b/runtime/jit/debugger_interface.h
@@ -18,35 +18,37 @@
#define ART_RUNTIME_JIT_DEBUGGER_INTERFACE_H_
#include <inttypes.h>
-#include <memory>
#include <vector>
-#include "base/array_ref.h"
#include "base/locks.h"
namespace art {
+class DexFile;
+class Thread;
+
// Notify native tools (e.g. libunwind) that DEX file has been opened.
-// It takes the lock itself. The parameter must point to dex data (not the DexFile* object).
-void AddNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile);
+void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile)
+ REQUIRES(!Locks::native_debug_interface_lock_);
// Notify native tools (e.g. libunwind) that DEX file has been closed.
-// It takes the lock itself. The parameter must point to dex data (not the DexFile* object).
-void RemoveNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile);
+void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile)
+ REQUIRES(!Locks::native_debug_interface_lock_);
-// Notify native tools about new JITed code by passing in-memory ELF.
-// The handle is the object that is being described (needed to be able to remove the entry).
+// Notify native tools (e.g. libunwind) that JIT has compiled a new method.
// The method will make copy of the passed ELF file (to shrink it to the minimum size).
-void AddNativeDebugInfoForJit(const void* handle, const std::vector<uint8_t>& symfile)
- REQUIRES(Locks::native_debug_interface_lock_);
+void AddNativeDebugInfoForJit(Thread* self,
+ const void* code_ptr,
+ const std::vector<uint8_t>& symfile)
+ REQUIRES(!Locks::native_debug_interface_lock_);
-// Notify native debugger that JITed code has been removed and free the debug info.
-void RemoveNativeDebugInfoForJit(const void* handle)
- REQUIRES(Locks::native_debug_interface_lock_);
+// Notify native tools (e.g. libunwind) that JIT code has been garbage collected.
+void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr)
+ REQUIRES(!Locks::native_debug_interface_lock_);
-// Returns approximate memory used by all JITCodeEntries.
-size_t GetJitNativeDebugInfoMemUsage()
- REQUIRES(Locks::native_debug_interface_lock_);
+// Returns approximate memory used by debug info for JIT code.
+size_t GetJitMiniDebugInfoMemUsage()
+ REQUIRES(!Locks::native_debug_interface_lock_);
} // namespace art
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/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 749758a..d976fec 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -765,8 +765,7 @@
uintptr_t allocation = FromCodeToAllocation(code_ptr);
// Notify native debugger that we are about to remove the code.
// It does nothing if we are not using native debugger.
- MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
- RemoveNativeDebugInfoForJit(code_ptr);
+ RemoveNativeDebugInfoForJit(Thread::Current(), code_ptr);
if (OatQuickMethodHeader::FromCodePointer(code_ptr)->IsOptimized()) {
FreeData(GetRootTable(code_ptr));
} // else this is a JNI stub without any data.
@@ -2101,10 +2100,9 @@
void JitCodeCache::Dump(std::ostream& os) {
MutexLock mu(Thread::Current(), lock_);
- MutexLock mu2(Thread::Current(), *Locks::native_debug_interface_lock_);
os << "Current JIT code cache size: " << PrettySize(used_memory_for_code_) << "\n"
<< "Current JIT data cache size: " << PrettySize(used_memory_for_data_) << "\n"
- << "Current JIT mini-debug-info size: " << PrettySize(GetJitNativeDebugInfoMemUsage()) << "\n"
+ << "Current JIT mini-debug-info size: " << PrettySize(GetJitMiniDebugInfoMemUsage()) << "\n"
<< "Current JIT capacity: " << PrettySize(current_capacity_) << "\n"
<< "Current number of JIT JNI stub entries: " << jni_stubs_map_.size() << "\n"
<< "Current number of JIT code cache entries: " << method_code_map_.size() << "\n"
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 203d200..1da91b0 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -337,8 +337,7 @@
int32_t i = kDexFileIndexStart; // Oat file is at index 0.
for (const DexFile* dex_file : dex_files) {
if (dex_file != nullptr) {
- RemoveNativeDebugInfoForDex(soa.Self(), ArrayRef<const uint8_t>(dex_file->Begin(),
- dex_file->Size()));
+ RemoveNativeDebugInfoForDex(soa.Self(), dex_file);
// Only delete the dex file if the dex cache is not found to prevent runtime crashes if there
// are calls to DexFile.close while the ART DexFile is still in use.
if (!class_linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
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/runtime.cc b/runtime/runtime.cc
index 69ef2fb..d2c915e 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";
}
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..16106ab 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
@@ -703,6 +710,8 @@
exit 1
fi
+BIN_DIR=$ANDROID_ROOT/bin
+
profman_cmdline="true"
dex2oat_cmdline="true"
vdex_cmdline="true"
@@ -712,6 +721,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 +737,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 +783,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 +840,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 +948,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 +990,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 +1034,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 +1048,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/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"