성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] How Much Memory Do You Need in 2024...
[정성태] 특권을 조회하는 whoami 명령어 c:\temp> who...
[정성태] 커널 디버깅에서 특정 프로세스의 Token 정보 조회 //...
[정성태] What has case distinction but is ne...
[정성태] 대소문자 '변환'과 함께 따라오는 문제가 바로 대소문자 구분 없...
[정성태] Reverse-engineering what a "short" ...
[정성태] 윈도우의 경우, 스레드 관련 자원을 완전히 회수하기 위해 Thr...
[지현명] Android쪽에서 activity 접근 할때 아래꺼 적어 놓고...
[지현명] Maui.Android에서 폴더 관련 내용 정리 잘 되어 있네요...
[정성태] @정한솔 언급하신 사항이 맞습니다. (C# 13부터) 중간에 i...
글쓰기
제목
이름
암호
전자우편
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'>LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째</h1> <p> 지난 글에 쓴 내용을,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1441'>https://www.sysnet.pe.kr/2/0/1441</a> </pre> <br /> 조금 업데이트해야 할 필요가 있어 다시 작성합니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 현재 비주얼 스튜디오의 경우 .NET Framework 프로젝트에 대해서는 빌드 시 기본적으로 <a target='tab' href='https://learn.microsoft.com/en-us/cpp/build/reference/largeaddressaware-handle-large-addresses'>LargeAddressAware 플래그</a>를 추가해 줍니다. (그래서 editbin.exe를 이용한 후처리 단계를 생략할 수 있습니다.)<br /> <br /> 반면, .NET Core/5+ 프로젝트는 그렇지 않습니다. 따라서 프로젝트의 PostBuildEvent 단계에 editbin을 이용한 설정 단계를 추가해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <span style='color: blue; font-weight: bold'><PlatformTarget>x86</PlatformTarget></span> </PropertyGroup> <Target Name="PostBuild" AfterTargets="PostBuildEvent"> <span style='color: blue; font-weight: bold'><Exec Command="&quot;C:\Program Files\Microsoft Visual Studio\2022\Enterprise\SDK\ScopeCppSDK\vc15\VC\bin\editbin.exe&quot; /largeaddressaware &quot;$(TargetDir)$(TargetName).exe&quot;" /></span> </Target> </Project> </pre> <br /> 끝입니다. ^^ 이제 다음과 같이 네이티브 메모리를 확보하는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.alloccotaskmem'>Marshal.AllocCoTaskMem</a>을 사용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET 7 + Release + x86 using System.Runtime.InteropServices; class Program { static void Main(string[] args) { int gb4 = 1024 * 1024; for (int i = 0; i < gb4; i++) { Marshal.AllocCoTaskMem(4096); // 의도적으로 4KB씩 메모리 누수 } } } </pre> <br /> 실행하면 메모리가 3GB를 넘어 4GB 즈음에 OOM으로 비정상 종료하는 것을 볼 수 있습니다. 근데... 사실 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1850'>작업 관리자에서 Commit size</a> 메모리를 제대로 확인하기도 전에 너무 빨리 없어집니다.<br /> <br /> 그래서 이런 경우 windbg를 실행하고 Launch Executable(단축키: Ctrl + E)로 열어 실행을 하면, OOM 예외가 발생하는 즈음에 windbg가 대상 프로세스를 종료하지 못하게 잡아두기 때문에 그 시점에 작업 관리자의 메모리를 정확하게 확인할 수 있습니다.<br /> <br /> PC마다 세세한 수치는 다를 수 있지만 제 경우에는 "3,989,884 KB"가 나왔습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 네이티브 메모리가 아닌 GC 힙 메모리를 사용하는 예제는 어떨까요? 이를 위해 다음과 같이 코딩을 바꾸면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET 7 + Release + x86 internal class Program { static void Main(string[] args) { int gb4 = 1024 * 1024; List<byte[]> load = new List<byte[]>(gb4); for (int i = 0; i < gb4; i++) { load.Add(new byte[4096]); } } } </pre> <br /> 역시 이번에도 windbg를 이용해 위의 코드를 실행해 보면 이번에는 3,851,904 KB의 Commit size가 나옵니다. 그런데... 뭔가 수치가 이상하죠? ^^ 3,851,904 KB는 대략 3.67GB 정도가 되기 때문에 4KB씩 할당하는 과정치고는 4GB 근처도 못 가서 OOM이 나버린 것입니다.<br /> <br /> 이에 대한 이야기는 이미 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/1850'>작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미</a>" 글에서 했었습니다.<br /> <br /> Commit size 외에도 주소 공간이 단순히 "예약된(reserved)" 공간도 있으므로 그것까지 합치면 4GB 가까운 수치가 나옵니다. 실제로 위에서 실습한 것을, "<a target='tab' href='https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer'>Process Explorer</a>"를 띄워 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1850#procexp'>"Virtual Size"를 확인</a>하면 4,007,496 KB가 나옵니다. 여전히 4,194,304 KB보다 186,808 KB가 부족하지만 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13210'>뭔가 이유는 있을 것</a>입니다. (혹시 여분의 공간에 대해 아시는 분은 덧글 부탁드립니다. ^^)<br /> <br /> 어쨌든 여기서 중요한 것은 3GB 메모리 벽을 넘느냐이기 때문에 그것을 확인했으니 목적은 달성했습니다. ^^ <br /> <br /> <hr style='width: 50%' /><br /> <br /> GC Heap 메모리의 크기는 보통 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.gc.gettotalmemory'>GC.GetTotalMemory</a>를 사용해 구할 수 있지만, 이것은 당연히 그 외의 네이티브 메모리 영역은 포함하지 않습니다. 만약 순수하게 프로세스의 사용 메모리를 구하고 싶다면 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process'>System.Diagnostics.Process</a> 타입이 제공하는 다양한 메모리 관련 멤버를 이용하면 되는데요, 이전에 설명한 Commit Size는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.virtualmemorysize64'>VirtualMemorySize64 속성</a>을 이용해 구할 수 있습니다.<br /> <br /> 단, 이 값은 cache가 되기 때문에 다음과 같이 사용해서는 안 되고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Process process = Process.GetCurrentProcess(); // 여기서 초기화 for (int i = 0; i < gb4; i++) { load.Add(new byte[4096]); Console.WriteLine(process.VirtualMemorySize64.ToString("n0")); // 루프 내에서 속성 접근 } </pre> <br /> 구할 때마다 매번 Process 인스턴스를 가져오는 식이어야 합니다.<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; using System.Collections.Generic; using System.Diagnostics; internal class Program { static void Main(string[] args) { int gb4 = 1024 * 1024; List<byte[]> load = new List<byte[]>(gb4); for (int i = 0; i < gb4; i++) { load.Add(new byte[4096]); Process process = Process.GetCurrentProcess(); // 루프 내에서 초기화 및 속성 접근 long kb = process.VirtualMemorySize64 / 1024; Console.WriteLine($"{kb:n0}"); } } } </pre> <br /> 하지만, 위의 코드처럼은 실행하지 않는 것이 좋습니다. Process 관련 코드들의 부하가 있어 실행 시간이 더 늘어지기 때문인데 차라리 이럴 때는 4KB가 아닌 1MB 단위씩 증가하도록 만드는 것이 더 편합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > load.Add(new byte[1024 * 1024]); /* 실행 시, ...[생략]... 3,812,616 3,812,568 3,812,588 3,812,616 3,812,604 3,812,580 */ </pre> <br /> 보는 바와 같이 출력 결과는 작업 관리자의 Commit size와 유사합니다. <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.virtualmemorysize64'>VirtualMemorySize64</a>의 도움말에 "Gets the amount of the virtual memory, in bytes, <span style='color: blue; font-weight: bold'>allocated</span> for the associated process."라고 나오는데, 아마도 여기서의 "allocated"에는 "reserved"는 포함하지 않는 듯합니다. </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1513
(왼쪽의 숫자를 입력해야 합니다.)