Merge "ART: Dump allocation stacks in reference table dumps"
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index bf287b1..7daf01c 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -278,7 +278,9 @@
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 50aabdc..b767b19 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -123,7 +123,9 @@
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 af933ae..2f6c5dc 100644
--- a/runtime/jni_env_ext.h
+++ b/runtime/jni_env_ext.h
@@ -45,7 +45,8 @@
~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 @@
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 e6e588e..a6df27b 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 @@
}
}
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 010c6f8..6af5ca5 100644
--- a/runtime/reference_table.h
+++ b/runtime/reference_table.h
@@ -47,7 +47,9 @@
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 @@
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 d830387..1e7fc3e 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 @@
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 @@
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);
+ }
}
}