Merge "Fix numerous issues with DdmVmInternal allocation tracking" into lmp-dev
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 80fb9ea..d149fbe 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -30,21 +30,23 @@
namespace art {
Mutex* Locks::abort_lock_ = nullptr;
+Mutex* Locks::alloc_tracker_lock_ = nullptr;
Mutex* Locks::allocated_monitor_ids_lock_ = nullptr;
Mutex* Locks::allocated_thread_ids_lock_ = nullptr;
ReaderWriterMutex* Locks::breakpoint_lock_ = nullptr;
ReaderWriterMutex* Locks::classlinker_classes_lock_ = nullptr;
+Mutex* Locks::deoptimization_lock_ = nullptr;
ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr;
Mutex* Locks::logging_lock_ = nullptr;
Mutex* Locks::mem_maps_lock_ = nullptr;
Mutex* Locks::modify_ldt_lock_ = nullptr;
ReaderWriterMutex* Locks::mutator_lock_ = nullptr;
+Mutex* Locks::profiler_lock_ = nullptr;
Mutex* Locks::runtime_shutdown_lock_ = nullptr;
Mutex* Locks::thread_list_lock_ = nullptr;
Mutex* Locks::thread_list_suspend_thread_lock_ = nullptr;
Mutex* Locks::thread_suspend_count_lock_ = nullptr;
Mutex* Locks::trace_lock_ = nullptr;
-Mutex* Locks::profiler_lock_ = nullptr;
Mutex* Locks::unexpected_signal_lock_ = nullptr;
Mutex* Locks::intern_table_lock_ = nullptr;
@@ -829,20 +831,22 @@
DCHECK(modify_ldt_lock_ == nullptr);
}
DCHECK(abort_lock_ != nullptr);
+ DCHECK(alloc_tracker_lock_ != nullptr);
DCHECK(allocated_monitor_ids_lock_ != nullptr);
DCHECK(allocated_thread_ids_lock_ != nullptr);
DCHECK(breakpoint_lock_ != nullptr);
DCHECK(classlinker_classes_lock_ != nullptr);
+ DCHECK(deoptimization_lock_ != nullptr);
DCHECK(heap_bitmap_lock_ != nullptr);
+ DCHECK(intern_table_lock_ != nullptr);
DCHECK(logging_lock_ != nullptr);
DCHECK(mutator_lock_ != nullptr);
+ DCHECK(profiler_lock_ != nullptr);
DCHECK(thread_list_lock_ != nullptr);
DCHECK(thread_list_suspend_thread_lock_ != nullptr);
DCHECK(thread_suspend_count_lock_ != nullptr);
DCHECK(trace_lock_ != nullptr);
- DCHECK(profiler_lock_ != nullptr);
DCHECK(unexpected_signal_lock_ != nullptr);
- DCHECK(intern_table_lock_ != nullptr);
} else {
// Create global locks in level order from highest lock level to lowest.
LockLevel current_lock_level = kThreadListSuspendThreadLock;
@@ -851,7 +855,12 @@
new Mutex("thread list suspend thread by .. lock", current_lock_level);
#define UPDATE_CURRENT_LOCK_LEVEL(new_level) \
- DCHECK_LT(new_level, current_lock_level); \
+ if (new_level >= current_lock_level) { \
+ /* Do not use CHECKs or FATAL here, abort_lock_ is not setup yet. */ \
+ fprintf(stderr, "New local level %d is not less than current level %d\n", \
+ new_level, current_lock_level); \
+ exit(1); \
+ } \
current_lock_level = new_level;
UPDATE_CURRENT_LOCK_LEVEL(kMutatorLock);
@@ -874,6 +883,14 @@
DCHECK(trace_lock_ == nullptr);
trace_lock_ = new Mutex("trace lock", current_lock_level);
+ UPDATE_CURRENT_LOCK_LEVEL(kDeoptimizationLock);
+ DCHECK(deoptimization_lock_ == nullptr);
+ deoptimization_lock_ = new Mutex("Deoptimization lock", current_lock_level);
+
+ UPDATE_CURRENT_LOCK_LEVEL(kAllocTrackerLock);
+ DCHECK(alloc_tracker_lock_ == nullptr);
+ alloc_tracker_lock_ = new Mutex("AllocTracker lock", current_lock_level);
+
UPDATE_CURRENT_LOCK_LEVEL(kThreadListLock);
DCHECK(thread_list_lock_ == nullptr);
thread_list_lock_ = new Mutex("thread list lock", current_lock_level);
@@ -905,7 +922,6 @@
DCHECK(intern_table_lock_ == nullptr);
intern_table_lock_ = new Mutex("InternTable lock", current_lock_level);
-
UPDATE_CURRENT_LOCK_LEVEL(kAbortLock);
DCHECK(abort_lock_ == nullptr);
abort_lock_ = new Mutex("abort lock", current_lock_level, true);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 8d89f96..89ae1ab 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -85,6 +85,7 @@
kMonitorListLock,
kThreadListLock,
kBreakpointInvokeLock,
+ kAllocTrackerLock,
kDeoptimizationLock,
kTraceLock,
kProfilerLock,
@@ -557,12 +558,20 @@
// Guards trace (ie traceview) requests.
static Mutex* trace_lock_ ACQUIRED_AFTER(profiler_lock_);
+ // Guards debugger recent allocation records.
+ static Mutex* alloc_tracker_lock_ ACQUIRED_AFTER(trace_lock_);
+
+ // Guards updates to instrumentation to ensure mutual exclusion of
+ // events like deoptimization requests.
+ // TODO: improve name, perhaps instrumentation_update_lock_.
+ static Mutex* deoptimization_lock_ ACQUIRED_AFTER(alloc_tracker_lock_);
+
// The thread_list_lock_ guards ThreadList::list_. It is also commonly held to stop threads
// attaching and detaching.
- static Mutex* thread_list_lock_ ACQUIRED_AFTER(trace_lock_);
+ static Mutex* thread_list_lock_ ACQUIRED_AFTER(deoptimization_lock_);
// Guards breakpoints.
- static ReaderWriterMutex* breakpoint_lock_ ACQUIRED_AFTER(thread_list_lock_);
+ static ReaderWriterMutex* breakpoint_lock_ ACQUIRED_AFTER(trace_lock_);
// Guards lists of classes within the class linker.
static ReaderWriterMutex* classlinker_classes_lock_ ACQUIRED_AFTER(breakpoint_lock_);
@@ -583,7 +592,7 @@
static Mutex* intern_table_lock_ ACQUIRED_AFTER(modify_ldt_lock_);
// Have an exclusive aborting thread.
- static Mutex* abort_lock_ ACQUIRED_AFTER(classlinker_classes_lock_);
+ static Mutex* abort_lock_ ACQUIRED_AFTER(intern_table_lock_);
// Allow mutual exclusion when manipulating Thread::suspend_count_.
// TODO: Does the trade-off of a per-thread lock make sense?
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 0092160..80ce081 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -61,7 +61,15 @@
namespace art {
static const size_t kMaxAllocRecordStackDepth = 16; // Max 255.
-static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2.
+static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2. 2BE can hold 64k-1.
+
+// Limit alloc_record_count to the 2BE value that is the limit of the current protocol.
+static uint16_t CappedAllocRecordCount(size_t alloc_record_count) {
+ if (alloc_record_count > 0xffff) {
+ return 0xffff;
+ }
+ return alloc_record_count;
+}
class AllocRecordStackTraceElement {
public:
@@ -116,9 +124,10 @@
}
void Dbg::TypeCache::Clear() {
- ScopedObjectAccess soa(Thread::Current());
+ JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+ Thread* self = Thread::Current();
for (const auto& p : objects_) {
- soa.Vm()->DeleteWeakGlobalRef(soa.Self(), p.second);
+ vm->DeleteWeakGlobalRef(self, p.second);
}
objects_.clear();
}
@@ -131,8 +140,9 @@
return down_cast<mirror::Class*>(Thread::Current()->DecodeJObject(type_));
}
- void SetType(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- type_ = Dbg::GetTypeCache().Add(t);
+ void SetType(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_,
+ Locks::alloc_tracker_lock_) {
+ type_ = Dbg::type_cache_.Add(t);
}
size_t GetDepth() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
@@ -304,7 +314,6 @@
static ObjectRegistry* gRegistry = nullptr;
// Recent allocation tracking.
-Mutex* Dbg::alloc_tracker_lock_ = nullptr;
AllocRecord* Dbg::recent_allocation_records_ = nullptr; // TODO: CircularBuffer<AllocRecord>
size_t Dbg::alloc_record_max_ = 0;
size_t Dbg::alloc_record_head_ = 0;
@@ -312,7 +321,6 @@
Dbg::TypeCache Dbg::type_cache_;
// Deoptimization support.
-Mutex* Dbg::deoptimization_lock_ = nullptr;
std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_;
size_t Dbg::full_deoptimization_event_count_ = 0;
size_t Dbg::delayed_full_undeoptimization_count_ = 0;
@@ -640,8 +648,6 @@
CHECK(gRegistry == nullptr);
gRegistry = new ObjectRegistry;
- alloc_tracker_lock_ = new Mutex("AllocTracker lock");
- deoptimization_lock_ = new Mutex("deoptimization lock", kDeoptimizationLock);
// Init JDWP if the debugger is enabled. This may connect out to a
// debugger, passively listen for a debugger, or block waiting for a
// debugger.
@@ -675,10 +681,6 @@
gJdwpState = nullptr;
delete gRegistry;
gRegistry = nullptr;
- delete alloc_tracker_lock_;
- alloc_tracker_lock_ = nullptr;
- delete deoptimization_lock_;
- deoptimization_lock_ = nullptr;
}
void Dbg::GcDidFinish() {
@@ -745,7 +747,7 @@
}
{
- MutexLock mu(Thread::Current(), *deoptimization_lock_);
+ MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
CHECK_EQ(deoptimization_requests_.size(), 0U);
CHECK_EQ(full_deoptimization_event_count_, 0U);
CHECK_EQ(delayed_full_undeoptimization_count_, 0U);
@@ -790,7 +792,7 @@
// Since we're going to disable deoptimization, we clear the deoptimization requests queue.
// This prevents us from having any pending deoptimization request when the debugger attaches
// to us again while no event has been requested yet.
- MutexLock mu(Thread::Current(), *deoptimization_lock_);
+ MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
deoptimization_requests_.clear();
full_deoptimization_event_count_ = 0U;
delayed_full_undeoptimization_count_ = 0U;
@@ -2896,7 +2898,7 @@
}
void Dbg::DelayFullUndeoptimization() {
- MutexLock mu(Thread::Current(), *deoptimization_lock_);
+ MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
++delayed_full_undeoptimization_count_;
DCHECK_LE(delayed_full_undeoptimization_count_, full_deoptimization_event_count_);
}
@@ -2904,7 +2906,7 @@
void Dbg::ProcessDelayedFullUndeoptimizations() {
// TODO: avoid taking the lock twice (once here and once in ManageDeoptimization).
{
- MutexLock mu(Thread::Current(), *deoptimization_lock_);
+ MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
while (delayed_full_undeoptimization_count_ > 0) {
DeoptimizationRequest req;
req.SetKind(DeoptimizationRequest::kFullUndeoptimization);
@@ -2921,7 +2923,7 @@
// Nothing to do.
return;
}
- MutexLock mu(Thread::Current(), *deoptimization_lock_);
+ MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
RequestDeoptimizationLocked(req);
}
@@ -2999,7 +3001,7 @@
Thread* const self = Thread::Current();
{
// Avoid suspend/resume if there is no pending request.
- MutexLock mu(self, *deoptimization_lock_);
+ MutexLock mu(self, *Locks::deoptimization_lock_);
if (deoptimization_requests_.empty()) {
return;
}
@@ -3011,7 +3013,7 @@
runtime->GetThreadList()->SuspendAll();
const ThreadState old_state = self->SetStateUnsafe(kRunnable);
{
- MutexLock mu(self, *deoptimization_lock_);
+ MutexLock mu(self, *Locks::deoptimization_lock_);
size_t req_index = 0;
for (DeoptimizationRequest& request : deoptimization_requests_) {
VLOG(jdwp) << "Process deoptimization request #" << req_index++;
@@ -4290,30 +4292,40 @@
return kDefaultNumAllocRecords;
}
-void Dbg::SetAllocTrackingEnabled(bool enabled) {
- if (enabled) {
+void Dbg::SetAllocTrackingEnabled(bool enable) {
+ Thread* self = Thread::Current();
+ if (enable) {
{
- MutexLock mu(Thread::Current(), *alloc_tracker_lock_);
- if (recent_allocation_records_ == NULL) {
- alloc_record_max_ = GetAllocTrackerMax();
- LOG(INFO) << "Enabling alloc tracker (" << alloc_record_max_ << " entries of "
- << kMaxAllocRecordStackDepth << " frames, taking "
- << PrettySize(sizeof(AllocRecord) * alloc_record_max_) << ")";
- alloc_record_head_ = alloc_record_count_ = 0;
- recent_allocation_records_ = new AllocRecord[alloc_record_max_];
- CHECK(recent_allocation_records_ != NULL);
+ MutexLock mu(self, *Locks::alloc_tracker_lock_);
+ if (recent_allocation_records_ != NULL) {
+ return; // Already enabled, bail.
}
+ alloc_record_max_ = GetAllocTrackerMax();
+ LOG(INFO) << "Enabling alloc tracker (" << alloc_record_max_ << " entries of "
+ << kMaxAllocRecordStackDepth << " frames, taking "
+ << PrettySize(sizeof(AllocRecord) * alloc_record_max_) << ")";
+ DCHECK_EQ(alloc_record_head_, 0U);
+ DCHECK_EQ(alloc_record_count_, 0U);
+ recent_allocation_records_ = new AllocRecord[alloc_record_max_];
+ CHECK(recent_allocation_records_ != NULL);
}
Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints();
} else {
- Runtime::Current()->GetInstrumentation()->UninstrumentQuickAllocEntryPoints();
{
- MutexLock mu(Thread::Current(), *alloc_tracker_lock_);
+ ScopedObjectAccess soa(self); // For type_cache_.Clear();
+ MutexLock mu(self, *Locks::alloc_tracker_lock_);
+ if (recent_allocation_records_ == NULL) {
+ return; // Already disabled, bail.
+ }
LOG(INFO) << "Disabling alloc tracker";
delete[] recent_allocation_records_;
recent_allocation_records_ = NULL;
+ alloc_record_head_ = 0;
+ alloc_record_count_ = 0;
type_cache_.Clear();
}
+ // If an allocation comes in before we uninstrument, we will safely drop it on the floor.
+ Runtime::Current()->GetInstrumentation()->UninstrumentQuickAllocEntryPoints();
}
}
@@ -4353,8 +4365,9 @@
Thread* self = Thread::Current();
CHECK(self != NULL);
- MutexLock mu(self, *alloc_tracker_lock_);
+ MutexLock mu(self, *Locks::alloc_tracker_lock_);
if (recent_allocation_records_ == NULL) {
+ // In the process of shutting down recording, bail.
return;
}
@@ -4380,12 +4393,12 @@
// Returns the index of the head element.
//
-// We point at the most-recently-written record, so if gAllocRecordCount is 1
+// We point at the most-recently-written record, so if alloc_record_count_ is 1
// we want to use the current element. Take "head+1" and subtract count
// from it.
//
// We need to handle underflow in our circular buffer, so we add
-// gAllocRecordMax and then mask it back down.
+// alloc_record_max_ and then mask it back down.
size_t Dbg::HeadIndex() {
return (Dbg::alloc_record_head_ + 1 + Dbg::alloc_record_max_ - Dbg::alloc_record_count_) &
(Dbg::alloc_record_max_ - 1);
@@ -4393,7 +4406,7 @@
void Dbg::DumpRecentAllocations() {
ScopedObjectAccess soa(Thread::Current());
- MutexLock mu(soa.Self(), *alloc_tracker_lock_);
+ MutexLock mu(soa.Self(), *Locks::alloc_tracker_lock_);
if (recent_allocation_records_ == NULL) {
LOG(INFO) << "Not recording tracked allocations";
return;
@@ -4402,7 +4415,8 @@
// "i" is the head of the list. We want to start at the end of the
// list and move forward to the tail.
size_t i = HeadIndex();
- size_t count = alloc_record_count_;
+ const uint16_t capped_count = CappedAllocRecordCount(Dbg::alloc_record_count_);
+ uint16_t count = capped_count;
LOG(INFO) << "Tracked allocations, (head=" << alloc_record_head_ << " count=" << count << ")";
while (count--) {
@@ -4506,7 +4520,7 @@
* followed by UTF-16 data.
*
* We send up 16-bit unsigned indexes into string tables. In theory there
- * can be (kMaxAllocRecordStackDepth * gAllocRecordMax) unique strings in
+ * can be (kMaxAllocRecordStackDepth * alloc_record_max_) unique strings in
* each table, but in practice there should be far fewer.
*
* The chief reason for using a string table here is to keep the size of
@@ -4526,7 +4540,7 @@
Thread* self = Thread::Current();
std::vector<uint8_t> bytes;
{
- MutexLock mu(self, *alloc_tracker_lock_);
+ MutexLock mu(self, *Locks::alloc_tracker_lock_);
//
// Part 1: generate string tables.
//
@@ -4534,8 +4548,9 @@
StringTable method_names;
StringTable filenames;
- int count = alloc_record_count_;
- int idx = HeadIndex();
+ const uint16_t capped_count = CappedAllocRecordCount(Dbg::alloc_record_count_);
+ uint16_t count = capped_count;
+ size_t idx = HeadIndex();
while (count--) {
AllocRecord* record = &recent_allocation_records_[idx];
std::string temp;
@@ -4552,7 +4567,7 @@
idx = (idx + 1) & (alloc_record_max_ - 1);
}
- LOG(INFO) << "allocation records: " << alloc_record_count_;
+ LOG(INFO) << "allocation records: " << capped_count;
//
// Part 2: Generate the output and store it in the buffer.
@@ -4573,7 +4588,7 @@
// (2b) number of class name strings
// (2b) number of method name strings
// (2b) number of source file name strings
- JDWP::Append2BE(bytes, alloc_record_count_);
+ JDWP::Append2BE(bytes, capped_count);
size_t string_table_offset = bytes.size();
JDWP::Append4BE(bytes, 0); // We'll patch this later...
JDWP::Append2BE(bytes, class_names.Size());
@@ -4582,7 +4597,7 @@
idx = HeadIndex();
std::string temp;
- for (count = alloc_record_count_; count != 0; --count) {
+ for (count = capped_count; count != 0; --count) {
// For each entry:
// (4b) total allocation size
// (2b) thread id
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 1d3668c..02d98ce 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -192,9 +192,11 @@
class TypeCache {
public:
// Returns a weak global for the input type. Deduplicates.
- jobject Add(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ jobject Add(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_,
+ Locks::alloc_tracker_lock_);
// Clears the type cache and deletes all the weak global refs.
- void Clear();
+ void Clear() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_,
+ Locks::alloc_tracker_lock_);
private:
std::multimap<int32_t, jobject> objects_;
@@ -221,8 +223,8 @@
*/
static void Connected();
static void GoActive()
- LOCKS_EXCLUDED(Locks::breakpoint_lock_, deoptimization_lock_, Locks::mutator_lock_);
- static void Disconnected() LOCKS_EXCLUDED(deoptimization_lock_, Locks::mutator_lock_);
+ LOCKS_EXCLUDED(Locks::breakpoint_lock_, Locks::deoptimization_lock_, Locks::mutator_lock_);
+ static void Disconnected() LOCKS_EXCLUDED(Locks::deoptimization_lock_, Locks::mutator_lock_);
static void Disposed();
// Returns true if we're actually debugging with a real debugger, false if it's
@@ -492,20 +494,20 @@
// Records deoptimization request in the queue.
static void RequestDeoptimization(const DeoptimizationRequest& req)
- LOCKS_EXCLUDED(deoptimization_lock_)
+ LOCKS_EXCLUDED(Locks::deoptimization_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
// Support delayed full undeoptimization requests. This is currently only used for single-step
// events.
- static void DelayFullUndeoptimization() LOCKS_EXCLUDED(deoptimization_lock_);
+ static void DelayFullUndeoptimization() LOCKS_EXCLUDED(Locks::deoptimization_lock_);
static void ProcessDelayedFullUndeoptimizations()
- LOCKS_EXCLUDED(deoptimization_lock_)
+ LOCKS_EXCLUDED(Locks::deoptimization_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
// Manage deoptimization after updating JDWP events list. Suspends all threads, processes each
// request and finally resumes all threads.
static void ManageDeoptimization()
- LOCKS_EXCLUDED(deoptimization_lock_)
+ LOCKS_EXCLUDED(Locks::deoptimization_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
// Breakpoints.
@@ -559,17 +561,17 @@
* Recent allocation tracking support.
*/
static void RecordAllocation(mirror::Class* type, size_t byte_count)
- LOCKS_EXCLUDED(alloc_tracker_lock_)
+ LOCKS_EXCLUDED(Locks::alloc_tracker_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- static void SetAllocTrackingEnabled(bool enabled) LOCKS_EXCLUDED(alloc_tracker_lock_);
+ static void SetAllocTrackingEnabled(bool enabled) LOCKS_EXCLUDED(Locks::alloc_tracker_lock_);
static bool IsAllocTrackingEnabled() {
return recent_allocation_records_ != nullptr;
}
static jbyteArray GetRecentAllocations()
- LOCKS_EXCLUDED(alloc_tracker_lock_)
+ LOCKS_EXCLUDED(Locks::alloc_tracker_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- static size_t HeadIndex() EXCLUSIVE_LOCKS_REQUIRED(alloc_tracker_lock_);
- static void DumpRecentAllocations() LOCKS_EXCLUDED(alloc_tracker_lock_);
+ static size_t HeadIndex() EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_);
+ static void DumpRecentAllocations() LOCKS_EXCLUDED(Locks::alloc_tracker_lock_);
enum HpifWhen {
HPIF_WHEN_NEVER = 0,
@@ -595,10 +597,6 @@
static void DdmSendHeapSegments(bool native)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- static TypeCache& GetTypeCache() {
- return type_cache_;
- }
-
private:
static void DdmBroadcast(bool connect) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
static void PostThreadStartOrStop(Thread*, uint32_t)
@@ -616,52 +614,47 @@
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
static void RequestDeoptimizationLocked(const DeoptimizationRequest& req)
- EXCLUSIVE_LOCKS_REQUIRED(deoptimization_lock_)
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::deoptimization_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- static Mutex* alloc_tracker_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-
- static AllocRecord* recent_allocation_records_ PT_GUARDED_BY(alloc_tracker_lock_);
- static size_t alloc_record_max_ GUARDED_BY(alloc_tracker_lock_);
- static size_t alloc_record_head_ GUARDED_BY(alloc_tracker_lock_);
- static size_t alloc_record_count_ GUARDED_BY(alloc_tracker_lock_);
-
- // Guards deoptimization requests.
- // TODO rename to instrumentation_update_lock.
- static Mutex* deoptimization_lock_ ACQUIRED_AFTER(Locks::breakpoint_lock_);
+ static AllocRecord* recent_allocation_records_ PT_GUARDED_BY(Locks::alloc_tracker_lock_);
+ static size_t alloc_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_);
+ static size_t alloc_record_head_ GUARDED_BY(Locks::alloc_tracker_lock_);
+ static size_t alloc_record_count_ GUARDED_BY(Locks::alloc_tracker_lock_);
// Deoptimization requests to be processed each time the event list is updated. This is used when
// registering and unregistering events so we do not deoptimize while holding the event list
// lock.
// TODO rename to instrumentation_requests.
- static std::vector<DeoptimizationRequest> deoptimization_requests_ GUARDED_BY(deoptimization_lock_);
+ static std::vector<DeoptimizationRequest> deoptimization_requests_ GUARDED_BY(Locks::deoptimization_lock_);
// Count the number of events requiring full deoptimization. When the counter is > 0, everything
// is deoptimized, otherwise everything is undeoptimized.
// Note: we fully deoptimize on the first event only (when the counter is set to 1). We fully
// undeoptimize when the last event is unregistered (when the counter is set to 0).
- static size_t full_deoptimization_event_count_ GUARDED_BY(deoptimization_lock_);
+ static size_t full_deoptimization_event_count_ GUARDED_BY(Locks::deoptimization_lock_);
// Count the number of full undeoptimization requests delayed to next resume or end of debug
// session.
- static size_t delayed_full_undeoptimization_count_ GUARDED_BY(deoptimization_lock_);
+ static size_t delayed_full_undeoptimization_count_ GUARDED_BY(Locks::deoptimization_lock_);
static size_t* GetReferenceCounterForEvent(uint32_t instrumentation_event);
// Weak global type cache, TODO improve this.
- static TypeCache type_cache_;
+ static TypeCache type_cache_ GUARDED_BY(Locks::alloc_tracker_lock_);
// Instrumentation event reference counters.
// TODO we could use an array instead of having all these dedicated counters. Instrumentation
// events are bits of a mask so we could convert them to array index.
- static size_t dex_pc_change_event_ref_count_ GUARDED_BY(deoptimization_lock_);
- static size_t method_enter_event_ref_count_ GUARDED_BY(deoptimization_lock_);
- static size_t method_exit_event_ref_count_ GUARDED_BY(deoptimization_lock_);
- static size_t field_read_event_ref_count_ GUARDED_BY(deoptimization_lock_);
- static size_t field_write_event_ref_count_ GUARDED_BY(deoptimization_lock_);
- static size_t exception_catch_event_ref_count_ GUARDED_BY(deoptimization_lock_);
+ static size_t dex_pc_change_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
+ static size_t method_enter_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
+ static size_t method_exit_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
+ static size_t field_read_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
+ static size_t field_write_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
+ static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_);
+ friend class AllocRecord; // For type_cache_ with proper annotalysis.
DISALLOW_COPY_AND_ASSIGN(Dbg);
};
diff --git a/test/098-ddmc/expected.txt b/test/098-ddmc/expected.txt
new file mode 100644
index 0000000..f8cda4c
--- /dev/null
+++ b/test/098-ddmc/expected.txt
@@ -0,0 +1,23 @@
+Confirm empty
+empty=Allocations[message header len: 15 entry header len: 9 stack frame len: 8 number of entries: 0 offset to string table from start of message: 15 number of class name strings: 0 number of method name strings: 0 number of source file name strings: 0]
+Confirm enable
+status=false
+status=true
+Capture some allocations (note just this causes allocations)
+before > 0=true
+Confirm when we overflow, we don't roll over to zero. b/17392248
+before < overflowAllocations=true
+after > before=true
+after.numberOfEntries=65535
+Disable and confirm back to empty
+status=false
+reset=Allocations[message header len: 15 entry header len: 9 stack frame len: 8 number of entries: 0 offset to string table from start of message: 15 number of class name strings: 0 number of method name strings: 0 number of source file name strings: 0]
+Confirm we can disable twice in a row
+status=false
+status=false
+Confirm we can reenable twice in a row without losing allocations
+status=true
+status=true
+second > first =true
+Goodbye
+goodbye=Allocations[message header len: 15 entry header len: 9 stack frame len: 8 number of entries: 0 offset to string table from start of message: 15 number of class name strings: 0 number of method name strings: 0 number of source file name strings: 0]
diff --git a/test/098-ddmc/info.txt b/test/098-ddmc/info.txt
new file mode 100644
index 0000000..39d26db
--- /dev/null
+++ b/test/098-ddmc/info.txt
@@ -0,0 +1 @@
+Tests of private org.apache.harmony.dalvik.ddmc.* APIs used for ddms support.
diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java
new file mode 100644
index 0000000..962bd7f
--- /dev/null
+++ b/test/098-ddmc/src/Main.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 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.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ String name = System.getProperty("java.vm.name");
+ if (!"Dalvik".equals(name)) {
+ System.out.println("This test is not supported on " + name);
+ return;
+ }
+ testRecentAllocationTracking();
+ }
+
+ private static void testRecentAllocationTracking() throws Exception {
+ System.out.println("Confirm empty");
+ Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("empty=" + empty);
+
+ System.out.println("Confirm enable");
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+ DdmVmInternal.enableRecentAllocations(true);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+
+ System.out.println("Capture some allocations (note just this causes allocations)");
+ Allocations before = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("before > 0=" + (before.numberOfEntries > 0));
+
+ System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248");
+ final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value.
+ for (int i = 0; i < overflowAllocations; i++) {
+ new String("fnord");
+ }
+ Allocations after = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations));
+ System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries));
+ System.out.println("after.numberOfEntries=" + after.numberOfEntries);
+
+ System.out.println("Disable and confirm back to empty");
+ DdmVmInternal.enableRecentAllocations(false);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+ Allocations reset = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("reset=" + reset);
+
+ System.out.println("Confirm we can disable twice in a row");
+ DdmVmInternal.enableRecentAllocations(false);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+ DdmVmInternal.enableRecentAllocations(false);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+
+ System.out.println("Confirm we can reenable twice in a row without losing allocations");
+ DdmVmInternal.enableRecentAllocations(true);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+ for (int i = 0; i < 16 * 1024; i++) {
+ new String("fnord");
+ }
+ Allocations first = new Allocations(DdmVmInternal.getRecentAllocations());
+ DdmVmInternal.enableRecentAllocations(true);
+ System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
+ Allocations second = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("second > first =" + (second.numberOfEntries > first.numberOfEntries));
+
+ System.out.println("Goodbye");
+ DdmVmInternal.enableRecentAllocations(false);
+ Allocations goodbye = new Allocations(DdmVmInternal.getRecentAllocations());
+ System.out.println("goodbye=" + goodbye);
+ }
+
+ private static class Allocations {
+ final int messageHeaderLen;
+ final int entryHeaderLen;
+ final int stackFrameLen;
+ final int numberOfEntries;
+ final int offsetToStringTableFromStartOfMessage;
+ final int numberOfClassNameStrings;
+ final int numberOfMethodNameStrings;
+ final int numberOfSourceFileNameStrings;
+
+ Allocations(byte[] allocations) {
+ ByteBuffer b = ByteBuffer.wrap(allocations);
+ messageHeaderLen = b.get() & 0xff;
+ if (messageHeaderLen != 15) {
+ throw new IllegalArgumentException("Unexpected messageHeaderLen " + messageHeaderLen);
+ }
+ entryHeaderLen = b.get() & 0xff;
+ if (entryHeaderLen != 9) {
+ throw new IllegalArgumentException("Unexpected entryHeaderLen " + entryHeaderLen);
+ }
+ stackFrameLen = b.get() & 0xff;
+ if (stackFrameLen != 8) {
+ throw new IllegalArgumentException("Unexpected messageHeaderLen " + stackFrameLen);
+ }
+ numberOfEntries = b.getShort() & 0xffff;
+ offsetToStringTableFromStartOfMessage = b.getInt();
+ numberOfClassNameStrings = b.getShort() & 0xffff;
+ numberOfMethodNameStrings = b.getShort() & 0xffff;
+ numberOfSourceFileNameStrings = b.getShort() & 0xffff;
+ }
+
+ public String toString() {
+ return ("Allocations[message header len: " + messageHeaderLen +
+ " entry header len: " + entryHeaderLen +
+ " stack frame len: " + stackFrameLen +
+ " number of entries: " + numberOfEntries +
+ " offset to string table from start of message: " + offsetToStringTableFromStartOfMessage +
+ " number of class name strings: " + numberOfClassNameStrings +
+ " number of method name strings: " + numberOfMethodNameStrings +
+ " number of source file name strings: " + numberOfSourceFileNameStrings +
+ "]");
+ }
+ }
+
+ private static class DdmVmInternal {
+ private static final Method enableRecentAllocationsMethod;
+ private static final Method getRecentAllocationStatusMethod;
+ private static final Method getRecentAllocationsMethod;
+ static {
+ try {
+ Class c = Class.forName("org.apache.harmony.dalvik.ddmc.DdmVmInternal");
+ enableRecentAllocationsMethod = c.getDeclaredMethod("enableRecentAllocations",
+ Boolean.TYPE);
+ getRecentAllocationStatusMethod = c.getDeclaredMethod("getRecentAllocationStatus");
+ getRecentAllocationsMethod = c.getDeclaredMethod("getRecentAllocations");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void enableRecentAllocations(boolean enable) throws Exception {
+ enableRecentAllocationsMethod.invoke(null, enable);
+ }
+ public static boolean getRecentAllocationStatus() throws Exception {
+ return (boolean) getRecentAllocationStatusMethod.invoke(null);
+ }
+ public static byte[] getRecentAllocations() throws Exception {
+ return (byte[]) getRecentAllocationsMethod.invoke(null);
+ }
+ }
+}