Androidオブジェクトの実際のサイズ📏
Header image: Deep Dive by Romain Guy.
私は現在どのようにリークカナリアcomputes the retained heap size of objects . クイックリマインダーとして:
オブジェクトの浅いヒープサイズ:メモリ内のオブジェクトサイズ.
オブジェクトのヒープサイズを保持します.オブジェクトの浅いサイズに加え、そのオブジェクトだけでメモリ内で一時的に保持されるすべてのオブジェクトの浅いサイズに加えます.つまり、そのオブジェクトがガベージコレクションされているときに解放されるメモリ量です.
浅いサイズを信頼できない
その仕事の一部として、私はYourKit、Eclipse Memory Analyzer Tools(MAT)とAndroid Studio Memory Analyzerなどの他のヒープダンプツールで報告されているオブジェクトの浅いサイズを比較しました.それは私が何かが間違って実現したときです:すべてのツールは、別の答えを提供します.
私はそれについてジェシー・ウィルソンに尋ねました、そして、彼はアレクシーShipilWhat Heap Dumps Are Lying To You About . いくつかの覚醒:
インExploring Java's Hidden Costs , ジェイクWhartonは、Julを使う方法を示しました.残念なことに、JOLはJVMSだけでなく、DalvikやArt Runtimeで動作します.ジェイクを引用する
In this case, because the classes are exactly the same and the JVM 64 bit, and Android's now 64 bit, the number should be translatable. If that bothers you, treat them as an approximation and allow for some 20% variance. It's certainly a lot easier than figuring out how to get the object sizes on Android itself. Which is not impossible, it's just a lot easier this way.
ダム、私たちが持っているすべてのリークキャラはヒープダンプです.我々は簡単な方法を行く必要がありますね!
私はロマン人にこれについて尋ねました、そして、彼はJVM TI , Android 8 +に実装されているエージェントインターフェイスART TI . JVM Ti露出GetObjectSize API.
ソースを読む
Learn to Read the Source, Luke - Coding Horror
Androidはオープンソースなので、いつも私たちの質問に対する答えを見つけることができます.限り、我々は検索する場所を知っている限り!
JVMチタン
ここでの実装ですJVM TI GetObjectSize() :
jvmtiError ObjectUtil::GetObjectSize(env* env ATTRIBUTE_UNUSED,
jobject jobject,
jlong* size_ptr) {
art::ObjPtr<art::mirror::Object> object =
soa.Decode<art::mirror::Object>(jobject);
*size_ptr = object->SizeOf();
return ERR(NONE);
}
興味深いコードはObject::SizeOf() :
template<VerifyObjectFlags kVFlags>
inline size_t Object::SizeOf() {
size_t result;
constexpr VerifyObjectFlags kNewFlags = RemoveThisFlags(kVFlags);
if (IsArrayInstance<kVFlags>()) {
result = AsArray<kNewFlags>()->template SizeOf<kNewFlags>();
} else if (IsClass<kNewFlags>()) {
result = AsClass<kNewFlags>()->template SizeOf<kNewFlags>();
} else if (IsString<kNewFlags>()) {
result = AsString<kNewFlags>()->template SizeOf<kNewFlags>();
} else {
result = GetClass<kNewFlags, kWithoutReadBarrier>()
->template GetObjectSize<kNewFlags>();
}
return result;
}
インスタンスサイズ
最後のインスタンスサイズに注目しましょうelse
その条件で.オブジェクトがインスタンスである場合、そのサイズはGetClass()->GetObjectSize()
を返すobject_size_ in class.h :
// Total object size; used when allocating storage on gc heap.
// (For interfaces and abstract classes this will be zero.)
// See also class_size_.
uint32_t object_size_;
インスタンス割り当て
これを実際に使用しているメモリのインスタンスの大きさをチェックしてみましょう.我々はそれを見つけるobject_size_
はClass::Alloc() in class-alloc-inl.h
template<bool kIsInstrumented, Class::AddFinalizer kAddFinalizer,
bool kCheckAddFinalizer>
inline ObjPtr<Object> Class::Alloc(Thread* self,
gc::AllocatorType allocator_type) {
gc::Heap* heap = Runtime::Current()->GetHeap();
return heap->AllocObjectWithAllocator<kIsInstrumented, false>(
self, this, this->object_size_, allocator_type,
VoidFunctor()
);
}
したがって、オブジェクトに割り当てられた実際のメモリはobject_size_ in class.h .
Note: the memory allocated by Heap::AllocObjectWithAllocator() in heap-inl.h might be rounded up to a multiple of 8 when using a Thread-local bump allocator (TLAB, see Trash Talk by Chet Haase and Romain Guy). However the default CMS GC does not use that allocator.
クラスリンク
より多くの用法を見たobject_size_
我々はそれが設定されてClassLinker::LinkFields() in class_linker.cc クラスのリンク
bool ClassLinker::LinkFields(Thread* self,
Handle<mirror::Class> klass,
bool is_static,
size_t* class_size) {
MemberOffset field_offset(0);
ObjPtr<mirror::Class> super_class = klass->GetSuperClass();
if (super_class != nullptr) {
field_offset = MemberOffset(super_class->GetObjectSize());
}
// ... code that increases field_offset as fields are added
size_t size = field_offset.Uint32Value();
klass->SetObjectSize(size);
return true;
}
ヒープダンプに戻る
インスタンスの実際のサイズを取得する方法を知ったので、Androidヒープダンプで報告されているインスタンスサイズと比較しましょう.
ヒープダンプをトリガーするときDebug.dumpHprofData() , VM呼び出しDumpHeap() in hprof.cc . 見ましょうHprof::DumpHeapClass()
, より具体的にはinstance size of a class is added :
// Instance size.
if (klass->IsClassClass()) {
// As mentioned above, we will emit instance fields as
// synthetic static fields. So the base object is "empty."
__ AddU4(0);
} else if (klass->IsStringClass()) {
// Strings are variable length with character data at the end
// like arrays. This outputs the size of an empty string.
__ AddU4(sizeof(mirror::String));
} else if (klass->IsArrayClass() || klass->IsPrimitive()) {
__ AddU4(0);
} else {
__ AddU4(klass->GetObjectSize()); // instance size
}
最後のelse
その条件では、ほとんどのインスタンスのインスタンスサイズであり、もう一度object_size_ in class.h .
したがって、オープンJDKヒープダンプとは異なり、Androidのヒープダンプには実際のメモリインスタンスのサイズが含まれます.
ヒープダンプ記録の探索
インExploring Java's Hidden Costs , JakeはJulからの出力を示しましたandroid.util.SparseArray
:
android.util.SparseArray object internals:
SIZE TYPE DESCRIPTION
4 (object header)
4 (object header)
4 (object header)
4 int SparseArray.mSize
1 boolean SparseArray.mGarbage
3 (alignment/padding gap)
4 int[] SparseArray.mKeys
4 Object[] SparseArray.mValues
4 (loss due to the next object alignment)
Instance size: 32 bytes
リークキャリーヒープダンプパーサーを使いましょうShark ) Androidヒープダンプレポートを参照するには、次の手順に従います.
val hprofFile = "heap_dump_android_o.hprof".classpathFile()
val sparseArraySize = hprofFile.openHeapGraph().use { graph ->
graph.findClassByName("android.util.SparseArray")!!.instanceByteSize
}
println("Instance size: $sparseArraySize bytes")
結果:
Instance size: 21 bytes
いいね、Julによって報告された32バイトより少ない方法です!
報告された分野の詳細を見てみましょう.
val description = hprofFile.openHeapGraph().use { graph ->
graph.findClassByName("android.util.SparseArray")!!
.classHierarchy
.flatMap { clazz ->
clazz.readRecord().fields.map { field ->
val fieldSize = if (field.type == REFERENCE_HPROF_TYPE)
graph.identifierByteSize
else
byteSizeByHprofType.getValue(field.type)
val typeName =
if (field.type == REFERENCE_HPROF_TYPE)
"REF"
else
primitiveTypeByHprofType.getValue(field.type).name
val className = clazz.name
val fieldName = clazz.instanceFieldName(field)
"$fieldSize $typeName $className#$fieldName"
}.asSequence()
}.joinToString("\n")
}
println(description)
結果:
1 BOOLEAN android.util.SparseArray#mGarbage
4 REF android.util.SparseArray#mKeys
4 INT android.util.SparseArray#mSize
4 REF android.util.SparseArray#mValues
4 REF java.lang.Object#shadow$_klass_
4 INT java.lang.Object#shadow$_monitor_
したがって、すべてのSparseRayインスタンスは21バイトの浅いサイズを持ちます.そして、それはオブジェクト・クラスから8バイトとそれ自身のフィールドのために13バイトを含みます.と0バイト無駄!
ギャップとアライメント
ClassLinker::LinkFields() in class_linker.cc メモリ内のすべてのフィールドの位置を決定します.
Learn to Read the Source, Luke - Coding Horror
jvmtiError ObjectUtil::GetObjectSize(env* env ATTRIBUTE_UNUSED,
jobject jobject,
jlong* size_ptr) {
art::ObjPtr<art::mirror::Object> object =
soa.Decode<art::mirror::Object>(jobject);
*size_ptr = object->SizeOf();
return ERR(NONE);
}
template<VerifyObjectFlags kVFlags>
inline size_t Object::SizeOf() {
size_t result;
constexpr VerifyObjectFlags kNewFlags = RemoveThisFlags(kVFlags);
if (IsArrayInstance<kVFlags>()) {
result = AsArray<kNewFlags>()->template SizeOf<kNewFlags>();
} else if (IsClass<kNewFlags>()) {
result = AsClass<kNewFlags>()->template SizeOf<kNewFlags>();
} else if (IsString<kNewFlags>()) {
result = AsString<kNewFlags>()->template SizeOf<kNewFlags>();
} else {
result = GetClass<kNewFlags, kWithoutReadBarrier>()
->template GetObjectSize<kNewFlags>();
}
return result;
}
// Total object size; used when allocating storage on gc heap.
// (For interfaces and abstract classes this will be zero.)
// See also class_size_.
uint32_t object_size_;
template<bool kIsInstrumented, Class::AddFinalizer kAddFinalizer,
bool kCheckAddFinalizer>
inline ObjPtr<Object> Class::Alloc(Thread* self,
gc::AllocatorType allocator_type) {
gc::Heap* heap = Runtime::Current()->GetHeap();
return heap->AllocObjectWithAllocator<kIsInstrumented, false>(
self, this, this->object_size_, allocator_type,
VoidFunctor()
);
}
Note: the memory allocated by Heap::AllocObjectWithAllocator() in heap-inl.h might be rounded up to a multiple of 8 when using a Thread-local bump allocator (TLAB, see Trash Talk by Chet Haase and Romain Guy). However the default CMS GC does not use that allocator.
bool ClassLinker::LinkFields(Thread* self,
Handle<mirror::Class> klass,
bool is_static,
size_t* class_size) {
MemberOffset field_offset(0);
ObjPtr<mirror::Class> super_class = klass->GetSuperClass();
if (super_class != nullptr) {
field_offset = MemberOffset(super_class->GetObjectSize());
}
// ... code that increases field_offset as fields are added
size_t size = field_offset.Uint32Value();
klass->SetObjectSize(size);
return true;
}
インスタンスの実際のサイズを取得する方法を知ったので、Androidヒープダンプで報告されているインスタンスサイズと比較しましょう.
ヒープダンプをトリガーするときDebug.dumpHprofData() , VM呼び出しDumpHeap() in hprof.cc . 見ましょう
Hprof::DumpHeapClass()
, より具体的にはinstance size of a class is added :// Instance size.
if (klass->IsClassClass()) {
// As mentioned above, we will emit instance fields as
// synthetic static fields. So the base object is "empty."
__ AddU4(0);
} else if (klass->IsStringClass()) {
// Strings are variable length with character data at the end
// like arrays. This outputs the size of an empty string.
__ AddU4(sizeof(mirror::String));
} else if (klass->IsArrayClass() || klass->IsPrimitive()) {
__ AddU4(0);
} else {
__ AddU4(klass->GetObjectSize()); // instance size
}
最後のelse
その条件では、ほとんどのインスタンスのインスタンスサイズであり、もう一度object_size_ in class.h .したがって、オープンJDKヒープダンプとは異なり、Androidのヒープダンプには実際のメモリインスタンスのサイズが含まれます.
ヒープダンプ記録の探索
インExploring Java's Hidden Costs , JakeはJulからの出力を示しました
android.util.SparseArray
:android.util.SparseArray object internals:
SIZE TYPE DESCRIPTION
4 (object header)
4 (object header)
4 (object header)
4 int SparseArray.mSize
1 boolean SparseArray.mGarbage
3 (alignment/padding gap)
4 int[] SparseArray.mKeys
4 Object[] SparseArray.mValues
4 (loss due to the next object alignment)
Instance size: 32 bytes
リークキャリーヒープダンプパーサーを使いましょうShark ) Androidヒープダンプレポートを参照するには、次の手順に従います.val hprofFile = "heap_dump_android_o.hprof".classpathFile()
val sparseArraySize = hprofFile.openHeapGraph().use { graph ->
graph.findClassByName("android.util.SparseArray")!!.instanceByteSize
}
println("Instance size: $sparseArraySize bytes")
結果:Instance size: 21 bytes
いいね、Julによって報告された32バイトより少ない方法です!報告された分野の詳細を見てみましょう.
val description = hprofFile.openHeapGraph().use { graph ->
graph.findClassByName("android.util.SparseArray")!!
.classHierarchy
.flatMap { clazz ->
clazz.readRecord().fields.map { field ->
val fieldSize = if (field.type == REFERENCE_HPROF_TYPE)
graph.identifierByteSize
else
byteSizeByHprofType.getValue(field.type)
val typeName =
if (field.type == REFERENCE_HPROF_TYPE)
"REF"
else
primitiveTypeByHprofType.getValue(field.type).name
val className = clazz.name
val fieldName = clazz.instanceFieldName(field)
"$fieldSize $typeName $className#$fieldName"
}.asSequence()
}.joinToString("\n")
}
println(description)
結果:1 BOOLEAN android.util.SparseArray#mGarbage
4 REF android.util.SparseArray#mKeys
4 INT android.util.SparseArray#mSize
4 REF android.util.SparseArray#mValues
4 REF java.lang.Object#shadow$_klass_
4 INT java.lang.Object#shadow$_monitor_
したがって、すべてのSparseRayインスタンスは21バイトの浅いサイズを持ちます.そして、それはオブジェクト・クラスから8バイトとそれ自身のフィールドのために13バイトを含みます.と0バイト無駄!ギャップとアライメント
ClassLinker::LinkFields() in class_linker.cc メモリ内のすべてのフィールドの位置を決定します.
Class::GetObjectSize()
. Nは何か、奇数でさえありえました.親クラスがギャップ(未使用のバイト)を持つ場合、これらは触れません.reference
then long
then int
then char
then boolean
. open class Parent {
val myChar = 'a'
val myBool1 = true
val myBool2 = false
}
class Child : Parent() {
val ref1 = Any()
val ref2 = Any()
val myLong = 0L
}
# java.lang.Object is 8 bytes
4 REF java.lang.Object#shadow$_klass_
4 INT java.lang.Object#shadow$_monitor_
# com.example.Parent is 8 + 3 = 11 bytes
2 CHAR com.example.Parent#myChar
1 BOOLEAN com.example.Parent#myBool1
1 BOOLEAN com.example.Parent#myBool2
# com.example.Child is 11 + 21 = 32 bytes
1 GAP for 4 byte alignment for refs
4 REF com.example.Child#ref1
4 REF com.example.Child#ref2
4 GAP for 8 byte alignment for long
8 LONG com.example.Child#myLong
ヒアcom.example.Child
は、5バイトを含む32バイトで、フィールドアライメントに無駄です.open class Parent {
val myChar = 0
val myBool1 = true
val myBool2 = false
}
class Child : Parent() {
val ref1 = Any()
val ref2 = Any()
val myLong = 0L
// Added myInt and myBool3
val myInt = 0
val myBool3 = true
}
# java.lang.Object is 8 bytes
4 REF java.lang.Object#shadow$_klass_
4 INT java.lang.Object#shadow$_monitor_
# com.example.Parent is 8 + 3 = 11 bytes
2 CHAR com.example.Parent#myChar
1 BOOLEAN com.example.Parent#myBool1
1 BOOLEAN com.example.Parent#myBool2
# com.example.Child is 11 + 21 = still 32 bytes!
1 BOOLEAN com.example.Parent#myBool3 (1 byte gap)
4 REF com.example.Child#ref1
4 REF com.example.Child#ref2
4 INT com.example.Child#myInt (4 byte gap)
8 LONG com.example.Child#myLong
この例では、intとbooleanChild
インスタンスのサイズは変更されません.結論
Reference
この問題について(Androidオブジェクトの実際のサイズ📏), 我々は、より多くの情報をここで見つけました https://dev.to/pyricau/the-real-size-of-android-objects-1i2eテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol