Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 68. windbg 분석 사례 - 메모리 부족 [링크 복사], [링크+제목 복사],
조회: 26306
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 7개 있습니다.)
디버깅 기술: 68. windbg 분석 사례 - 메모리 부족
; https://www.sysnet.pe.kr/2/0/1837

디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
; https://www.sysnet.pe.kr/2/0/11808

.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
; https://www.sysnet.pe.kr/2/0/11809

.NET Framework: 944. C# - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12341

디버깅 기술: 171. windbg - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12342

.NET Framework: 945. C# - 닷넷 응용 프로그램에서 메모리 누수가 발생할 수 있는 패턴
; https://www.sysnet.pe.kr/2/0/12343

VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
; https://www.sysnet.pe.kr/2/0/12699




windbg 분석 사례 - 메모리 부족

테스트 중의 한 웹 애플리케이션이 정상 동작하지 않고 있음을 발견했습니다. 이벤트 로그를 보니 다음과 같이 희귀한 메모리 부족 현상으로 웹 애플리케이션 동작에 문제가 발생한 것인데요.

Name: Application
Source: System.ServiceModel 3.0.0.0
Date: 2014-12-19 오후 10:29:17
Event ID: 3
Task Category: WebHost
Level: Error
Keywords: Classic
User: SYSTEM
Computer: win2008r2.testad.com
Description:
WebHost failed to process a request.
Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/7699499
Exception: System.ServiceModel.ServiceActivationException: The service '/WCFs/CHelloWorld.svc' cannot be activated due to an exception during compilation. The exception message is: Memory gates checking failed because the free memory (126545920 bytes) is less than 5% of total memory. As a result, the service will not be available for incoming requests. To resolve this, either reduce the load on the machine or adjust the value of minFreeMemoryPercentageToActivateService on the serviceHostingEnvironment config element.. ---> System.InsufficientMemoryException: Memory gates checking failed because the free memory (126545920 bytes) is less than 5% of total memory. As a result, the service will not be available for incoming requests. To resolve this, either reduce the load on the machine or adjust the value of minFreeMemoryPercentageToActivateService on the serviceHostingEnvironment config element.
at System.ServiceModel.Activation.ServiceMemoryGates.Check(Int32 minFreeMemoryPercentage)
at System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
--- End of inner exception stack trace ---
at System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
at System.ServiceModel.ServiceHostingEnvironment.EnsureServiceAvailableFast(String relativeVirtualPath)
Process Name: w3wp
Process ID: 68448


문제의 원인은 다른 닷넷 프로세스가 시스템의 전체 메모리를 가져간 것입니다. 그나마 다행인 점은, 가상 머신이어서 메모리 부족 현상이 빨리 발견되었습니다. 메모리 6GB, pagefile.sys 메모리 11GB 정도가 할당되었는데... 그 프로세스 하나만 16GB 가까운 용량을 쓰고 있었던 것입니다. 도저히 해당 머신에서 분석할 수 있는 상황이 아니어서 덤프를 떠서 제 컴퓨터로 가져와 분석을 해보았습니다. (여기서 한가지 재미있는 점! 작업 관리자를 통해 풀 덤프를 뜨면 그 주체는 (ID가 4번인) SYSTEM 프로세스가 됩니다. 또한 도중에 취소도 안됩니다.)

닷넷 프로세스였기 때문에, 당연히 코드 상의 잘못으로 메모리 leak이 발생했다고 여겼고, Managed 객체가 많이 생성된 것을 알아내면 문제를 해결할 수 있을 거라 생각했습니다. 하지만, !dumpheap의 결과는 의외였습니다.

0:021> !dumpheap -stat
total 0 objects
Statistics:
              MT    Count    TotalSize Class Name
000007ff00b16a38        1           24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Data.ProviderBase.DbConnectionPoolGroup, System.Data]]
...[생략]...
000007ff0019d478    63398      4177336 System.String
000007ff0086cf48   399576     22376256 System.Diagnostics.ProcessThread
000007ff0056fdf8  1134356     54449088 System.Diagnostics.ThreadInfo
000000000036b9f0    12011     83246488      Free
000007ff0019c308    74791     91600328 System.Object[]
000007ff0024c0c0    30736    170218072 System.Byte[]
Total 2317105 objects
Fragmented blocks larger than 0.5 MB:
            Addr     Size      Followed by
000000000ef50938    0.6MB 000000000efe1f88 System.Net.IPEndPoint
000000000f585200    0.6MB 000000000f617348 System.Net.IPEndPoint
000000000f61bcb0    0.6MB 000000000f6b3a50 System.Net.Sockets.Socket
000000000f94d780    0.8MB 000000000fa0f338 System.Runtime.Remoting.Channels.SocketStream

보시는 바와 같이 System.Byte[] 용량이 170MB에 불과했고 나머지 용량을 합친다고 해도 도저히 16GB에 육박하는 메모리 사용은 설명이 안됩니다.

그런데, 위의 결과에서 약간 이상한 점이 눈에 띕니다. ProcessThread와 ThreadInfo 객체가 유달리 많다는 점인데, !threads 명령으로 문제가 확인됩니다.

0:000> !threads
ThreadCount:      14796
UnstartedThread:  0
BackgroundThread: 14791
PendingThread:    0
DeadThread:       4
Hosted Runtime:   no
                                           PreEmptive                                                   Lock
       ID  OSID        ThreadOBJ     State GC       GC Alloc Context                  Domain           Count APT Exception
   0    1  8dc8 0000000000f9c910      a020 Enabled  0000000000000000:0000000000000000 0000000000340500     0 MTA
   4    2  a1c4 0000000000fa1470      b220 Enabled  0000000000000000:0000000000000000 0000000000340500     0 MTA (Finalizer)
...[생략]...
  
14795 39c8  e934 00000004d9866e90   3009220 Enabled  0000000000000000:0000000000000000 0000000000340500     0 MTA (Threadpool Worker)
XXXX 39c1       00000004d974b620   1019820 Enabled  0000000000000000:0000000000000000 0000000000340500     0 Ukn (Threadpool Worker)
XXXX 39c9       00000004d98675a0   1019820 Enabled  0000000000000000:0000000000000000 0000000000340500     0 Ukn (Threadpool Worker)
XXXX 39ca       00000004d9867cb0   1019820 Enabled  0000000000000000:0000000000000000 0000000000340500     0 Ukn (Threadpool Worker)
XXXX 39cb       00000004d98683c0   1019820 Enabled  0000000000000000:0000000000000000 0000000000340500     0 Ukn (Threadpool Worker)
14797 39cc  edfc 00000004d9868ad0   1009220 Enabled  0000000000000000:0000000000000000 0000000000340500     0 MTA (Threadpool Worker)

무려 14,000개가 넘는 스레드가 생성된 것입니다. 그렇다면 각각의 스레드 스택만으로 그렇게 메모리 소비가 되었다는 것인데, 이를 확인하려면 !address 명령을 사용해야 합니다.

0:000> !address -summary

Mapping file section regions...
...[생략]...
Mapping activation context regions...

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                  12259      7fc`0a3e3000 (   7.985 Tb)           99.81%
Stack                                 73981        3`9cf00000 (  14.452 Gb)  91.24%    0.18%
<unknown>                               318        0`3c0e7000 ( 960.902 Mb)   5.92%    0.01%
Heap                                     85        0`10b68000 ( 267.406 Mb)   1.65%    0.00%
TEB                                   14799        0`0739e000 ( 115.617 Mb)   0.71%    0.00%
Image                                   506        0`04b5c000 (  75.359 Mb)   0.46%    0.00%
Other                                    10        0`001c3000 (   1.762 Mb)   0.01%    0.00%
PEB                                       1        0`00001000 (   4.000 kb)   0.00%    0.00%

...[생략]...

위에서 "Stack" 영역이란 "Memory used for thread stacks."를 의미하는 것으로 말 그대로 스레드 스택으로 할당된 용량입니다. 1만 4천 개의 스레드가 스택 용량만으로 14GB가 넘는 용량을 잡아먹고 있는 것입니다. 그럼, !threadpool 명령을 내려 볼까요?

0:14770> !threadpool
CPU utilization: 81%
Worker Thread: Total: 13779 Running: 13778 Idle: 1 MaxLimit: 32767 MinLimit: 2
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 997 Free: 1 MaxFree: 4 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 2

오호~~~ ThreadPool의 Max 값은 32767인데, 그 중에 현재 13779까지 스레드가 생성되었고 그중에서 13778개가 사용 중입니다. 그런데, 특이하게도 CPU 사용률이 81%군요. !runaway 명령어를 내려봐야겠습니다.

0:14770> !runaway
 User Mode Time
  Thread       Time
  21:1120      1 days 18:23:27.203
  17:96b0      0 days 2:26:34.046
   4:a1c4      0 days 0:01:40.484
  10:5aa8      0 days 0:01:30.031
 ..[생략]...
   1:ac28      0 days 0:00:00.000

다행히 1개의 스레드가 독점하고 있군요. 해당 스레드로 문맥 전환을 하고 스레드 콜 스택을 확인해 봅니다.

0:14770> ~21s
ntdll!NtWaitForSingleObject+0xa:
00000000`76fb05fa c3              ret

0:021> !clrstack
OS Thread Id: 0x1120 (21)
Child SP         IP               Call Site
00000000204fe0f8 0000000076fb05fa [RedirectedThreadFrame: 00000000204fe0f8] 
00000000204fe190 000007ff007e8882 MyApp.util.IntBoundSet.put(Int32) [d:\...\IntBoundSet.cs @ 74]
...[생략]...
00000000204fe840 000007ff006bbf93 System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow()
00000000204fe880 000007ff006bb66e System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(System.IAsyncResult)
00000000204fe8e0 000007ff006b7226 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)
00000000204feb80 000007fef8d9c9e4 [GCFrame: 00000000204feb80] 
00000000204fed50 000007fef8d9c9e4 [DebuggerU2MCatchHandlerFrame: 00000000204fed50] 

이후의 문제 해결은 예전에 썼던 글로 이어집니다.

windbg 분석 사례 - cpu 100% 현상
; https://www.sysnet.pe.kr/2/0/1831

위에서 제공한 IntBoundSet.put 메서드에는 lock이 있습니다. 그 내부에서 무한 루프 현상이 발생했기 때문에 lock 점유가 풀리지 않았고 이후 들어오는 모든 요청들은 스레드 풀로부터 지속적으로 새로 생성된 스레드에 할당된 것입니다.

정리해 보면, 메모리 leak 문제가 아니라 lock 동시성 문제로 인한 스레드 증가로 인해 메모리 부족현상이 발생한 것입니다.

한가지 더 재미있는 점이 있는데, 동일한 문제가 Web Application에서는 표면상으로 나타나지 않았다는 것입니다. 왜냐하면 IIS 는 우수한(!) Recycle 정책을 제공하기 때문에, w3wp.exe가 recycle 정책을 위반하는 상태로 진행되면 기존 프로세스를 종료하고 새로운 프로세스를 띄워 서비스를 한 것입니다. 다행히도, 제가 테스트를 IIS 뿐만 아니라 별도로 만든 NT 서비스에서도 수행했고 recycle 정책이 없는 NT 서비스는 문제를 지속적으로 안고 수행되는 바람에 제 눈에 띈 것입니다.




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







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

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

비밀번호

댓글 작성자
 



2018-11-21 08시53분
정성태

... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11484정성태4/11/201824684.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법파일 다운로드1
11483정성태4/10/201827991개발 환경 구성: 358. "Let's Encrypt"에서 제공하는 무료 SSL 인증서를 IIS에 적용하는 방법 (2) [1]
11482정성태4/10/201820451VC++: 126. CUDA Core 수를 알아내는 방법
11481정성태4/10/201832062개발 환경 구성: 357. CUDA의 인덱싱 관련 용어 - blockIdx, threadIdx, blockDim, gridDim
11480정성태4/9/201822089.NET Framework: 736. C# - API를 사용해 Azure에 접근하는 방법 [2]파일 다운로드1
11479정성태4/9/201817737.NET Framework: 735. Azure - PowerShell로 Access control(IAM)에 새로운 계정 만드는 방법
11478정성태11/8/201919961디버깅 기술: 115. windbg - 덤프 파일로부터 PID와 환경변수 등의 정보를 구하는 방법 [1]
11477정성태4/8/201817442오류 유형: 460. windbg - sos 명령어 수행 시 c0000006 오류 발생
11476정성태4/8/201819005디버깅 기술: 114. windbg - !threads 출력 결과로부터 닷넷 관리 스레드(System.Threading.Thread) 객체를 구하는 방법
11475정성태3/28/201821289디버깅 기술: 113. windbg - Thread.Suspend 호출 시 응용 프로그램 hang 현상에 대한 덤프 분석
11474정성태3/27/201819409오류 유형: 459. xperf: error: TEST.Event: Invalid flags. (0x3ec).
11473정성태3/22/201824584.NET Framework: 734. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상파일 다운로드2
11472정성태3/22/201818543개발 환경 구성: 356. GTX 1070, GTX 960, GT 640M의 cudaGetDeviceProperties 출력 결과
11471정성태3/20/201821926VC++: 125. CUDA로 작성한 RGB2RGBA 성능 [1]파일 다운로드1
11470정성태3/20/201824017오류 유형: 458. Visual Studio - CUDA 프로젝트 빌드 시 오류 C1189, expression must have a constant value
11469정성태3/19/201817039오류 유형: 457. error MSB3103: Invalid Resx file. Could not load file or assembly 'System.Windows.Forms, ...' or one of its dependencies.
11468정성태3/19/201816565오류 유형: 456. 닷넷 응용 프로그램 실행 시 0x80131401 예외 발생
11467정성태3/19/201816046오류 유형: 455. Visual Studio Installer - 업데이트 실패
11466정성태3/18/201817201개발 환경 구성: 355. 한 대의 PC에서 2개 이상의 DirectX 게임을 실행하는 방법
11463정성태3/15/201819543.NET Framework: 733. 스레드 간의 read/write 시에도 lock이 필요 없는 경우파일 다운로드1
11462정성태3/14/201822395개발 환경 구성: 354. HTTPS 호출에 대한 TLS 설정 확인하는 방법 [1]
11461정성태3/13/201825024오류 유형: 454. 윈도우 업데이트 설치 오류 - 0x800705b4 [1]
11460정성태3/13/201817496디버깅 기술: 112. windbg - 닷넷 메모리 덤프에서 전역 객체의 내용을 조사하는 방법
11459정성태3/13/201818305오류 유형: 453. Debug Diagnostic Tool에서 mscordacwks.dll을 찾지 못하는 문제
11458정성태2/21/201819283오류 유형: 452. This share requires the obsolete SMB1 protocol, which is unsafe and could expose your system to attack. [1]
11457정성태2/17/201823987.NET Framework: 732. C# - Task.ContinueWith 설명 [1]파일 다운로드1
... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...