티스토리 뷰

Web

.NET Garbage Collection

풍요로운 해구름 2018. 11. 13. 11:54

ASP.NET Core2에서 Server GC를 활성화 하는 방법
GC Mode는 Workstation과 Server 2가지로 구분된다.

[Workstation Mode]
exe 프로세스 당 1개의 GC Managed Heap를 생성한다.
.NET의 gcServer 기본값은 false이므로 기본적으로 workstation Mode로 동작한다.
일반적인 Windows Application은 Workstation Mode로 동작한다고 볼 수 있다.

[Server Mode]
논리 프로세서 1개 당 1개 GC Managed Heap를 생성된다. 따라서 서버에서는 여러 개의 GC Managed Heap이 생성되며 더 빠른 GC속도를 보여준다.
예를들어 8Thread CPU를 사용하는 경우 8개의 GC Managed Heap이 생성된다.
Server Mode에서 GC가 수행되면 모든 Thread가 동시에 GC를 수행하며 따라서 GC처리 속도가 빨라지게 된다.
Workstation Mode에서 100MB를 GC하는데 10초가 걸린다면, Server Mode에서는 8개의 Thread가 존재한다고 할 때 약 1.25초가 소요된다고 볼수 있다.
단점은 모든 Thread에서 동시에 GC를 수행하기 위해 약간의 Hang(멈춤)이 발생하게 된다. 사용자 반응속도가 중요한 Windows Application에서는
이러한 멈춤이 사용자 경험을 떨어뜨리기에 적절하지 않을 수 있지만, 웹사이트는 약간의 지연이 발생해도
사용자 경험에는 큰영향이 아니므로 ASP.NET은 기본적으로 Server Mode로 동작한다.

GCMode의 확인은 System.Runtime.GCSettings.IsServerGC로 확인할 수 있다.

ASP.NET에서는 기본적으로 Server GC가 활성화되어 있지만
Core앱의 경우 직접 ServerGC를 활성화시켜줘야하는 경우가 발생할 수 있는데
아래의 코드를 사용하여 활성화시킬 수 있게 된다.

[app.config]
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <runtime>
        <gcServer enabled="true"/>
    </runtime>
</configuration>

아래 자료는 http://blog.naver.com/PostPrint.nhn?blogId=techshare&logNo=220276947725와 동일한 자료입니다.
원작자에 의해 언제든지 글을 내릴 수 있습니다. 가급적 원문을 확인하시기 바랍니다.

우선, GC는 크게 "Workstation GC", "Server GC"로 나뉘고 다음과 같은 특징을 지닙니다.

Workstation GC: GC Managed Heap은 EXE 프로세스 당 1개가 생성됨. 
                당연히 EXE 프로세스 내의 모든 스레드는 그 하나의 Heap에 관리 개체를 할당하게 됩니다.

                Workstation GC의 경우 다시 Concurrent 기능 유무에 따라 다음과 같이 나뉩니다.

                1. Workstation GC: 1 heap
                2. Workstation GC + Concurrent: 1 heap and 1 GC thread

Server GC: 논리 프로세서 1개당 1개의 GC Managed Heap 생성. 
                (예를 들어, "쿼드 코어"에서 "Hyper Threading"이 활성화 되어 있다면 총 8개의 논리 프로세서가 있으므로 8개의 GC Managed Heap 생성)
                (일단 힙이 n개 생성되면, 이후 프로세스가 종료하는 동안 그 수는 변경되지 않는다.)
            Heap 간의 참조 가능
                (예를 들어, 1번 Heap에 있는 객체는 2번 Heap에 있는 객체를 참조하는 것이 가능.)
            GC 힙에 대한 정리 작업이 동시 수행됨.
                (예를 들어, 2개의 GC Managed Heap이 있고 그것들의 총 용량이 100MB일 때 이상적인 경우 각각 50MB씩 GC 수행 작업이 동시에 수행됨.)
                (따라서, Workstation GC라면 100MB GC 작업에 10초가 걸렸다면 Server GC는 5초만에 완료 가능)

관리 힙에 대해서 정리하면 다음과 같습니다.

GC는 연속된 주소를 갖는 메모리 영역을 VirtualAlloc을 이용해 예약하고 이 한 개의 단위를 Segment라 한다.
    세그먼트의 경우, 필요에 의해 reserved 범위 내에서 committed/decommitted가 되거나 free될 수 있다.
    Segment라 불리는 메모리의 크기는 구현하기 나름이다. 
    즉, 특정 환경에서 Segment 크기를 알았다고 해서 다른 .NET 버전(심지어 업데이트까지도)에서도 같을 거라 가정해서는 안된다.

GC는 필요한 만큼 Segment를 할당할 수 있고 필요없어지면 (VirtualFree로) OS에 반납한다.

Heap은 대체로 85,000 바이트 이상의 객체들을 위해 LOH(Large Object Heap)와, 그 이하의 객체들을 담는 힙으로 나뉩니다. LOH의 경우, dead object에 대해 compacting 단계를 기본적으로 수행하지 않지만 .NET 4.5.1 이후 GCSettings.LargeObjectHeapCompactionMode 속성을 이용해 compacting이 가능하게 되었습니다. (아시는 것처럼, LOH는 Gen 2 GC 수행 시에 함께 GC됩니다.)

Heap은 세대(generation)별로 나뉘어 구성되는데, 0과 1세대는 ephemeral generations라고도 불립니다. 새로운 객체들은 "ephemeral segment"라고 알려진 segment에 할당되고, 살아남아 2세대까지 진행합니다. 최초 1개의 segment가 할당된 경우, 동시에 ephemeral segment가 되지만 시간이 지날수록 2세대 객체가 해당 segment를 잠식하게 되고 GC는 다시 새롭게 segment를 만듭니다. 그럼, 새롭게 만들어진 segment가 다시 "ephemeral segment"로 여겨지고 새로운 객체들은 거기에 할당됩니다. 따라서, 초기 시점에는 0, 1, 2 세대들이 하나의 segment에 있게 되지만, 시간이 지나면 2세대만 있는 segment로 변화하게 됩니다. 프로세스가 필요로 하고 메모리가 허용하는 한 2세대 segment는 그만큼 늘어나게 됩니다.

따라서, 1세대와 2세대 생존 객체들은 현재의 segment가 아닌 이전의 segment로 이전할 수도 있습니다. (아마도, 0세대는 해당 segment에 1세대가 없다고 해도 ephemeral segment의 관리 정책 상 다른 segment로는 이전하지 않는 듯!)

Gen 1/Gen 0의 크기는 절대로 segment를 넘을 수 없는데, 왜냐하면 상식적으로 GC가 발생하면 Gen 2로 넘어가 버리기 때문입니다. 또한, 생존 객체율이 일정 기준을 넘어서면 세대의 크기를 증가시킨다고 합니다.

CLR은 응용 프로그램의 working set 메모리가 너무 크지 않게, GC의 동작 시간이 너무 길지 않게 조정하는 역할을 합니다.

GC는 생존 개체에 대한 결정을 "Stack roots", "GC Handles", "Static data"를 근간으로 판단하는데,

Stack roots: Stack variables provided by the just-in-time (JIT) compiler and stack walker.

Garbage collection handles: Handles that point to managed objects and that can be allocated by user code or by the common language runtime.

Static data: Static objects in application domains that could be referencing other objects. Each application domain keeps track of its static objects.

(기본적인 동작 방식으로 본다면) GC가 수행되기 전, GC를 발생시킨 스레드를 제외한 다른 모드 스레드들은 정지합니다. 이를 그림으로 도식하면 다음과 같습니다.

[그림 1: GC 기본 동작]


소멸자(finalizer)가 정의된 객체가 생성된 경우 특별히 종료 큐(Finalization Queue)에 객체를 함께 등록합니다. 그와 함께 해당 객체는 다음 세대로 살아남는 효과가 적용됩니다. 왜냐하면, 그 객체에 대한 참조를 종료 큐에서 가지고 있는 것과 같기 때문입니다. (따라서, GC 생존을 결정하는 것은 "Stack roots, Garbage Collection Handles, Static data" 뿐만 아니라 종료 큐도 체크 대상이 됩니다.) 종료 큐 이외의 참조가 없어진 경우, GC는 종료 큐로부터 객체를 꺼내 Freachable 큐에 다시 객체를 보관합니다. 그리고 Freachable 큐에 보관된 객체는 CLR이 미리 생성해 둔 스레드에 의해 꺼내지고 소멸자가 호출됩니다. 이런 과정이 완료되고서야 마침내 다음 GC 수행이 되었을 때 비로소 Managed Heap에서 객체가 삭제됩니다. (참고로, 소멸자에 대한 처리 방식은 "시작하세요! C# 프로그래밍" 책에서 "5.4.2.6 소멸자" 편에도 소개하고 있습니다.)




모드 별 GC의 행동에 대해 좀더 살펴보겠습니다. 우선 가장 기본적인 Workstation GC입니다.

Workstation GC(garbage collection): 클라이언트 응용 프로그램에서 사용되는 방식으로 <gcServer>의 기본 설정값

다시 Workstation GC는 concurrent와 non-concurrent로 나뉩니다. (.NET 3.5 이하에서는 다음과 같은 2가지 모드의 GC가 있음.)

- Workstation non-concurrent GC (1개의 논리 CPU에서 실행되는 서버 응용 프로그램을 위한 GC)
0,1,2세대와 상관없이 GC 작업이 수행되면 다른 모든 관리 스레드의 수행을 중지, 현재 거의 사용되지 않는 모드임. (n-코어 CPU가 일반적인 요즘 서버 컴퓨터에 1-core로 된 1-cpu를 사용하는 경우는 거의 없으므로.) "[그림 1: GC 기본 동작]"과 동일.

- Workstation concurrent GC (윈폼/윈도우 서비스 프로그램을 위한 기본 설정값)
Concurrent GC는 GC 수행 시에 다른 스레드들도 함께 실행되는 것을 허용. ASP.NET 환경이 아닌 경우, 일반적인 모든 응용 프로그램은 이 모드를 사용함.
0,1,2세대와 상관없이 GC 작업이 수행되면 다른 모든 스레드의 수행을 중지, 하지만 0,1 세대에 대해서만 GC를 유발한 스레드가 GC 작업을 수행하다가 2세대 힙 메모리를 GC하는 동안에는 다른 모든 스레드의 실행을 재개하고 GC 작업을 별도의 dedicated GC 스레드로 넘겨서 실행.


Workstation concurrent GC의 경우, 이상적인 GC 상황은 다음과 같습니다.

[그림 2: 이상적인 Workstation concurrent GC의 동작 방식(http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx])


하지만, dedicated GC 스레드가 2세대 힙 영역을 정리하는 동안, 다른 스레드들이 0,1 세대에 객체를 할당하다 보면 다시 ephemeral segment의 용량을 모두 소비할 수도 있는 상황이 올 수 있고, 그럼 어쩔 수 없이 모든 스레드들이 중지됩니다. 그래서, 가끔은 다음과 같은 GC 상황이 펼쳐지기도 합니다.

[그림 3: Workstation concurrent GC의 또 다른 동작 사례]


Workstation concurrent GC는 이후 .NET 4.0에서 새롭게 도입된 background garbage collection으로 대체됩니다. 즉, .NET 4.0부터 다음과 같은 2가지 모드의 GC가 있습니다.

  • Workstation non-concurrent GC
  • Workstation background GC

결국 정리해 보면, .NET 4.0 응용 프로그램의 경우 ASP.NET 환경이 아닌, 일반적으로 여러분들이 만드는 모든 응용 프로그램은 "Workstation background GC" 모드를 사용한다고 보면 됩니다.

"Workstation background GC"는 dedicated GC 스레드가 2세대 힙 영역을 정리하는 동안, 다른 스레드들이 0,1 세대에 객체를 할당하다 보면 다시 ephemeral segment의 용량을 모두 소비할 수도 있는 상황이 오는데, 이 때가 되면 2세대 힙 정리를 멈추고 다시 0,1 세대 힙 정리를 시작/완료하고 지난 2세대 힙 정리를 재개하도록 바뀌었습니다.

[그림 4: Workstation background GC의 동작 방식]


background GC에서 새롭게 "foreground GC"라는 용어가 생기는데, 이것은 2세대 힙을 정리하는 동안 수행되는 0,1세대 힙의 정리를 하는 GC를 의미합니다.




이제 Server GC의 특징을 보겠습니다.

  • 서버 응용 프로그램을 위한 GC
  • 서버 GC의 경우 "non-concurrent"와 닷넷 4.5이후부터 지원되는 "background"로 나뉨.
  • 서버 GC는 Workstation GC보다 segment 크기를 더 늘릴 수 있음.
  • 주의할 점은, 서버 GC가 프로세서 당 dedicated GC 스레드를 갖기 때문에 시스템에 서버 GC를 사용하는 EXE 프로세스가 많은 경우 자원 소비가 클 수 있음. 예를 들어, 4-core에 Server GC를 사용하는 12개의 EXE 프로세스가 있다면 총 48개의 dedicated GC 스레드가 생성되고, 전체적인 시스템 메모리 부족 현상으로 진입하게 되는 경우 자칫 48개의 GC 스레드가 구동되며 더욱 큰 부하를 가져오게 됨.
  • dedicated GC 스레드는 THREAD_PRIORITY_HIGHEST 우선 순위

Server GC는 .NET 4.5 미만에서는 "non-concurrent" 유형만 제공되는데, Workstation non-concurrent GC와 다른 점은 논리 프로세서마다 GC Heap과 Dedicated GC 스레드가 생성되므로, GC 수행시 모든 dedicated GC 스레드가 함께 각각 맡은 GC Heap을 정리하며, 그 외의 다른 모든 스레드들은 정지시킨다는 특징이 있습니다. 이를 그림으로 표현하면 다음과 같습니다.

[그림 5: 이상적인 Server GC의 동작 상황]


위의 그림은 GC 스레드가 맡은 크기가 거의 비슷하다는 이상적인 상황이고, 현실적으로는 아래와 같이 GC 스레드들마다 정리 작업에 끝나는 시간이 다를 것이고, 완료 기준은 가장 길게 수행된 GC 스레드가 될 것입니다. (그 외에도, 프로세서 자원은 더 높은 우선 순위의 스레드들에 할당될 수 있으므로.)

[그림 6: 일반적인 Server GC의 동작 상황: (http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx)]


단일 모드만 지원해 외롭기만 보였던 Server GC 유형에, .NET 4.5부터 Concurrent 성격이 부여된 "Background GC" 유형이 추가됩니다. 이는 함께 개선된 "Workstation background GC"와 비슷한데, 다른 점이라면 "Dedicated GC 스레드"와 함께 "Background GC 스레드"가 별도로 존재한다는 점입니다.



어찌되었든, .NET 4.5 이후부터는 Workstation, Server 모두에 "background GC"가 포함되었습니다.

가장 중요한 Server GC의 특징은 다중 프로세서인 경우에만 허용된다는 점입니다. (물론, 요즘의 현실에 비춰봤을 때 1개의 논리 프로세서를 갖는 경우는 거의 없으므로 그냥 잊어도 되는 특징이 되었습니다.) 만약 싱글 프로세서 시스템에서 Server GC를 지정하면 Workstation non-concurrent GC를 사용하도록 강제로 바뀝니다.


간략하게 GC 모드를 정리한 표가 "How does the GC work and what are the sizes of the different generations?" 글에 나오는데 인용해 보겠습니다. ^^

# Concurrent WS Non-Concurrent WS Server GC
Design Goal Balance throughput and responsiveness for client apps with UI Maximize throughput on single-proc machines Maximize throughput on MP machines for server apps that create multiple threads to handle the same types of requests
Number of heaps 1 1 1 per processor (HT aware)
GC threads The thread which performs the allocation that triggers the GC The thread which performs the allocation that triggers the GC 1 dedicated GC thread per processor
EE Suspension EE is suspended much shorter but several times during a GC EE is suspended during a GC EE is suspended during a GC
Config setting <gcConcurrent enabled="true"> <gcConcurrent enabled="false"> <gcServer enabled="true">
On a single proc WS GC + non-concurrent




이제 관련 설정값들을 좀 볼까요? ^^ NT 서비스와 같은 서버 응용 프로그램에서 Server GC를 사용하고 싶다면 명시적으로 app.config에 다음과 같이 지정해야 합니다.

<configuration>
 <runtime>
   <gcServer enabled="true" />
 </runtime>
</configuration>

하지만, ASP.NET의 경우에는 기본으로 설정되어 있으므로 상관없습니다. concurrent는 기본 값이 켜져 있으므로 명시적으로 끄고 싶다면 다음과 같이 설정합니다.

<configuration>
 <runtime>
   <gcConcurrent enabled="false" />
 </runtime>
</configuration>




나중에 시간이 되면 GC Heap에 대해 windbg 차원에서 분석하는 것을 해볼텐데, 현재는 그냥 아래와 같은 정도로만 "!eeheap -gc" 명령어의 결과로 나온다고만 알고 지나가겠습니다.

 Heap 0 (001c3a88)
generation 0 starts at 0x0310d288
generation 1 starts at 0x030ee154
generation 2 starts at 0x03030038
ephemeral segment allocation context: none
segment   begin    allocated size                reserved
001c92f0  7a733370 7a754b98  0x00021828(137,256) 00004000
001c5428  790d8620 790f7d8c  0x0001f76c(128,876) 00004000
03030000  03030038 03115294  0x000e525c(938,588) 03d3f000
Large object heap starts at 0x0b030038
segment   begin     allocated  size                    reserved
0b030000 0b030038 0b4d5aa8 0x004a5a70(4,872,816) 01af8000
Heap Size 0x5cbc60(6,077,536)

그런데, 그냥 지나가려니 너무나 특이한 점이 눈에 띄는데요. 03030000 segment 앞에 2개의 이상한 segment가 있는데, 이것은 문자열 상수 데이터를 보관한다고 합니다. 그 정도만 이번엔 언급하고 넘어가겠습니다. ^^

참고로, GC에 대한 보다 많은 정보를 원하시는 분들은 아래의 글들을 꼭 읽어보시길 바랍니다. ^^

닷넷 가비지 컬렉션 다시 보기 - Part I 기본 작동 방식
 http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx

닷넷 가비지 컬렉션 다시 보기–Part II 세대별 가비지 콜렉션
 http://www.simpleisbest.net/post/2011/04/05/Generational-Garbage-Collection.aspx

닷넷 가비지 컬렉션 다시 보기 - Part III LOH
 http://www.simpleisbest.net/post/2011/04/11/Large-Object-Heap-Intro.aspx

닷넷 가비지 컬렉션 다시 보기 - Part IV 가비지 컬렉션 발생 시기
 http://www.simpleisbest.net/post/2011/04/18/When-GC-Occurs.aspx

닷넷 가비지 컬렉션 다시 보기 - Part V 가비지 컬렉션 모드
 http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx

닷넷 가비지 컬렉션 다시 보기 - Part VI 가비지 컬렉션 모드 활용
 http://www.simpleisbest.net/post/2011/04/27/Using-Garbage-Collection-Modes.aspx

How does the GC work and what are the sizes of the different generations?
 http://blogs.msdn.com/b/tess/archive/2008/04/17/how-does-the-gc-work-and-what-are-the-sizes-of-the-different-generations.aspx

The .NET Framework 4.5 includes new garbage collector enhancements for client and server apps
 http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx

Fundamentals of Garbage Collection
 http://msdn.microsoft.com/library/ee787088(v=vs.110).aspx

 




'Web' 카테고리의 다른 글

[CSS] White-space  (0) 2021.09.10
OIDC(OpenID Connect)와 OAuth 2.0  (0) 2021.07.22
Token 기반 인증 vs Cookie 인증  (0) 2021.07.22
.NET Garbage Collection  (0) 2018.11.13
댓글
댓글쓰기 폼