x64 환경에서 참조형의 기본 메모리 소비는 얼마나 될까요?
지난 글이 x86 실습으로 한 것이라서,
일반 참조형의 기본 메모리 소비는 얼마나 될까요?
; https://www.sysnet.pe.kr/2/0/1174
.NET Array는 왜 12bytes의 기본 메모리를 점유할까?
; https://www.sysnet.pe.kr/2/0/1173
x64 환경에서 실습하는 분들은 헷갈리는 경우가 있는 듯해서 이번엔 64비트 환경에서 테스트하는 것으로 진행해 보겠습니다.
예제를 위해, 코드는 다음과 같이 작성하고,
namespace ConsoleApplication1
{
class MyType
{
}
class Program
{
static void Main(string[] args)
{
MyType inst = new MyType();
Thread.Sleep(-1);
}
}
}
windbg로 확인하면,
0:004> .loadby sos clr
0:000> !name2ee ConsoleApplication1!ConsoleApplication1.MyType
Module: 00007fff789d4148
Assembly: ConsoleApplication1.exe
Token: 0000000002000002
MethodTable: 00007fff789d5b00
EEClass: 00007fff789d25f8
Name: ConsoleApplication1.MyType
0:000> !dumpheap -mt 00007fff789d5b00
Address MT Size
0000000003e931b8 00007fff789d5b00 24
Statistics:
MT Count TotalSize Class Name
00007fff789d5b00 1 24 ConsoleApplication1.MyType
Total 1 objects
0:000> !do 0000000003e931b8
Name: ConsoleApplication1.MyType
MethodTable: 00007fff789d5b00
EEClass: 00007fff789d25f8
Size: 24(0x18) bytes
File: C:\temp\ConsoleApplication1\bin\x64\Debug\ConsoleApplication1.exe
Fields:
None
0:000> !dumpclass 00007fff789d25f8
Class Name: ConsoleApplication1.MyType
mdToken: 0000000002000002
File: C:\temp\ConsoleApplication1\bin\x64\Debug\ConsoleApplication1.exe
Parent Class: 00007fffd4372f68
Module: 00007fff789d4148
Method Table: 00007fff789d5b00
Vtable Slots: 4
Total Method Slots: 5
Class Attributes: 100000
Transparency: Critical
NumInstanceFields: 0
NumStaticFields: 0
0:000> dq 0000000003e931b8
00000000`03e931b8 00007fff`789d5b00 00000000`00000000
00000000`03e931c8 00000000`00000000 00000000`00000000
00000000`03e931d8 00000000`00000000 00000000`00000000
00000000`03e931e8 00000000`00000000 00000000`00000000
00000000`03e931f8 00000000`00000000 00000000`00000000
00000000`03e93208 00000000`00000000 00000000`00000000
00000000`03e93218 00000000`00000000 00000000`00000000
00000000`03e93228 00000000`00000000 00000000`00000000
보시는 바와 같이, 24(0x18)bytes를 소비하고 있습니다. 왜냐하면 빈 객체라고 해도 데이터 공간으로 기본 8바이트를 점유하고 있기 때문입니다. (참고로, 00007fff`789d5b00 데이터 앞의 8바이트 위치에는 OBJECT HEADER가 위치하고 있습니다.)
-8 : OBJECT HEADER(예: SyncBlock Index를 담는 용도)
+0 : MethodTable Address
+8 : 빈 값
그럼, int (4바이트) 형의 필드를 추가하면 어떻게 될까요?
namespace ConsoleApplication1
{
class MyType
{
int test = 0xFF;
}
class Program
{
static void Main(string[] args)
{
MyType inst = new MyType();
Thread.Sleep(-1);
}
}
}
다시 메모리 값을 확인해 보면 다음과 같이 나옵니다.
0:000> !name2ee ConsoleApplication1!ConsoleApplication1.MyType
Module: 00007fff789f4148
Assembly: ConsoleApplication1.exe
Token: 0000000002000002
MethodTable: 00007fff789f5b10
EEClass: 00007fff789f2600
Name: ConsoleApplication1.MyType
0:000> !dumpheap -mt 00007fff789f5b10
Address MT Size
00000000035131b8 00007fff789f5b10 24
Statistics:
MT Count TotalSize Class Name
00007fff789f5b10 1 24 ConsoleApplication1.MyType
Total 1 objects
0:000> !dumpclass 00007fff789f2600
Class Name: ConsoleApplication1.MyType
mdToken: 0000000002000002
File: C:\temp\ConsoleApplication1\bin\x64\Debug\ConsoleApplication1.exe
Parent Class: 00007fffd4372f68
Module: 00007fff789f4148
Method Table: 00007fff789f5b10
Vtable Slots: 4
Total Method Slots: 5
Class Attributes: 100000
Transparency: Critical
NumInstanceFields: 1
NumStaticFields: 0
MT Field Offset Type VT Attr Value Name
00007fffd43985a0 4000001 8 System.Int32 1 instance test
0:000> dq 00000000035131b8
00000000`035131b8 00007fff`789f5b10 00000000`000000ff
00000000`035131c8 00000000`00000000 00000000`00000000
00000000`035131d8 00000000`00000000 00000000`00000000
00000000`035131e8 00000000`00000000 00000000`00000000
00000000`035131f8 00000000`00000000 00000000`00000000
00000000`03513208 00000000`00000000 00000000`00000000
00000000`03513218 00000000`00000000 00000000`00000000
00000000`03513228 00000000`00000000 00000000`00000000
0:000> dd 00000000035131b8
00000000`032031b8 789f5b10 00007fff 000000ff 00000000
00000000`032031c8 00000000 00000000 00000000 00000000
00000000`032031d8 00000000 00000000 00000000 00000000
00000000`032031e8 00000000 00000000 00000000 00000000
00000000`032031f8 00000000 00000000 00000000 00000000
00000000`03203208 00000000 00000000 00000000 00000000
00000000`03203218 00000000 00000000 00000000 00000000
00000000`03203228 00000000 00000000 00000000 00000000
0:000> !do 00000000035131b8
Name: ConsoleApplication1.MyType
MethodTable: 00007fff789f5b10
EEClass: 00007fff789f2600
Size: 24(0x18) bytes
File: C:\temp\ConsoleApplication1\bin\x64\Debug\ConsoleApplication1.exe
Fields:
MT Field Offset Type VT Attr Value Name
00007fffd43985a0 4000001 8 System.Int32 1 instance 255 test
-8 : OBJECT HEADER(예: SyncBlock Index를 담는 용도)
+0 : MethodTable Address
+8 : 이후 4바이트 영역에 int 값
필드를 하나 더 포함하고 있는데도 여전히 24bytes가 소비되고 있습니다. 왜냐하면 필드를 포함하지 않았을 때도 00000000`00000000으로 기본 포함되어 있던 영역이 사용되고 있기 때문입니다. 이 상태에서 다시 int 4바이트 필드를 하나 더 추가하면 8바이트 중 남은 4바이트를 사용하기 때문에 메모리는 여전히 24바이트가 소비됩니다.
-8 : OBJECT HEADER(예: SyncBlock Index를 담는 용도)
+0 : MethodTable Address
+8 : 이후 4바이트 영역에 int 값
+12: 이후 4바이트 영역에 int 값
하지만 int 4바이트 필드를 하나 더 추가하면 32바이트로 늘어납니다. (즉, 8바이트 정렬 단위로 메모리가 증가합니다.)
배열도 테스트해야겠지요? ^^
{
MyType[] arr0 = new MyType[0];
MyType[] arr1 = new MyType[1];
arr1[0] = new MyType();
MyType[] arr2 = new MyType[2];
arr2[0] = new MyType();
arr2[1] = new MyType();
Thread.Sleep(-1);
}
이를 windbg로 확인하면 아래와 같은 결과가 나옵니다.
0:000> !dumpheap -stat -type ConsoleApplication1.MyType[]
Statistics:
MT Count TotalSize Class Name
00007fff789e5bc0 3 96 ConsoleApplication1.MyType[]
Total 3 objects
0:000> !dumpheap -mt 00007fff789e5bc0
Address MT Size
00000000033631b8 00007fff789e5bc0 24
00000000033631d0 00007fff789e5bc0 32
0000000003363210 00007fff789e5bc0 40
Statistics:
MT Count TotalSize Class Name
00007fff789e5bc0 3 96 ConsoleApplication1.MyType[]
Total 3 objects
// 요소가 0개인 배열
0:000> dq 00000000033631b8-8 L3
00000000`033631b0 00000000`00000000 00007fff`789e5bc0
00000000`033631c0 00000000`00000000
// 요소가 1개인 배열
0:000> dq 00000000033631d0-8 L4
00000000`033631c8 00000000`00000000 00007fff`789e5bc0
00000000`033631d8 00000000`00000001 00000000`033631f0
0:000> !do 00000000`033631f0
Name: ConsoleApplication1.MyType
MethodTable: 00007fff789e5b30
EEClass: 00007fff789e2610
Size: 32(0x20) bytes
File: C:\temp\ConsoleApplication1\bin\x64\Debug\ConsoleApplication1.exe
Fields:
MT Field Offset Type VT Attr Value Name
00007fffd43985a0 4000001 8 System.Int32 1 instance 255 test
00007fffd43985a0 4000002 c System.Int32 1 instance 238 test2
00007fffd43985a0 4000003 10 System.Int32 1 instance 221 test3
// 요소가 2개인 배열
0:000> dq 0000000003363210-8 L5
00000000`03363208 00000000`00000000 00007fff`789e5bc0
00000000`03363218 00000000`00000002 00000000`03363238
00000000`03363228 00000000`03363258
따라서, x64 환경에서의 .NET Array는 다음과 같은 구조를 갖습니다.
-8 : OBJECT HEADER(예: SyncBlock Index를 담는 용도)
+0 : MethodTable Address
+8 : 배열 요소 크기
+16 ~ : 값 배열
이로써, 그동안의 이야기를 정리해 보면 아래와 같습니다.
[x86 기준]
0 크기 배열: 12 bytes
배열이 아닌 일반 참조형 개체: 12bytes
[x64 기준]
0 크기 배열: 24 bytes
배열이 아닌 일반 참조형 개체: 24bytes
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]