diff options
author | 2017-09-29 17:53:18 -0700 | |
---|---|---|
committer | 2017-10-02 14:41:40 -0700 | |
commit | 8a2a1fc5d7a338a9b29794b2ee5b40a1c24a4e52 (patch) | |
tree | 0de46574f1bcb0820287db63722178473158b1ec | |
parent | a96c47805bdb4c6755ef539bf8c0f945181428b1 (diff) |
ART: Dump allocation stacks in reference table dumps
When allocation tracking is enabled and allocation stacks are available,
print the stack traces of the objects in a reference table dumps, to
aid tracking table overflows.
Extend reference_table_test.
Bug: 67044702
Test: m test-art-host
Change-Id: I0118ba095f08dc66739707cd6a184487974b1570
-rw-r--r-- | runtime/indirect_reference_table.h | 4 | ||||
-rw-r--r-- | runtime/java_vm_ext.h | 4 | ||||
-rw-r--r-- | runtime/jni_env_ext.h | 7 | ||||
-rw-r--r-- | runtime/reference_table.cc | 50 | ||||
-rw-r--r-- | runtime/reference_table.h | 7 | ||||
-rw-r--r-- | runtime/reference_table_test.cc | 84 |
6 files changed, 150 insertions, 6 deletions
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h index bf287b1ac9..7daf01ce61 100644 --- a/runtime/indirect_reference_table.h +++ b/runtime/indirect_reference_table.h @@ -278,7 +278,9 @@ class IndirectReferenceTable { void AssertEmpty() REQUIRES_SHARED(Locks::mutator_lock_); - void Dump(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_); + void Dump(std::ostream& os) const + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::alloc_tracker_lock_); // Return the #of entries in the entire table. This includes holes, and // so may be larger than the actual number of "live" entries. diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index 50aabdcdf5..b767b199f0 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -123,7 +123,9 @@ class JavaVMExt : public JavaVM { void DumpReferenceTables(std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_) - REQUIRES(!Locks::jni_globals_lock_, !Locks::jni_weak_globals_lock_); + REQUIRES(!Locks::jni_globals_lock_, + !Locks::jni_weak_globals_lock_, + !Locks::alloc_tracker_lock_); bool SetCheckJniEnabled(bool enabled); diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h index af933ae835..2f6c5dc92a 100644 --- a/runtime/jni_env_ext.h +++ b/runtime/jni_env_ext.h @@ -45,7 +45,8 @@ struct JNIEnvExt : public JNIEnv { ~JNIEnvExt(); void DumpReferenceTables(std::ostream& os) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::alloc_tracker_lock_); void SetCheckJniEnabled(bool enabled) REQUIRES(!Locks::jni_function_table_lock_); @@ -53,7 +54,9 @@ struct JNIEnvExt : public JNIEnv { void PopFrame() REQUIRES_SHARED(Locks::mutator_lock_); template<typename T> - T AddLocalReference(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_); + T AddLocalReference(ObjPtr<mirror::Object> obj) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::alloc_tracker_lock_); static Offset SegmentStateOffset(size_t pointer_size); static Offset LocalRefCookieOffset(size_t pointer_size); diff --git a/runtime/reference_table.cc b/runtime/reference_table.cc index e6e588e9b0..a6df27b236 100644 --- a/runtime/reference_table.cc +++ b/runtime/reference_table.cc @@ -19,6 +19,8 @@ #include "android-base/stringprintf.h" #include "base/mutex.h" +#include "gc/allocation_record.h" +#include "gc/heap.h" #include "indirect_reference_table.h" #include "mirror/array-inl.h" #include "mirror/array.h" @@ -206,6 +208,54 @@ void ReferenceTable::Dump(std::ostream& os, Table& entries) { } } os << StringPrintf(" %5d: ", idx) << ref << " " << className << extras << "\n"; + if (runtime->GetHeap()->IsAllocTrackingEnabled()) { + MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_); + + gc::AllocRecordObjectMap* records = runtime->GetHeap()->GetAllocationRecords(); + DCHECK(records != nullptr); + // It's annoying that this is a list. But this code should be very uncommon to be executed. + + auto print_stack = [&](ObjPtr<mirror::Object> to_print, const std::string& msg) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(Locks::alloc_tracker_lock_) { + for (auto it = records->Begin(), end = records->End(); it != end; ++it) { + GcRoot<mirror::Object>& stack_for_object = it->first; + gc::AllocRecord& record = it->second; + if (stack_for_object.Read() == to_print.Ptr()) { + os << " " << msg << "\n"; + const gc::AllocRecordStackTrace* trace = record.GetStackTrace(); + size_t depth = trace->GetDepth(); + if (depth == 0) { + os << " (No managed frames)\n"; + } else { + for (size_t i = 0; i < depth; ++i) { + const gc::AllocRecordStackTraceElement& frame = trace->GetStackElement(i); + os << " "; + if (frame.GetMethod() == nullptr) { + os << "(missing method data)\n"; + continue; + } + os << frame.GetMethod()->PrettyMethod(true) + << ":" + << frame.ComputeLineNumber() + << "\n"; + } + } + break; + } + } + }; + // Print the stack trace of the ref. + print_stack(ref, "Allocated at:"); + + // If it's a reference, see if we have data about the referent. + if (ref->IsReferenceInstance()) { + ObjPtr<mirror::Object> referent = ref->AsReference()->GetReferent(); + if (referent != nullptr) { + print_stack(referent, "Referent allocated at:"); + } + } + } } // Make a copy of the table and sort it, only adding non null and not cleared elements. diff --git a/runtime/reference_table.h b/runtime/reference_table.h index 010c6f8fde..6af5ca5224 100644 --- a/runtime/reference_table.h +++ b/runtime/reference_table.h @@ -47,7 +47,9 @@ class ReferenceTable { size_t Size() const; - void Dump(std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_); + void Dump(std::ostream& os) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::alloc_tracker_lock_); void VisitRoots(RootVisitor* visitor, const RootInfo& root_info) REQUIRES_SHARED(Locks::mutator_lock_); @@ -56,7 +58,8 @@ class ReferenceTable { typedef std::vector<GcRoot<mirror::Object>, TrackingAllocator<GcRoot<mirror::Object>, kAllocatorTagReferenceTable>> Table; static void Dump(std::ostream& os, Table& entries) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::alloc_tracker_lock_); friend class IndirectReferenceTable; // For Dump. std::string name_; diff --git a/runtime/reference_table_test.cc b/runtime/reference_table_test.cc index d830387b24..1e7fc3ee92 100644 --- a/runtime/reference_table_test.cc +++ b/runtime/reference_table_test.cc @@ -16,6 +16,8 @@ #include "reference_table.h" +#include <regex> + #include "android-base/stringprintf.h" #include "art_method-inl.h" @@ -30,6 +32,7 @@ #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" +#include "well_known_classes.h" namespace art { @@ -156,6 +159,7 @@ TEST_F(ReferenceTableTest, Basics) { rt.Dump(oss); EXPECT_NE(oss.str().find("java.lang.ref.WeakReference (referent is null)"), std::string::npos) << oss.str(); + rt.Remove(empty_reference); } { @@ -168,6 +172,86 @@ TEST_F(ReferenceTableTest, Basics) { EXPECT_NE(oss.str().find("java.lang.ref.WeakReference (referent is a java.lang.String)"), std::string::npos) << oss.str(); + rt.Remove(non_empty_reference); + } + + // Add two objects. Enable allocation tracking for the latter. + { + StackHandleScope<3> hs(soa.Self()); + Handle<mirror::String> h_without_trace(hs.NewHandle( + mirror::String::AllocFromModifiedUtf8(soa.Self(), "Without"))); + + { + ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended); + gc::AllocRecordObjectMap::SetAllocTrackingEnabled(true); + } + + // To get a stack, actually make a call. Use substring, that's simple. Calling through JNI + // avoids having to create the low-level args array ourselves. + Handle<mirror::Object> h_with_trace; + { + jmethodID substr = soa.Env()->GetMethodID(WellKnownClasses::java_lang_String, + "substring", + "(II)Ljava/lang/String;"); + ASSERT_TRUE(substr != nullptr); + jobject jobj = soa.Env()->AddLocalReference<jobject>(h_without_trace.Get()); + ASSERT_TRUE(jobj != nullptr); + jobject result = soa.Env()->CallObjectMethod(jobj, + substr, + static_cast<jint>(0), + static_cast<jint>(4)); + ASSERT_TRUE(result != nullptr); + h_with_trace = hs.NewHandle(soa.Self()->DecodeJObject(result)); + } + + Handle<mirror::Object> h_ref; + { + jclass weak_ref_class = soa.Env()->FindClass("java/lang/ref/WeakReference"); + ASSERT_TRUE(weak_ref_class != nullptr); + jmethodID init = soa.Env()->GetMethodID(weak_ref_class, + "<init>", + "(Ljava/lang/Object;)V"); + ASSERT_TRUE(init != nullptr); + jobject referent = soa.Env()->AddLocalReference<jobject>(h_with_trace.Get()); + jobject result = soa.Env()->NewObject(weak_ref_class, init, referent); + ASSERT_TRUE(result != nullptr); + h_ref = hs.NewHandle(soa.Self()->DecodeJObject(result)); + } + + rt.Add(h_without_trace.Get()); + rt.Add(h_with_trace.Get()); + rt.Add(h_ref.Get()); + + std::ostringstream oss; + rt.Dump(oss); + + constexpr const char* kStackTracePattern = + R"(test reference table dump:\n)" + R"( Last 3 entries \(of 3\):\n)" // NOLINT + R"( 2: 0x[0-9a-f]* java.lang.ref.WeakReference \(referent is a java.lang.String\)\n)" // NOLINT + R"( Allocated at:\n)" + R"( \(No managed frames\)\n)" // NOLINT + R"( Referent allocated at:\n)" + R"( java.lang.String java.lang.String.fastSubstring\(int, int\):-2\n)" // NOLINT + R"( java.lang.String java.lang.String.substring\(int, int\):[0-9]*\n)" // NOLINT + R"( 1: 0x[0-9a-f]* java.lang.String "With"\n)" + R"( Allocated at:\n)" + R"( java.lang.String java.lang.String.fastSubstring\(int, int\):-2\n)" // NOLINT + R"( java.lang.String java.lang.String.substring\(int, int\):[0-9]*\n)" // NOLINT + R"( 0: 0x[0-9a-f]* java.lang.String "Without"\n)" + R"( Summary:\n)" + R"( 2 of java.lang.String \(2 unique instances\)\n)" // NOLINT + R"( 1 of java.lang.ref.WeakReference\n)"; + std::regex stack_trace_regex(kStackTracePattern); + std::smatch stack_trace_match; + std::string str = oss.str(); + bool found = std::regex_search(str, stack_trace_match, stack_trace_regex); + EXPECT_TRUE(found) << str; + + { + ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended); + gc::AllocRecordObjectMap::SetAllocTrackingEnabled(false); + } } } |