Add VMDebug.getInstancesOfClasses API.
The API can be used to iterate over instances of a given type on the
heap.
The GetInstancesOfClassesBenchmark run on bullhead shows the run time
of the API grows linearly in the number of classes, C, to get instances
of and the number of total instances, N, allocated on the heap.
C N=~2^18 N=~2^19
1 13ms 21ms
2 26ms 43ms
4 53ms 87ms
Bug: 69729799
Test: ./test/testrunner/testrunner.py -t 099-vmdebug -b --host
Change-Id: Ied053d19760e656012e2577776f75a1cc0a14ac3
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 1dcd935..13029fb 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -963,7 +963,11 @@
}
VariableSizedHandleScope hs(Thread::Current());
std::vector<Handle<mirror::Object>> raw_instances;
- Runtime::Current()->GetHeap()->GetInstances(hs, hs.NewHandle(c), max_count, raw_instances);
+ Runtime::Current()->GetHeap()->GetInstances(hs,
+ hs.NewHandle(c),
+ /* use_is_assignable_from */ false,
+ max_count,
+ raw_instances);
for (size_t i = 0; i < raw_instances.size(); ++i) {
instances->push_back(gRegistry->Add(raw_instances[i].Get()));
}
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 9f62666..f29ae92 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -1796,19 +1796,25 @@
return GetBytesFreedEver() + GetBytesAllocated();
}
+// Check whether the given object is an instance of the given class.
+static bool MatchesClass(mirror::Object* obj,
+ Handle<mirror::Class> h_class,
+ bool use_is_assignable_from) REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* instance_class = obj->GetClass();
+ CHECK(instance_class != nullptr);
+ ObjPtr<mirror::Class> klass = h_class.Get();
+ if (use_is_assignable_from) {
+ return klass != nullptr && klass->IsAssignableFrom(instance_class);
+ }
+ return instance_class == klass;
+}
+
void Heap::CountInstances(const std::vector<Handle<mirror::Class>>& classes,
bool use_is_assignable_from,
uint64_t* counts) {
auto instance_counter = [&](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::Class* instance_class = obj->GetClass();
- CHECK(instance_class != nullptr);
for (size_t i = 0; i < classes.size(); ++i) {
- ObjPtr<mirror::Class> klass = classes[i].Get();
- if (use_is_assignable_from) {
- if (klass != nullptr && klass->IsAssignableFrom(instance_class)) {
- ++counts[i];
- }
- } else if (instance_class == klass) {
+ if (MatchesClass(obj, classes[i], use_is_assignable_from)) {
++counts[i];
}
}
@@ -1818,11 +1824,12 @@
void Heap::GetInstances(VariableSizedHandleScope& scope,
Handle<mirror::Class> h_class,
+ bool use_is_assignable_from,
int32_t max_count,
std::vector<Handle<mirror::Object>>& instances) {
DCHECK_GE(max_count, 0);
auto instance_collector = [&](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (obj->GetClass() == h_class.Get()) {
+ if (MatchesClass(obj, h_class, use_is_assignable_from)) {
if (max_count == 0 || instances.size() < static_cast<size_t>(max_count)) {
instances.push_back(scope.NewHandle(obj));
}
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 4d7424c..ac0d82e 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -346,9 +346,10 @@
REQUIRES(!Locks::heap_bitmap_lock_, !*gc_complete_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Implements JDWP RT_Instances.
+ // Implements VMDebug.getInstancesOfClasses and JDWP RT_Instances.
void GetInstances(VariableSizedHandleScope& scope,
Handle<mirror::Class> c,
+ bool use_is_assignable_from,
int32_t max_count,
std::vector<Handle<mirror::Object>>& instances)
REQUIRES(!Locks::heap_bitmap_lock_, !*gc_complete_lock_)
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 2663bea..88a78ab 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -319,6 +319,53 @@
return soa.AddLocalReference<jlongArray>(long_counts);
}
+static jobjectArray VMDebug_getInstancesOfClasses(JNIEnv* env,
+ jclass,
+ jobjectArray javaClasses,
+ jboolean includeAssignable) {
+ ScopedObjectAccess soa(env);
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ObjectArray<mirror::Class>> classes = hs.NewHandle(
+ soa.Decode<mirror::ObjectArray<mirror::Class>>(javaClasses));
+ if (classes == nullptr) {
+ return nullptr;
+ }
+
+ jclass object_array_class = env->FindClass("[Ljava/lang/Object;");
+ if (env->ExceptionCheck() == JNI_TRUE) {
+ return nullptr;
+ }
+ CHECK(object_array_class != nullptr);
+
+ size_t num_classes = classes->GetLength();
+ jobjectArray result = env->NewObjectArray(num_classes, object_array_class, nullptr);
+ if (env->ExceptionCheck() == JNI_TRUE) {
+ return nullptr;
+ }
+
+ gc::Heap* const heap = Runtime::Current()->GetHeap();
+ MutableHandle<mirror::Class> h_class(hs.NewHandle<mirror::Class>(nullptr));
+ for (size_t i = 0; i < num_classes; ++i) {
+ h_class.Assign(classes->Get(i));
+
+ VariableSizedHandleScope hs2(soa.Self());
+ std::vector<Handle<mirror::Object>> raw_instances;
+ heap->GetInstances(hs2, h_class, includeAssignable, /* max_count */ 0, raw_instances);
+ jobjectArray array = env->NewObjectArray(raw_instances.size(),
+ WellKnownClasses::java_lang_Object,
+ nullptr);
+ if (env->ExceptionCheck() == JNI_TRUE) {
+ return nullptr;
+ }
+
+ for (size_t j = 0; j < raw_instances.size(); ++j) {
+ env->SetObjectArrayElement(array, j, raw_instances[j].ToJObject());
+ }
+ env->SetObjectArrayElement(result, i, array);
+ }
+ return result;
+}
+
// We export the VM internal per-heap-space size/alloc/free metrics
// for the zygote space, alloc space (application heap), and the large
// object space for dumpsys meminfo. The other memory region data such
@@ -534,6 +581,7 @@
NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
NATIVE_METHOD(VMDebug, getHeapSpaceStats, "([J)V"),
+ NATIVE_METHOD(VMDebug, getInstancesOfClasses, "([Ljava/lang/Class;Z)[[Ljava/lang/Object;"),
NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
FAST_NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
diff --git a/test/099-vmdebug/expected.txt b/test/099-vmdebug/expected.txt
index b8d72f6..f7801de 100644
--- a/test/099-vmdebug/expected.txt
+++ b/test/099-vmdebug/expected.txt
@@ -23,3 +23,9 @@
Instances of ClassA assignable 3
Array counts [2, 1, 0]
Array counts assignable [3, 1, 0]
+ClassD got 3, combined mask: 13
+ClassE got 2, combined mask: 18
+null got 0
+ClassD assignable got 5, combined mask: 31
+ClassE assignable got 2, combined mask: 18
+null assignable got 0
diff --git a/test/099-vmdebug/info.txt b/test/099-vmdebug/info.txt
index 7f88086..873429e 100644
--- a/test/099-vmdebug/info.txt
+++ b/test/099-vmdebug/info.txt
@@ -1 +1 @@
-Tests of private dalvik.system.VMDebug support for method tracing.
+Tests of dalvik.system.VMDebug APIs.
diff --git a/test/099-vmdebug/src/Main.java b/test/099-vmdebug/src/Main.java
index 90ad315..e0d829a 100644
--- a/test/099-vmdebug/src/Main.java
+++ b/test/099-vmdebug/src/Main.java
@@ -33,6 +33,7 @@
}
testMethodTracing();
testCountInstances();
+ testGetInstances();
testRuntimeStat();
testRuntimeStats();
}
@@ -249,6 +250,59 @@
System.out.println("Array counts assignable " + Arrays.toString(counts));
}
+ static class ClassD {
+ public int mask;
+
+ public ClassD(int mask) {
+ this.mask = mask;
+ }
+ }
+
+ static class ClassE extends ClassD {
+ public ClassE(int mask) {
+ super(mask);
+ }
+ }
+
+ private static void testGetInstances() throws Exception {
+ ArrayList<Object> l = new ArrayList<Object>();
+ l.add(new ClassD(0x01));
+ l.add(new ClassE(0x02));
+ l.add(new ClassD(0x04));
+ l.add(new ClassD(0x08));
+ l.add(new ClassE(0x10));
+ Runtime.getRuntime().gc();
+ Class<?>[] classes = new Class<?>[] {ClassD.class, ClassE.class, null};
+ Object[][] instances = VMDebug.getInstancesOfClasses(classes, false);
+
+ int mask = 0;
+ for (Object instance : instances[0]) {
+ mask |= ((ClassD)instance).mask;
+ }
+ System.out.println("ClassD got " + instances[0].length + ", combined mask: " + mask);
+
+ mask = 0;
+ for (Object instance : instances[1]) {
+ mask |= ((ClassD)instance).mask;
+ }
+ System.out.println("ClassE got " + instances[1].length + ", combined mask: " + mask);
+ System.out.println("null got " + instances[2].length);
+
+ instances = VMDebug.getInstancesOfClasses(classes, true);
+ mask = 0;
+ for (Object instance : instances[0]) {
+ mask |= ((ClassD)instance).mask;
+ }
+ System.out.println("ClassD assignable got " + instances[0].length + ", combined mask: " + mask);
+
+ mask = 0;
+ for (Object instance : instances[1]) {
+ mask |= ((ClassD)instance).mask;
+ }
+ System.out.println("ClassE assignable got " + instances[1].length + ", combined mask: " + mask);
+ System.out.println("null assignable got " + instances[2].length);
+ }
+
private static class VMDebug {
private static final Method startMethodTracingMethod;
private static final Method stopMethodTracingMethod;
@@ -257,6 +311,7 @@
private static final Method getRuntimeStatsMethod;
private static final Method countInstancesOfClassMethod;
private static final Method countInstancesOfClassesMethod;
+ private static final Method getInstancesOfClassesMethod;
static {
try {
Class<?> c = Class.forName("dalvik.system.VMDebug");
@@ -270,6 +325,8 @@
Class.class, Boolean.TYPE);
countInstancesOfClassesMethod = c.getDeclaredMethod("countInstancesOfClasses",
Class[].class, Boolean.TYPE);
+ getInstancesOfClassesMethod = c.getDeclaredMethod("getInstancesOfClasses",
+ Class[].class, Boolean.TYPE);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -300,5 +357,9 @@
return (long[]) countInstancesOfClassesMethod.invoke(
null, new Object[]{classes, assignable});
}
+ public static Object[][] getInstancesOfClasses(Class<?>[] classes, boolean assignable) throws Exception {
+ return (Object[][]) getInstancesOfClassesMethod.invoke(
+ null, new Object[]{classes, assignable});
+ }
}
}