성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET 5+부터 지원되는 GC.GetGCMemoryInfo</h1> <p> 아래와 같은 글에서 GetGCMemoryInfo에 대해 소개하고 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > The updated GetGCMemoryInfo API in .NET 5.0 and how it can help you ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/the-updated-getgcmemoryinfo-api-in-net-5-0-and-how-it-can-help-you/'>https://devblogs.microsoft.com/dotnet/the-updated-getgcmemoryinfo-api-in-net-5-0-and-how-it-can-help-you/</a> </pre> <br /> .NET 3.0부터 BCL에 포함시켜둔 GC.GetGCMemoryInfo를 <a target='tab' href='https://github.com/dotnet/runtime/issues/28439'>5.0부터 기능을 좀 더 보강</a>했다고 합니다.<br /> <br /> 우선, .NET 3.0의 경우 GC.GetGCMemoryInfo가 반환한 GCMemoryInfo 타입은 아래의 기능만 가지고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { var buffer = new byte[4096]; buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); } static void CallGC() { GC.Collect(2, GCCollectionMode.Forced, true); } static void ShowGCInfo() { var info = GC.GetGCMemoryInfo(); Console.WriteLine($"HeapSizeBytes: {info.HeapSizeBytes}, {GC.GetTotalMemory(false)}"); Console.WriteLine($"TotalAvailableMemoryBytes: {info.TotalAvailableMemoryBytes}"); Console.WriteLine($"FragmentedBytes: {info.FragmentedBytes}"); Console.WriteLine($"HighMemoryLoadThresholdBytes: {info.HighMemoryLoadThresholdBytes}"); Console.WriteLine($"MemoryLoadBytes: {info.MemoryLoadBytes}"); } } } /* 출력 결과 HeapSizeBytes: 66168, 72928 TotalAvailableMemoryBytes: 67968360448 FragmentedBytes: 1432 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 55054371962 HeapSizeBytes: 81432, 84560 TotalAvailableMemoryBytes: 67968360448 FragmentedBytes: 2584 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 55054371962 */ </pre> <br /> 한 가지 유의할 점은, GetGCMemoryInfo는 마지막 GC 이후의 상황을 보여주는 것이므로 반드시 GC가 한 번 이상은 되어야 합니다.<br /> <br /> 하나씩 값을 살펴볼까요? ^^ 우선 HeapSizeBytes는 마지막 GC 이후 사용 중인 총 Heap의 크기를 의미합니다. 사실 이와 유사한 값을 반환하는 메서드가 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.gc.gettotalmemory'>GC.GetTotalMemory</a>인데, 값이 살짝 다른 것을 볼 수 있습니다. 어느 것을 신뢰해야 할지 모르겠군요. ^^<br /> <br /> 그다음 TotalAvailableMemoryBytes는 지난 예제에서 다룬 것처럼,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - Java의 Xmx와 유사한 힙 메모리 최댓값 제어 옵션 HeapHardLimit ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13146'>https://www.sysnet.pe.kr/2/0/13146</a> </pre> <br /> 프로세스가 사용할 수 있는 총 메모리 크기를 가져옵니다. 따라서 GCHeapHardLimit가 설정됐다면 그것을 반영한 값이 나옵니다. 보통은 일반적인 환경에서 물리 메모리에 가까운 값을 반환하므로, 크게 의미는 없습니다.<br /> <br /> FragmentedBytes는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.gcmemoryinfo.fragmentedbytes'>문서에 자세하게 설명이 나오므로</a> 그것을 참조하시고... 역시나 GC Heap의 상황을 나타내는 것이므로 특별한 사유가 아니라면 역시 일반적인 성능 모니터링 상황에서는 눈여겨 보게 될 수치는 아닙니다.<br /> <br /> HighMemoryLoadThresholdBytes는 GC가 좀 더 적극적으로 Full GC를 수행하는 임곗값이 됩니다. 따라서 정말 물리 메모리가 숨넘어가는 상황이 발생해 성능 저하가 발생하고 있는지를 판단할 수 있는 값이 되는데요, 꽤나 의미가 있어 보입니다. 그러니까, 현재 시스템 메모리의 사용량이 HighMemoryLoadThresholdBytes를 넘게 되면 일종의 경고 상황이 발생하는 것과 마찬가지라고 보면 되겠습니다.<br /> <br /> 마지막으로, MemoryLoadBytes는 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-memorystatus'>MEMORYSTATUS structure</a>의 dwMemoryLoad 필드 값에 해당한다고 합니다. 따라서 현재 물리 메모리의 사용률 정도로 여기면 되겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 .NET 5 이후부터 추가된 멤버를 살펴볼까요? ^^ 대충 예제 코드는 이렇게 확장할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { var buffer = new byte[4096]; buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); buffer = new byte[4096]; CallGC(); ShowGCInfo(); Console.WriteLine(Environment.NewLine); } static void CallGC() { GC.Collect(2, GCCollectionMode.Forced, true); } static void ShowGCInfo() { var info = GC.GetGCMemoryInfo(GCKind.Any); /* private GCGenerationInfo _generationInfo0; // Gen 0 private GCGenerationInfo _generationInfo1; // Gen 1 private GCGenerationInfo _generationInfo2; // Gen 2 private GCGenerationInfo _generationInfo3; // LOH private GCGenerationInfo _generationInfo4; // POH */ long totalAfter = 0; long totalBefore = 0; foreach (var ginfo in info.GenerationInfo) { Console.WriteLine($"{ginfo.SizeAfterBytes} {ginfo.SizeBeforeBytes}, {ginfo.FragmentationAfterBytes} {ginfo.FragmentationBeforeBytes}"); totalAfter += ginfo.SizeAfterBytes; totalBefore += ginfo.SizeBeforeBytes; } Console.WriteLine($"HeapSizeBytes: {info.HeapSizeBytes}, {GC.GetTotalMemory(false)}, GenInfo After == {totalAfter}, Before == {totalBefore}"); Console.WriteLine($"TotalCommittedBytes: {info.TotalCommittedBytes}"); Console.WriteLine($"Generation: {info.Generation}"); Console.WriteLine($"TotalAvailableMemoryBytes: {info.TotalAvailableMemoryBytes}"); Console.WriteLine($"FinalizationPendingCount: {info.FinalizationPendingCount}"); Console.WriteLine($"Concurrent: {info.Concurrent}"); Console.WriteLine($"Compacted: {info.Compacted}"); Console.WriteLine($"Index: {info.Index}"); Console.WriteLine($"FragmentedBytes: {info.FragmentedBytes}"); Console.WriteLine($"HighMemoryLoadThresholdBytes: {info.HighMemoryLoadThresholdBytes}"); Console.WriteLine($"MemoryLoadBytes: {info.MemoryLoadBytes}"); foreach (var duration in info.PauseDurations) { Console.WriteLine($"{duration.TotalMilliseconds}, {duration.Ticks}"); } Console.WriteLine($"PauseTimePercentage: {info.PauseTimePercentage}"); Console.WriteLine($"PinnedObjectsCount: {info.PinnedObjectsCount}"); Console.WriteLine($"PromotedBytes: {info.PromotedBytes}"); } } } </pre> <br /> 실행해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 24 82048, 0 11640 82072 24, 15648 0 24 24, 0 0 24 24, 0 0 17440 17440, 0 0 HeapSizeBytes: 99584, 118776, GenInfo After == 99584, Before == 99560 TotalCommittedBytes: 221184 Generation: 2 TotalAvailableMemoryBytes: 67968360448 FinalizationPendingCount: 2 Concurrent: False Compacted: False Index: 1 FragmentedBytes: 15648 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 51655953940 0.706, 7060 0, 0 PauseTimePercentage: 2.78 PinnedObjectsCount: 1 PromotedBytes: 84128 48 147472, 0 37736 147496 82096, 46088 15648 82120 48, 16128 0 48 48, 0 0 53296 53296, 0 0 HeapSizeBytes: 183424, 153240, GenInfo After == 283008, Before == 282960 TotalCommittedBytes: 286720 Generation: 2 TotalAvailableMemoryBytes: 67968360448 FinalizationPendingCount: 2 Concurrent: False Compacted: False Index: 2 FragmentedBytes: 46568 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 51655953940 0.163, 1630 0, 0 PauseTimePercentage: 2.54 PinnedObjectsCount: 1 PromotedBytes: 137120 72 173592, 0 47536 173568 147520, 61952 46088 229640 82144, 62664 16128 72 72, 0 0 89152 89152, 0 0 HeapSizeBytes: 209496, 171672, GenInfo After == 492504, Before == 492480 TotalCommittedBytes: 286720 Generation: 2 TotalAvailableMemoryBytes: 67968360448 FinalizationPendingCount: 4 Concurrent: False Compacted: False Index: 3 FragmentedBytes: 62400 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 51655953940 0.163, 1630 0, 0 PauseTimePercentage: 2.54 PinnedObjectsCount: 1 PromotedBytes: 147168 96 219856, 0 75208 219832 173592, 94736 61952 403232 229664, 126056 62664 96 96, 0 0 125008 125008, 0 0 HeapSizeBytes: 255760, 184160, GenInfo After == 748264, Before == 748216 TotalCommittedBytes: 352256 Generation: 2 TotalAvailableMemoryBytes: 67968360448 FinalizationPendingCount: 3 Concurrent: False Compacted: False Index: 4 FragmentedBytes: 96176 HighMemoryLoadThresholdBytes: 61171524403 MemoryLoadBytes: 51655953940 0.186, 1860 0, 0 PauseTimePercentage: 2.59 PinnedObjectsCount: 1 PromotedBytes: 159608 </pre> <br /> 다른 건 크게 관심이 없고, 세대별 GC Heap의 크기를 알 수 있는 GenerationInfo가 눈에 띄는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > long totalAfter = 0; long totalBefore = 0; foreach (var ginfo in info.GenerationInfo) { Console.WriteLine($"{ginfo.SizeAfterBytes} {ginfo.SizeBeforeBytes}, {ginfo.FragmentationAfterBytes} {ginfo.FragmentationBeforeBytes}"); totalAfter += ginfo.SizeAfterBytes; totalBefore += ginfo.SizeBeforeBytes; } </pre> <br /> 이에 대한 출력 결과는 5개가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 96 219856, 0 75208 219832 173592, 94736 61952 403232 229664, 126056 62664 96 96, 0 0 125008 125008, 0 0 </pre> <br /> 왜냐하면, 차례대로 Gen0, Gen1, Gen2의 정보와 함께 이후 LOH, POH의 정보를 보여주기 때문입니다.<br /> <br /> 그런데, 각각의 세대에 해당하는 SizeAfterBytes 정보가 해석이 잘 안 됩니다. 그냥 문서상으로는 GC 이후 해당 Gen에 남아 있는 바이트를 의미하는데요, 그렇다면 그 값들을 모두 더한 경우 HeapSizeBytes와 (적어도) 비슷한 정도는 돼야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HeapSizeBytes: 255760, 184160, GenInfo After == 748264, Before == 748216 </pre> <br /> 아쉽게도 보는 바와 같이, SizeAfter의 총 크기가 748264인 반면, HeapSizeBytes는 255760에 불과합니다. 여기서 재미있는 건, 처음 2번의 GC에서는 HeapSizeBytes와 SizeAfter가 값이 동일했다는 점입니다. 오호~~~ 이걸 어떻게 해석해야 할지 모르겠군요. ^^ 그러다 혹시나 싶어 .NET 7 런타임에서 해당 코드를 실행했더니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[이전 출력 생략]... 0 24544, 0 17968 24544 19584, 20416 12568 174392 154872, 64512 52000 0 0, 0 0 35832 35832, 0 0 HeapSizeBytes: <span style='color: blue; font-weight: bold'>234768</span>, 162072, <span style='color: blue; font-weight: bold'>GenInfo After == 234768</span>, Before == 234832 ...[생략]... </pre> <br /> 정확히 일치합니다. 아쉽지만, 저 값을 각 세대별 Heap 사용량으로 쓰려면 .NET 7까지 기다려야 할 듯합니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1979&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1167
(왼쪽의 숫자를 입력해야 합니다.)