Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 5개 있습니다.)
(시리즈 글이 6개 있습니다.)
.NET Framework: 188. .NET 64비트 응용 프로그램에서 왜 (2GB) OutOfMemoryException 예외가 발생할까?
; https://www.sysnet.pe.kr/2/0/946

.NET Framework: 266. StringBuilder에서의 OutOfMemoryException 오류 원인 분석
; https://www.sysnet.pe.kr/2/0/1171

.NET Framework: 357. .NET 4.5의 2GB 힙 한계 극복
; https://www.sysnet.pe.kr/2/0/1403

.NET Framework: 367. LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리
; https://www.sysnet.pe.kr/2/0/1441

.NET Framework: 640. 닷넷 - 배열 크기의 한계
; https://www.sysnet.pe.kr/2/0/11142

.NET Framework: 2105. LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째
; https://www.sysnet.pe.kr/2/0/13294




LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리

서버 측 개발 환경에서는 이제 거의 64비트가 대세를 이룬 것 같습니다. 그래도 여전히 가끔은 32비트 DLL과의 호환 때문에 프로세스까지도 32비트로 제한하는 경우가 종종 있는데요.

32비트 EXE의 경우, 32비트 운영체제에서는 기본적으로 2GB 상한값을 갖습니다. 한번 테스트 해볼까요? ^^ 다음의 간단한 프로그램을 실행하고,

using System;
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 씩 메모리 누수
        }
    }
}

"Unhandled Exception: OutOfMemoryException." 예외가 발생하는 시점의 Task Manager를 확인해 보면 Commit Size == 1,978,052KB (약 1931MB)를 점유합니다. (물론, 구체적인 수치는 컴퓨터마다 달라질 수 있습니다.)

4GB까지 주소 지정이 가능한 32비트 응용 프로그램에게 이런 2GB 제한이 있는 것은 커널 주소 영역으로 2GB를 미리 예약하고 있기 때문입니다.





2GB를 넘어서 3GB로!

점점 더 응용 프로그램의 메모리 사용량이 커지면서 2GB는 너무나 작은 메모리가 되어 버렸습니다. 그래서 마이크로소프트는 EXE 프로세스가 2GB 상한을 넘어 사용할 수 있다는 LargeAddressAware 플래그를 추가했습니다.

하지만, Visual Studio는 닷넷 프로그램에 대해 기본적으로 LargeAddressAware 옵션을 적용하지 않는 것은 물론이고 아예 프로젝트 속성창에서 선택할 수 있는 옵션 자체가 없습니다. (Visual C++ 프로젝트에는 있습니다.)

LargeAddressAware 옵션이 적용된 EXE인가 하는 것은 닷넷 EXE 파일에 대해 dumpbin으로 확인해 보면 알 수 있는데, 보통은 다음과 같이 "characteristics"에 2개의 값만 나옵니다.

C:\ConsoleApplication2\bin\Debug>dumpbin /HEADERS ConsoleApplication2.exe
Microsoft (R) COFF/PE Dumper Version 11.00.60315.1
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file ConsoleApplication2.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
             ...[생략]...
             102 characteristics
                   Executable
                   32 bit word machine

닷넷 프로젝트에서 LargeAddressAware 옵션을 켜려면 editbin.exe를 이용해 빌드 이벤트 구성을 해야 합니다. 예전에 editbin.exe를 적용하는 방법에 대해서는 잠깐 소개해 드렸었지요. ^^

DEP 비호환 ActiveX 오류
; https://www.sysnet.pe.kr/2/0/773

그래서, "Post-build event command line"에 다음과 같이 옵션을 적용해 주면 됩니다.

call "$(DevEnvDir)..\tools\vsvars32.bat"
editbin.exe /largeaddressaware  "$(TargetPath)"

이렇게 적용하고 다시 한번 dumpbin으로 확인을 해보면 다음과 같이 새롭게 플래그가 적용된 것을 볼 수 있습니다.

122 characteristics
      Executable
      Application can handle large (>2GB) addresses
      32 bit word machine

그런데, 정말 효력이 있을까요? 4GB 메모리의 Windows Server 2008 x86에서 테스트 해보았습니다. 그런데, 별로 바뀐 점 없이 2GB 내에서 OOM 예외가 발생했습니다. ^^ 왜냐하면 사용자 메모리가 2GB를 넘어선다는 것을 운영체제도 알아야 하기 때문입니다. 그래서, Vista/2008 운영체제에서는 bcdedit.exe로 다음과 같은 명령어를 관리자 권한으로 실행하고 재부팅해야 합니다.

설정: bcdedit.exe /set IncreaseUserVA 3072
해제: bcdedit.exe /deletevalue IncreaseUserVA

이 경우, Windows Server 2008 x86에서 작업 관리자의 Commit Size가 3,142,268KB (약 3,068MB)까지 올라간 후 OOM 예외가 발생합니다.

하지만, 이것이 꼭 긍정적인 효과만 있는 것은 아닙니다. 왜냐하면, CPU 레지스터의 4GB 메모리 주소 지정에는 변함이 없는 상태에서 강제로 운영체제 차원에서 커널 1GB, 사용자 영역 3GB로 제한을 둔 것이기 때문에 자칫 커널 메모리가 많이 필요한 프로그램이 있다면 문제가 발생할 여지가 있습니다.





3GB를 넘어서 4GB로!

그렇다면, /largeaddressaware x86 응용 프로그램을 64비트 운영체제에서 실행하면 어떻게 될까요?

64비트 운영체제에 설치된 커널 디바이스 드라이버 프로그램들은 반드시 64비트여야 합니다. 즉, 64비트 윈도우는 사용자 프로그램에 대해서만 WOW64를 통해서 32비트/64비트 모두 실행해 주는 것일 뿐, 커널 측에는 오직 64비트만 허용됩니다.

따라서, 64비트 운영체제는 "bcdedit.exe /set IncreaseUserVA ..." 식의 설정이 필요없고 응용 프로그램만 /largeaddressaware 플래그만 적용되어 있다면 32비트로 허용된 4GB 영역 모두를 사용할 수 있습니다. 커널 드라이버가 모두 64비트로 작성되었으므로 하위 4GB에 배치시킬 필요가 없어 32비트 응용 프로그램에게 완전하게 4GB 주소 영역을 내줄 수 있는 것입니다.

실제로, "/largeaddressaware가 적용된 x86 응용 프로그램"을 "Windows Server 2008 R2"에서 실행시키면 Commit Size == 4,079,060KB (약 3,983MB)까지 올라갑니다.

edit_bin_test_1.png

따라서, 메모리 압박이 심한 32비트 응용 프로그램을 위해 운영체제를 64비트로 업그레이드하는 것은 어느 정도는 의미가 있습니다. ^^




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/3/2022]

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

비밀번호

댓글 작성자
 



2014-10-24 01시42분
How can I detect programmatically whether the /3GB switch is enabled?
; https://devblogs.microsoft.com/oldnewthing/20141023-00/?p=43783
정성태
2016-10-19 10시59분
[차가워] 안녕하세요
비주얼스튜디오2015, 윈도우10, 32비트 프로젝트
윗 명령줄을 넣어도 1.3기가를 초과할 경우 OOM 메시지를 출력합니다
[guest]
2016-10-19 11시05분
[차가워] 타켓을 anycpu로 하니 잘 작동하네요
[guest]
2016-10-19 11시34분
@차가워 님 윈도우 10은 64비트에서 테스트하신 건가요? 그런 경우라면 AnyCPU로 한 경우, 64비트 응용 프로그램으로 동작하기 때문에 OOM이 발생하지 않습니다. 32비트 운영체제를 대상으로 1.3GB에서 OOM이 발생했다면, .exe 파일을 다시 한번 명령행에서 dumpbin.exe를 이용해 "Application can handle large (>2GB) addresses" 표시가 있는지 확인해 보시고, 아울러 운영체제도 "bcdedit.exe /set IncreaseUserVA 3072" 옵션을 실행한 후 부팅해야 합니다.
정성태
2018-08-18 04시24분
[이범철] 명령줄 넣어도~ 메모리가 1.7기가 넘어가니까 OOM 발생하는데요~
물론 OS 62bit 입니다
[guest]
2018-08-18 06시31분
다음의 글을 참고하세요.

.NET 4.5의 2GB 힙 한계 극복
; http://www.sysnet.pe.kr/2/0/1403

(이범철 님, 다음부터는 OOM이 발생한 상황에 대한 설명도 좀 추가해 주세요. 그냥 발생했다고 하면... ^^;;;)
정성태
2023-03-21 03시27분
[김훈] windows10 64bit 환경에서 32bit c# .net 프로그램을 작성합니다.
dumpbin 으로 실행파일에서 "Application can handle large (>2GB) addresses" 를 확인했지만
commit 메모리 2.6 ~ 2.7 GByte 정도에서 out of memory 오류가 발생합니다.

64bit windows 환경인데 4G 메모리 까지 사용하려면 또 어떤 설정이 필요할까요?
[guest]
2023-03-21 03시30분
[김훈] 아.. out of memory 테스트는 임의의 byte[1000] 을 List 에 계속 add 하는 방법으로 테스트 합니다.
[guest]
2023-03-21 03시46분
@김훈 이 글의 예제 코드로도 3GB 벽을 못 넘나요?
정성태
2023-03-22 09시06분
[김훈] 아.. 아래와 같이 거의 4G 못미쳐서 oom 이 발생하네요
2.6 에서의 oom 은 GC 에서의 total memory 값인데 , 아마도 managed heap 사이즈가 아닐까 합니다.
메모리가 알아볼려고 하면 알아볼수록 너무 어렵네요...

Alloc [ 3,933,588 MB ]
Alloc [ 3,933,592 MB ]

Unhandled Exception: OutOfMemoryException.
[guest]
2023-03-22 09시19분
[김훈] static void Main(string[] args)
        {
            long total = 0;
            List<byte[]> load = new List<byte[]>();

            int gb4 = 1024 * 1024;
            for (int i = 0; i < gb4; i++)
            {
                Marshal.AllocCoTaskMem(4096);

                total += 4096;

                // load 없음 -- 1번
                load.Add(new byte[1024]); // 2번
                load.Add(new byte[4096]); // 3번
                Console.WriteLine($"Alloc [ {(int)(total / 1024):n0} MB ], GC [ {(int)(GC.GetTotalMemory(false) / 1024):n0} MB]");
            }
        }

이와같이 테스트하여
[guest]
2023-03-22 09시20분
[김훈] 1번결과

Alloc [ 3,933,352 MB ], GC [ 4,447 MB]
Alloc [ 3,933,356 MB ], GC [ 4,447 MB]
Alloc [ 3,933,360 MB ], GC [ 4,447 MB]
Alloc [ 3,933,364 MB ], GC [ 4,447 MB]

Unhandled Exception: OutOfMemoryException.


2번결과

Alloc [ 2,911,084 MB ], GC [ 741,192 MB]
Alloc [ 2,911,088 MB ], GC [ 741,200 MB]
Alloc [ 2,911,092 MB ], GC [ 741,200 MB]
Alloc [ 2,911,096 MB ], GC [ 741,200 MB]
Alloc [ 2,911,100 MB ], GC [ 741,200 MB]
Alloc [ 2,911,104 MB ], GC [ 741,200 MB]

처리되지 않은 예외: OutOfMemoryException.



3번결과
Alloc [ 1,763,224 MB ], GC [ 1,771,058 MB]
Alloc [ 1,763,228 MB ], GC [ 1,771,066 MB]
Alloc [ 1,763,232 MB ], GC [ 1,771,066 MB]
Alloc [ 1,763,236 MB ], GC [ 1,771,074 MB]
Alloc [ 1,763,240 MB ], GC [ 1,771,074 MB]

처리되지 않은 예외: OutOfMemoryException.

이런 결과가 나오네요, 혹시 Marshal.AllocCoTaskMem(4096); 이 코딩은 어느 메모리를 Alloc 하는 걸까요?
[guest]
2023-03-22 09시31분
[김훈] 혹시 GC.GetTatalMemory() 이외의
메모리 사용량을 조회하는 함수가 있을까요?
(Marshal.AllocCoTaskMem() 이 alloc 하는 메모리의 현재 총 메모리 )
[guest]
2023-03-22 09시27분
@김훈 아래의 글을 읽어보시고, 그래도 궁금하신 것이 있다면 다시 질문해 주세요.

LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째
; https://www.sysnet.pe.kr/2/0/13294
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13838정성태12/4/2024633오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/2024701디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
13836정성태12/3/2024922디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
13835정성태12/2/2024985오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/20241073Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우)파일 다운로드1
13833정성태11/29/20241077개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/20241110Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/2024977Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/20241069개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/20241026스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20241047개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/20241117Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/20241174닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/20241144Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20241083Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/20241084개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/20241044개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/20241170Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/20241127VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20241122Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/20241215Windows: 272. Windows 11 24H2 - sudo 추가
13817정성태11/14/20241100Linux: 106. eBPF / bpf2go - (BPF_MAP_TYPE_HASH) Map을 이용한 전역 변수 구현
13816정성태11/14/20241157닷넷: 2312. C#, C++ - Windows / Linux 환경의 Thread Name 설정파일 다운로드1
13815정성태11/13/20241101Linux: 105. eBPF - bpf2go에서 전역 변수 설정 방법
13814정성태11/13/20241214닷넷: 2311. C# - Windows / Linux 환경에서 Native Thread ID 가져오기파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...