Avoid heap trims in foreground and audio apps.
Heap trimming the dlmalloc spaces holds their locks causing jank.
Improve diagnostic message to break apart managed and native heap trims and
detail amount of heap advised back to the kernel.
Change-Id: Ic08cf4749342cbd057b162b901d1676bb7e6418b
diff --git a/src/gc/space.cc b/src/gc/space.cc
index 37565db..7e6f7ed 100644
--- a/src/gc/space.cc
+++ b/src/gc/space.cc
@@ -406,7 +406,7 @@
return InternalAllocationSize(obj);
}
-void MspaceMadviseCallback(void* start, void* end, size_t used_bytes, void* /* arg */) {
+void MspaceMadviseCallback(void* start, void* end, size_t used_bytes, void* arg) {
// Is this chunk in use?
if (used_bytes != 0) {
return;
@@ -417,15 +417,19 @@
if (end > start) {
size_t length = reinterpret_cast<byte*>(end) - reinterpret_cast<byte*>(start);
CHECK_MEMORY_CALL(madvise, (start, length, MADV_DONTNEED), "trim");
+ size_t* reclaimed = reinterpret_cast<size_t*>(arg);
+ *reclaimed += length;
}
}
-void DlMallocSpace::Trim() {
+size_t DlMallocSpace::Trim() {
MutexLock mu(Thread::Current(), lock_);
// Trim to release memory at the end of the space.
mspace_trim(mspace_, 0);
// Visit space looking for page-sized holes to advise the kernel we don't need.
- mspace_inspect_all(mspace_, MspaceMadviseCallback, NULL);
+ size_t reclaimed = 0;
+ mspace_inspect_all(mspace_, MspaceMadviseCallback, &reclaimed);
+ return reclaimed;
}
void DlMallocSpace::Walk(void(*callback)(void *start, void *end, size_t num_bytes, void* callback_arg),
diff --git a/src/gc/space.h b/src/gc/space.h
index f51ae5d..2ed4988 100644
--- a/src/gc/space.h
+++ b/src/gc/space.h
@@ -284,7 +284,7 @@
}
// Hands unused pages back to the system.
- void Trim();
+ size_t Trim();
// Perform a mspace_inspect_all which calls back for each allocation chunk. The chunk may not be
// in use, indicated by num_bytes equaling zero.
diff --git a/src/heap.cc b/src/heap.cc
index 656aae7..40037e3 100644
--- a/src/heap.cc
+++ b/src/heap.cc
@@ -23,6 +23,7 @@
#include <vector>
#include "base/stl_util.h"
+#include "cutils/sched_policy.h"
#include "debugger.h"
#include "gc/atomic_stack.h"
#include "gc/card_table.h"
@@ -1789,18 +1790,20 @@
}
}
-void Heap::Trim() {
- alloc_space_->Trim();
-}
-
void Heap::RequestHeapTrim() {
+ // GC completed and now we must decide whether to request a heap trim (advising pages back to the
+ // kernel) or not. Issuing a request will also cause trimming of the libc heap. As a trim scans
+ // a space it will hold its lock and can become a cause of jank.
+ // Note, the large object space self trims and the Zygote space was trimmed and unchanging since
+ // forking.
+
// We don't have a good measure of how worthwhile a trim might be. We can't use the live bitmap
// because that only marks object heads, so a large array looks like lots of empty space. We
// don't just call dlmalloc all the time, because the cost of an _attempted_ trim is proportional
// to utilization (which is probably inversely proportional to how much benefit we can expect).
// We could try mincore(2) but that's only a measure of how many pages we haven't given away,
// not how much use we're making of those pages.
- uint64_t ms_time = NsToMs(NanoTime());
+ uint64_t ms_time = MilliTime();
float utilization =
static_cast<float>(alloc_space_->GetNumBytesAllocated()) / alloc_space_->Size();
if ((utilization > 0.75f) || ((ms_time - last_trim_time_) < 2 * 1000)) {
@@ -1820,6 +1823,14 @@
return;
}
}
+
+ SchedPolicy policy;
+ get_sched_policy(self->GetTid(), &policy);
+ if (policy == SP_FOREGROUND || policy == SP_AUDIO_APP) {
+ // Don't trim the heap if we are a foreground or audio app.
+ return;
+ }
+
last_trim_time_ = ms_time;
JNIEnv* env = self->GetJniEnv();
DCHECK(WellKnownClasses::java_lang_Daemons != NULL);
@@ -1829,4 +1840,9 @@
CHECK(!env->ExceptionCheck());
}
+size_t Heap::Trim() {
+ // Handle a requested heap trim on a thread outside of the main GC thread.
+ return alloc_space_->Trim();
+}
+
} // namespace art
diff --git a/src/heap.h b/src/heap.h
index 78cbe99..b7fc34d 100644
--- a/src/heap.h
+++ b/src/heap.h
@@ -297,7 +297,7 @@
void DumpForSigQuit(std::ostream& os);
- void Trim();
+ size_t Trim();
HeapBitmap* GetLiveBitmap() SHARED_LOCKS_REQUIRED(Locks::heap_bitmap_lock_) {
return live_bitmap_.get();
diff --git a/src/native/dalvik_system_VMRuntime.cc b/src/native/dalvik_system_VMRuntime.cc
index 9c40041..bf518dc 100644
--- a/src/native/dalvik_system_VMRuntime.cc
+++ b/src/native/dalvik_system_VMRuntime.cc
@@ -157,19 +157,30 @@
}
static void VMRuntime_trimHeap(JNIEnv*, jobject) {
+ uint64_t start_ns = NanoTime();
+
// Trim the managed heap.
Heap* heap = Runtime::Current()->GetHeap();
- uint64_t start_ns = NanoTime();
DlMallocSpace* alloc_space = heap->GetAllocSpace();
size_t alloc_space_size = alloc_space->Size();
- float utilization = static_cast<float>(alloc_space->GetNumBytesAllocated()) / alloc_space_size;
- heap->Trim();
+ float managed_utilization =
+ static_cast<float>(alloc_space->GetNumBytesAllocated()) / alloc_space_size;
+ size_t managed_reclaimed = heap->Trim();
+
+ uint64_t gc_heap_end_ns = NanoTime();
+
// Trim the native heap.
dlmalloc_trim(0);
- dlmalloc_inspect_all(MspaceMadviseCallback, NULL);
- LOG(INFO) << "Parallel heap trimming took " << PrettyDuration(NanoTime() - start_ns)
- << " on a " << PrettySize(alloc_space_size)
- << " alloc space with " << static_cast<int>(100 * utilization) << "% utilization";
+ size_t native_reclaimed = 0;
+ dlmalloc_inspect_all(MspaceMadviseCallback, &native_reclaimed);
+
+ uint64_t end_ns = NanoTime();
+
+ LOG(INFO) << "Heap trim of managed (duration=" << PrettyDuration(gc_heap_end_ns - start_ns)
+ << ", advised=" << PrettySize(managed_reclaimed) << ") and native (duration="
+ << PrettyDuration(end_ns - gc_heap_end_ns) << ", advised=" << PrettySize(native_reclaimed)
+ << ") heaps. Managed heap utilization of " << static_cast<int>(100 * managed_utilization)
+ << "%.";
}
static void VMRuntime_concurrentGC(JNIEnv* env, jobject) {