Microsoft MVP성태의 닷넷 이야기
.NET Framework: 729. windbg로 살펴보는 GC heap의 Segment 구조 [링크 복사], [링크+제목 복사]
조회: 14447
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
[test.pptx]    
(연관된 글이 3개 있습니다.)
(시리즈 글이 9개 있습니다.)
.NET Framework: 497. .NET Garbage Collection에 대한 정리
; https://www.sysnet.pe.kr/2/0/1862

.NET Framework: 728. windbg - 눈으로 확인하는 Workstation GC / Server GC
; https://www.sysnet.pe.kr/2/0/11445

.NET Framework: 729. windbg로 살펴보는 GC heap의 Segment 구조
; https://www.sysnet.pe.kr/2/0/11446

.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap)
; https://www.sysnet.pe.kr/2/0/12545

.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12572

.NET Framework: 1059. 세대 별 GC(Garbage Collection) 방식에서 Card table의 사용 의미
; https://www.sysnet.pe.kr/2/0/12649

.NET Framework: 1060. 닷넷 GC에 새롭게 구현되는 DPAD(Dynamic Promotion And Demotion for GC)
; https://www.sysnet.pe.kr/2/0/12653

.NET Framework: 2024. .NET 7에 도입된 GC의 메모리 해제에 대한 segment와 region의 차이점
; https://www.sysnet.pe.kr/2/0/13083

닷넷: 2209. .NET 8 - NonGC Heap / FOH (Frozen Object Heap)
; https://www.sysnet.pe.kr/2/0/13536




windbg로 살펴보는 GC heap의 Segment 구조

지난번 GC 설명 글에서,

.NET Garbage Collection에 대한 정리
; https://www.sysnet.pe.kr/2/0/1862

링크한 다음의 글에는 Segment에 대한 이야기가 나옵니다.

How does the GC work and what are the sizes of the different generations?
; https://blogs.msdn.microsoft.com/tess/2008/04/17/how-does-the-gc-work-and-what-are-the-sizes-of-the-different-generations/
; https://www.tessferrandez.com/blog/2008/04/17/how-does-the-gc-work.html

이 내용을 한번 정리해 보겠습니다. ^^

우선, 위의 글에서 예로 든 Server GC를 사용하는 응용 프로그램의 경우 총 192MB 크기의 힙이 점유되어 있는데, (2 CPU를 가진 시스템의) Server GC이므로 다음과 같은 구성으로 잡혀 있습니다.

Heap 0:
    Small Object용 segment 64MB
    Large Object용 segment 32MB

Heap 1:
    Small Object용 segment 64MB
    Large Object용 segment 32MB

당연히 Gen0, Gen1, Gen2객체는 64MB로 할당되는 small object용 segment에서 시작합니다.

이것을 확인해 볼까요? ^^ 간단하게 코드를 만들어,

// .NET 4.7 + 32bit

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press ENTER key to exit...");
            Console.ReadLine();
        }
    }
}

실행 후 windbg로 연결해 힙을 보면,

0:007> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x04bf1018
generation 1 starts at 0x04bf100c
generation 2 starts at 0x04bf1000
ephemeral segment allocation context: none
 segment     begin  allocated      size
04bf0000  04bf1000  04bf5ff4  0x4ff4(20468)
Large object heap starts at 0x05bf1000
 segment     begin  allocated      size
05bf0000  05bf1000  05bf5500  0x4500(17664)
Total Size:              Size: 0x94f4 (38132) bytes.
------------------------------
GC Heap Size:    Size: 0x94f4 (38132) bytes.

콘솔 프로그램이기 때문에 Workstation GC가 사용되어 GC Heap의 수는 1입니다. 그리고 "ephemeral segment", 즉 small object heap으로 1개의 segment가 할당되어 있고,

 segment     begin  allocated      size
04bf0000  04bf1000  04bf5ff4  0x4ff4(20468)

Large object heap으로 또 1개의 segment가 할당되어 있습니다.

 segment     begin  allocated      size
05bf0000  05bf1000  05bf5500  0x4500(17664)

SOH(Small Object Heap)의 경우 segment 시작 주소는 04bf0000이지만 begin은 04bf1000로 나옵니다. 개인적인 의견으로 앞의 0x1000 바이트는 예약된 영역이 아닌가 생각됩니다. 그런 다음 할당이 04bf5ff4로 나오는데 04bf5ff4 - 04bf0000 = 0x5ff4로 겨우 24,564 바이트에 불과합니다. 아니... 전에는 64MB가 할당된다고 하면서 왜 24,564바이트일까요? 그 이유는, 64MB는 "예약(reserved)"영역이고 0x5ff4 바이트 공간은 commit 영역이기 때문입니다.

여기서 아쉬운 것은 "How does the GC work and what are the sizes of the different generations?" 글에서 소개할 당시의 sos 확장에서는 예약 영역의 크기까지도 출력이 되는 반면 현재 버전의 sos 확장에서는 그 부분이 누락되었습니다. 그렇긴 해도 해당 글에서 설명한 !address 명령어를 이용해 예약 크기를 살펴보는 것이 가능합니다.

예를 들어, SOH segment의 시작 주소 04bf0000를 !address로 살펴보면,

0:007> !address 04bf0000  
                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...

Usage:                  <unknown>
Base Address:           04bf0000
End Address:            04c02000
Region Size:            00012000 (  72.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        04bf0000
Allocation Protect:     00000004          PAGE_READWRITE


Content source: 1 (target), length: 12000

현재 commit된 페이지가 04bf0000 ~ 04c02000까지로 0x12000(73,728, 약 72KB)만큼의 크기가 됩니다. 이후 공간을 살펴볼까요?

0:007> !address 04c02000

Usage:                  <unknown>
Base Address:           04c02000
End Address:            05bf0000
Region Size:            00fee000 (  15.930 MB)
State:                  00002000          MEM_RESERVE
Protect:                <info not present at the target>
Type:                   00020000          MEM_PRIVATE
Allocation Base:        04bf0000
Allocation Protect:     00000004          PAGE_READWRITE

보는 바와 같이 05bf0000 주소까지 15.930MB만큼 예약(reserved)되어 있습니다. 그러니까, SOH의 연속 공간으로 총 16MB의 가상 주소가 점유된 것입니다. 이것을 그림으로 정리하면 다음과 같이 됩니다.

gc_segment_1.png

그런데 역시 이번에도 64MB가 아닙니다. 왜냐하면, "How does the GC work and what are the sizes of the different generations?" 글에서 언급한 것처럼,

How much it will allocate is depends on what framework version you use (including service packs or hotfixes), if you are running on x64 or x86 and what GC flavor the application is using (workstation or server)

환경에 따라 다르기 때문입니다. 즉, x86 + .NET 4.7의 Workstation GC에서는 SOH의 한 Segment 크기가 16MB입니다. 실제로 이 프로그램에서 Server GC 유형으로 설정해 실행하면 SOH의 한 Segment 크기가 32MB로 바뀌고 논리 CPU 만큼 생성됩니다. 예를 들어, 8개의 논리 CPU를 가진 시스템에서 x86 + .NET 4.7 + Server GC로 수행하면 32MB SOH 힙이 8개가 생성되어 총 256MB가 할당됩니다.




이어서 LOH도 유사하게 살펴볼 수 있는데요. 가만 보면, SOH의 예약 공간 주소의 끝 주소가 05bf0000로 되어 있고 LOH의 시작 주소가 05bf0000인 걸로 봐서 CLR은 초기 응용 프로그램 시작에서 SOH 예약 공간 다음에 이어서 LOH 공간을 예약하는 것으로 보입니다.

!address 명령어를 통해 살펴보면,

0:007> !address 05bf0000  

Usage:                  <unknown>
Base Address:           05bf0000
End Address:            05c02000
Region Size:            00012000 (  72.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        04bf0000
Allocation Protect:     00000004          PAGE_READWRITE


Content source: 1 (target), length: 12000

0:007> !address 05c02000

Usage:                  <unknown>
Base Address:           05c02000
End Address:            06bf0000
Region Size:            00fee000 (  15.930 MB)
State:                  00002000          MEM_RESERVE
Protect:                <info not present at the target>
Type:                   00020000          MEM_PRIVATE
Allocation Base:        04bf0000
Allocation Protect:     00000004          PAGE_READWRITE

최초 commit은 72KB이고 총 예약 크기는 16MB임을 알 수 있습니다. 동일한 프로그램을 Server GC로 보면 LOH의 commit 크기가 8KB이고 예약은 16MB인 것을 확인할 수 있습니다.




다시 !eeheap 명령어 결과로 가서,

0:007> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x04bf1018
generation 1 starts at 0x04bf100c
generation 2 starts at 0x04bf1000
ephemeral segment allocation context: none
 segment     begin  allocated      size
04bf0000  04bf1000  04bf5ff4  0x4ff4(20468)
Large object heap starts at 0x05bf1000
 segment     begin  allocated      size
05bf0000  05bf1000  05bf5500  0x4500(17664)
Total Size:              Size: 0x94f4 (38132) bytes.
------------------------------
GC Heap Size:    Size: 0x94f4 (38132) bytes.

이번에는 Gen 0, 1, 2 각각의 시작 주소가 다음과 같습니다.

Gen 0 0x04bf1018
Gen 1 0x04bf100c
Gen 2 0x04bf1000

그림으로 정리해 보면 다음과 같습니다.

gc_segment_3.png

이 정도면 이제 CLR GC Heap의 구조가 머릿속에 그려질 것입니다.




그렇다면, SOH 힙의 구조를 알았으니 이제 힙에 어떤 객체들이 할당되어 있는지 열람할 수 있습니다. 시작은 Gen2 주소부터 하면, 이렇게 나옵니다.

0:007> !do 0x04bf1000
Free Object
Size:        12(0xc) bytes

"Free Object"라고 나오는데, 아직 한 번도 2세대 GC가 구동된 적이 없는 상태이기 때문에 reserved 의미로 할당된 듯 합니다. 이후 0x04bf100c 주소부터는 Gen 1세대 객체들인데요.

0:007> !do 0x04bf100c
Free Object
Size:        12(0xc) bytes

역시 1세대 GC도 구동된 적이 없기 때문에 reserved 의미로 1개의 빈 객체가 할당된 것 같습니다. 0세대 객체가 위치한 0x04bf1018 주소도 첫 번째 객체는 "Free Object"로 나옵니다.

0:007> !do 04bf1018
Free Object
Size:        12(0xc) bytes

음... 아무래도 0, 1, 2 세대의 첫 번째 객체는 "Free Object"로 고정된 것 같습니다. (순전히 저의 예상입니다.)

하지만, 이후부터는 정상적으로 객체들이 나옵니다.

0:007> !do 04bf1018 + c
Name:        System.Exception
MethodTable: 70eefc1c
EEClass:     709d6558
Size:        84(0x54) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
70eefad4  400028f        4        System.String  0 instance 00000000 _className
70ee4b18  4000290        8 ...ection.MethodBase  0 instance 00000000 _exceptionMethod
...[생략]...
70eb254c  40002a1       38 ...ializationManager  0 instance 00000000 _safeSerializationManager
70eefe74  400028e       5c        System.Object  0   shared   static s_EDILock
    >> Domain:Value  02c0cfe8:NotInit  <<


0:007> !do 04bf1018 + c + 54
Name:        System.OutOfMemoryException
MethodTable: 70eefd70
EEClass:     70a78e04
Size:        84(0x54) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
70eefad4  400028f        4        System.String  0 instance 00000000 _className
70ee4b18  4000290        8 ...ection.MethodBase  0 instance 00000000 _exceptionMethod
...[생략]...
70eb254c  40002a1       38 ...ializationManager  0 instance 00000000 _safeSerializationManager
70eefe74  400028e       5c        System.Object  0   shared   static s_EDILock
    >> Domain:Value  02c0cfe8:NotInit  <<

이런 식으로 Segment 별로 할당된 CLR 객체를 모두 열람할 수 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/22/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2021-02-28 01시39분
Internals of the POH
; https://devblogs.microsoft.com/dotnet/internals-of-the-poh/

닷넷 5에 추가된 POH (Pinned Object Heap)
; https://www.sysnet.pe.kr/2/0/12545
정성태
2021-03-02 02시39분
정성태

... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12647정성태5/15/20219132.NET Framework: 1058. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용파일 다운로드1
12646정성태5/15/20218262사물인터넷: 65. C# - Arduino IDE의 Serial Monitor 기능 구현파일 다운로드1
12645정성태5/14/20217975사물인터넷: 64. NodeMCU v1 ESP8266 - LittleFS를 이용한 와이파이 접속 정보 업데이트파일 다운로드1
12644정성태5/14/20219130오류 유형: 719. 윈도우 - 제어판의 "프로그램 및 기능" / "Windows 기능 켜기/끄기" 오류 0x800736B3
12643정성태5/14/20218280오류 유형: 718. 서버 유형의 COM+ 사용 시 0x80080005(Server execution failed) 오류 발생
12642정성태5/14/20219212오류 유형: 717. The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.
12641정성태5/13/20218936디버깅 기술: 179. 윈도우용 .NET Core 3 이상에서 Windbg의 sos 사용법
12640정성태5/13/202111841오류 유형: 716. RDP 연결 - Because of a protocol error (code: 0x112f), the remote session will be disconnected. [1]
12639정성태5/12/20218745오류 유형: 715. Arduino: Open Serial Monitor - The module '...\detection.node' was compiled against a different Node.js version using NODE_MODULE_VERSION
12638정성태5/12/20219634사물인터넷: 63. NodeMCU v1 ESP8266 - 펌웨어 내 파일 시스템(SPIFFS, LittleFS) 및 EEPROM 활용
12637정성태5/10/20219307사물인터넷: 62. NodeMCU v1 ESP8266 보드의 A0 핀에 다중 아날로그 센서 연결 [1]
12636정성태5/10/20219461사물인터넷: 61. NodeMCU v1 ESP8266 보드의 A0 핀 사용법 - FSR-402 아날로그 압력 센서 연동파일 다운로드1
12635정성태5/9/20218782기타: 81. OpenTabletDriver를 (관리자 권한으로 실행하지 않고도) 관리자 권한의 프로그램에서 동작하게 만드는 방법
12634정성태5/9/20217885개발 환경 구성: 572. .NET에서의 신뢰도 등급 조정 - 외부 Manifest 파일을 두는 방법파일 다운로드1
12633정성태5/7/20219339개발 환경 구성: 571. UAC - 관리자 권한 없이 UIPI 제약을 없애는 방법
12632정성태5/7/20219505기타: 80. (WACOM도 지원하는) Tablet 공통 디바이스 드라이버 - OpenTabletDriver
12631정성태5/5/20219438사물인터넷: 60. ThingSpeak 사물인터넷 플랫폼에 ESP8266 NodeMCU v1 + 조도 센서 장비 연동파일 다운로드1
12630정성태5/5/20219776사물인터넷: 59. NodeMCU v1 ESP8266 보드의 A0 핀 사용법 - CdS Cell(GL3526) 조도 센서 연동파일 다운로드1
12629정성태5/5/202111521.NET Framework: 1057. C# - CoAP 서버 및 클라이언트 제작 (UDP 소켓 통신) [1]파일 다운로드1
12628정성태5/4/20219469Linux: 39. Eclipse 원격 디버깅 - Cannot run program "gdb": Launching failed
12627정성태5/4/202110181Linux: 38. 라즈베리 파이 제로 용 프로그램 개발을 위한 Eclipse C/C++ 윈도우 환경 설정
12626정성태5/3/202110198.NET Framework: 1056. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상 (2)파일 다운로드1
12625정성태5/3/20219144오류 유형: 714. error CS5001: Program does not contain a static 'Main' method suitable for an entry point
12624정성태5/2/202112900.NET Framework: 1055. C# - struct/class가 스택/힙에 할당되는 사례 정리 [10]파일 다운로드1
12623정성태5/2/20219550.NET Framework: 1054. C# 9 최상위 문에 STAThread 사용 [1]파일 다운로드1
12622정성태5/2/20216363오류 유형: 713. XSD 파일을 포함한 프로젝트 - The type or namespace name 'TypedTableBase<>' does not exist in the namespace 'System.Data'
... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...