ART: Add allocation callback
Bug: 31684277
Test: m test-art-host
Change-Id: I959f44e23ca5fe55ed678315708895faf0aadb04
diff --git a/runtime/gc/allocation_listener.h b/runtime/gc/allocation_listener.h
new file mode 100644
index 0000000..6fb74d3
--- /dev/null
+++ b/runtime/gc/allocation_listener.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_RUNTIME_GC_ALLOCATION_LISTENER_H_
+#define ART_RUNTIME_GC_ALLOCATION_LISTENER_H_
+
+#include <list>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "object_callbacks.h"
+#include "gc_root.h"
+
+namespace art {
+
+namespace mirror {
+ class Object;
+}
+
+class Thread;
+
+namespace gc {
+
+class AllocationListener {
+ public:
+ virtual ~AllocationListener() {}
+
+ virtual void ObjectAllocated(Thread* self, mirror::Object** obj, size_t byte_count)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
+} // namespace gc
+} // namespace art
+
+#endif // ART_RUNTIME_GC_ALLOCATION_LISTENER_H_
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 83789cc..00adefb 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -19,6 +19,7 @@
#include "heap.h"
+#include "allocation_listener.h"
#include "base/time_utils.h"
#include "gc/accounting/card_table-inl.h"
#include "gc/allocation_record.h"
@@ -184,6 +185,12 @@
DCHECK(allocation_records_ != nullptr);
allocation_records_->RecordAllocation(self, &obj, bytes_allocated);
}
+ AllocationListener* l = alloc_listener_.LoadSequentiallyConsistent();
+ if (l != nullptr) {
+ // Same as above. We assume that a listener that was once stored will never be deleted.
+ // Otherwise we'd have to perform this under a lock.
+ l->ObjectAllocated(self, &obj, bytes_allocated);
+ }
} else {
DCHECK(!IsAllocTrackingEnabled());
}
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 01ad8d0..33f849a 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -21,6 +21,7 @@
#include <unwind.h> // For GC verification.
#include <vector>
+#include "allocation_listener.h"
#include "art_field-inl.h"
#include "base/allocator.h"
#include "base/arena_allocator.h"
@@ -1287,6 +1288,16 @@
}
}
+ALWAYS_INLINE
+static inline AllocationListener* GetAndOverwriteAllocationListener(
+ Atomic<AllocationListener*>* storage, AllocationListener* new_value) {
+ AllocationListener* old;
+ do {
+ old = storage->LoadSequentiallyConsistent();
+ } while (!storage->CompareExchangeStrongSequentiallyConsistent(old, new_value));
+ return old;
+}
+
Heap::~Heap() {
VLOG(heap) << "Starting ~Heap()";
STLDeleteElements(&garbage_collectors_);
@@ -1307,6 +1318,10 @@
<< " total=" << seen_backtrace_count_.LoadRelaxed() +
unique_backtrace_count_.LoadRelaxed();
}
+ // Delete any still registered allocation listener.
+ AllocationListener* l = GetAndOverwriteAllocationListener(&alloc_listener_, nullptr);
+ delete l;
+
VLOG(heap) << "Finished ~Heap()";
}
@@ -4223,5 +4238,22 @@
}
}
+void Heap::SetAllocationListener(AllocationListener* l) {
+ AllocationListener* old = GetAndOverwriteAllocationListener(&alloc_listener_, l);
+
+ if (old == nullptr) {
+ Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints();
+ }
+}
+
+void Heap::RemoveAllocationListener() {
+ AllocationListener* old = GetAndOverwriteAllocationListener(&alloc_listener_, nullptr);
+
+ if (old != nullptr) {
+ Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints();
+ }
+}
+
+
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 678edff..5e17a52 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -57,6 +57,7 @@
namespace gc {
+class AllocationListener;
class AllocRecordObjectMap;
class ReferenceProcessor;
class TaskProcessor;
@@ -784,6 +785,12 @@
HomogeneousSpaceCompactResult PerformHomogeneousSpaceCompact() REQUIRES(!*gc_complete_lock_);
bool SupportHomogeneousSpaceCompactAndCollectorTransitions() const;
+ // Install an allocation listener.
+ void SetAllocationListener(AllocationListener* l);
+ // Remove an allocation listener. Note: the listener must not be deleted, as for performance
+ // reasons, we assume it stays valid when we read it (so that we don't require a lock).
+ void RemoveAllocationListener();
+
private:
class ConcurrentGCTask;
class CollectorTransitionTask;
@@ -1352,6 +1359,9 @@
// Boot image spaces.
std::vector<space::ImageSpace*> boot_image_spaces_;
+ // An installed allocation listener.
+ Atomic<AllocationListener*> alloc_listener_;
+
friend class CollectorTransitionTask;
friend class collector::GarbageCollector;
friend class collector::MarkCompact;
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index b298301..d027201 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -29,6 +29,9 @@
return nullptr;
}
+ // TODO: Add a type check. Can be done, for example, by an explicitly instantiated template
+ // function.
+
switch (event) {
case JVMTI_EVENT_VM_INIT:
return reinterpret_cast<FnType*>(env->event_callbacks->VMInit);
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index 48f3de4..4d5b7e0 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -29,9 +29,17 @@
* questions.
*/
-#include "events.h"
+#include "events-inl.h"
#include "art_jvmti.h"
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "instrumentation.h"
+#include "jni_env_ext-inl.h"
+#include "mirror/class.h"
+#include "mirror/object.h"
+#include "runtime.h"
+#include "ScopedLocalRef.h"
namespace openjdkjvmti {
@@ -116,8 +124,65 @@
}
}
+class JvmtiAllocationListener : public art::gc::AllocationListener {
+ public:
+ explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {}
+
+ void ObjectAllocated(art::Thread* self, art::mirror::Object** obj, size_t byte_count)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK_EQ(self, art::Thread::Current());
+
+ if (handler_->IsEventEnabledAnywhere(JVMTI_EVENT_VM_OBJECT_ALLOC)) {
+ // jvmtiEventVMObjectAlloc parameters:
+ // jvmtiEnv *jvmti_env,
+ // JNIEnv* jni_env,
+ // jthread thread,
+ // jobject object,
+ // jclass object_klass,
+ // jlong size
+ art::JNIEnvExt* jni_env = self->GetJniEnv();
+
+ jthread thread_peer;
+ if (self->IsStillStarting()) {
+ thread_peer = nullptr;
+ } else {
+ thread_peer = jni_env->AddLocalReference<jthread>(self->GetPeer());
+ }
+
+ ScopedLocalRef<jthread> thread(jni_env, thread_peer);
+ ScopedLocalRef<jobject> object(
+ jni_env, jni_env->AddLocalReference<jobject>(*obj));
+ ScopedLocalRef<jclass> klass(
+ jni_env, jni_env->AddLocalReference<jclass>((*obj)->GetClass()));
+
+ handler_->DispatchEvent(self,
+ JVMTI_EVENT_VM_OBJECT_ALLOC,
+ jni_env,
+ thread.get(),
+ object.get(),
+ klass.get(),
+ byte_count);
+ }
+ }
+
+ private:
+ EventHandler* handler_;
+};
+
+static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, bool enable) {
+ if (enable) {
+ art::Runtime::Current()->GetHeap()->SetAllocationListener(listener);
+ } else {
+ art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+ }
+}
+
// Handle special work for the given event type, if necessary.
-static void HandleEventType(jvmtiEvent event ATTRIBUTE_UNUSED, bool enable ATTRIBUTE_UNUSED) {
+void EventHandler::HandleEventType(jvmtiEvent event, bool enable) {
+ if (event == JVMTI_EVENT_VM_OBJECT_ALLOC) {
+ SetupObjectAllocationTracking(alloc_listener_.get(), enable);
+ return;
+ }
}
jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env,
@@ -172,4 +237,11 @@
return ERR(NONE);
}
+EventHandler::EventHandler() {
+ alloc_listener_.reset(new JvmtiAllocationListener(this));
+}
+
+EventHandler::~EventHandler() {
+}
+
} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h
index 5716d03..3212b12 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/runtime/openjdkjvmti/events.h
@@ -27,6 +27,7 @@
namespace openjdkjvmti {
struct ArtJvmTiEnv;
+class JvmtiAllocationListener;
struct EventMask {
static constexpr size_t kEventsSize = JVMTI_MAX_EVENT_TYPE_VAL - JVMTI_MIN_EVENT_TYPE_VAL + 1;
@@ -71,12 +72,10 @@
};
// Helper class for event handling.
-struct EventHandler {
- // List of all JvmTiEnv objects that have been created, in their creation order.
- std::vector<ArtJvmTiEnv*> envs;
-
- // A union of all enabled events, anywhere.
- EventMask global_mask;
+class EventHandler {
+ public:
+ EventHandler();
+ ~EventHandler();
// Register an env. It is assumed that this happens on env creation, that is, no events are
// enabled, yet.
@@ -93,6 +92,17 @@
template <typename ...Args>
ALWAYS_INLINE inline void DispatchEvent(art::Thread* thread, jvmtiEvent event, Args... args);
+
+ private:
+ void HandleEventType(jvmtiEvent event, bool enable);
+
+ // List of all JvmTiEnv objects that have been created, in their creation order.
+ std::vector<ArtJvmTiEnv*> envs;
+
+ // A union of all enabled events, anywhere.
+ EventMask global_mask;
+
+ std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
};
} // namespace openjdkjvmti