본문 바로가기
책/Effective C#

Effective C# - Item11 .NET 리소스 관리에 대한 이해

by 코딩하는 돼징 2023. 11. 29.
반응형

.NET 개발자가 되기 위해서는 관리 환경에서 메모리와 주요 리소스들이 어떻게 관리되는지를 올바르게 이해해야 한다.

특히 메모리 관리와 가비지 컬렉션의 동장 방식을 정확이 이해해야 한다.

 

가비지 컬렉션(GC)과 메모리 관리
가비지 컬렉션은 관리되는 메모리(managed memory)를 관리하는 기술로, 메모리 누수, 댕글링 포인터, 초기화되지 않는 포인터 등과 같은 메모리 관리 문제를 자동으로 처리한다. .NET 프레임워크에서는 가비지 컬렉터가 주기적으로 실행되어 더 이상 사용되지 않는 객체를 찾아내고 메모리에서 제거한다


마크/콤팩트(Mark/Compact) 알고리즘

여러 객체 사이의 연관 관계를 효율적으로 파악하여 더 이상 사용하지 않는 객체를 자동으로 제거한다.

 

마킹(Marking)

이 알고리즘의 첫 번째 단계는 도달 가능한 객체를 식별하기 위한 "마킹" 단계이다. 가비지 컬렉터는 최상위(root) 객체에서부터 출발하여 참조 체인을 따라가며 도달 가능한 모든 객체에 마크를 표시한다.

콤팩팅(Compacting)

마킹이 완료된 후, 콤팩팅 단계가 이루어진다. 콤팩팅은 도달 가능한 객체들을 한쪽으로 모으고 그 과정에서 빈 메모리 공간을 없애며 메모리를 조각 없이 정리하는 작업이다. 이로써 메모리 단편화를 최소화하고 힙의 사용 가능한 공간을 최적화다. 이처럼 힙에 대한 메모리 관리는 가비지 수집기가 완전히 책임을 진다.

예를 들어 Entity Set 클래스는 객체 간에 복잡한 참조 관계를 가질 수 있다. 이 알고리즘을 사용하면 Entity Set이나 다른 복잡한 객체 구조에서 발생하는 메모리 관리 문제를 개발자가 직접 신경쓰지 않아도 된다. 가비지 컬렉터가 알아서 마킹하고 콤팩트하는 과정을 수행하여 메모리 누수나 문제를 방지한다.


비관리 리소스는 여전히 개발자가 관리해야한다

 

01 Finalizer

비관리 리소스에 대한 해제 작업이 반드시 수행될 수 있도록 도와주는 방어적인 메커니즘이다.

단점

1) 호출 시점

해당 객체가 GC에 의해 수집될 때 호출되며 호출 시점은 정확히 알 지 못한다는 단점이 있다.

2) 메모리 해제

Finalizer가 호출되면 해당 객체는 아직 메모리에서 제거되지 않는다. 호출 후에도 객체는 메모리에 남아 있게 된다. 그러므

가비지로 간주된 이후에도 꽤 긴 시간 메모리를 점유하게 된다. 결과적으로 메모리는 다음 GC주기에서 해제된다.

 

02 IDisposable 인터페이스

위의 단점들 때문에 Finalizer보다 IDisposable인터페이스를 활용하는 것이 권장된다.


.NET의 GC는 GC과정을 최적화하기 위해서 세대(generation)라는 개념을 사용한다. 이에따라 가비지가 될 가능성이 높은 객체를 더 빠르게 찾아낼 수 있다.

GC 세대별 정리

0세대 : 마지막으로 가비지 수집기가 수행된 이후 임의의 객체가 생성

1세대 : 또 한번의 가비지 수집 절차가 수행됐고 기존에 생성한 객체가 여전히 쓰이고 있다면 이번 가비지 수집 절차의 정리 대상이 되지 않고 살아남은 객체

2세다 : 두번 혹은 그 이상의 가비지 수집 절차가 수행 됐음에도 여전히 사용되고 있는 객체

 

이처럼 세대를 구분하는 이유는 응용프로그램 내에서 비교적 짧은 시간만 사용되는 개체를 다른 객체와 구분하기 위해서이다.


가비지 수집절차

0세대 : 객체에 대해서만 가비지 수집을 진행

1세대 : 대략 10번에 한 번 꼴로 추가적으로 1세대 객체에 대해서 수집 수행

2세대 : 약 100번에 한번 꼴로 2세대 객체를 포함한 모든 세대의 객체를 대상으로 가비지 수집을 수행

 

Finalizer 를 가진 객체는 즉각 제거되지 못하므로 1세대 객체가 되고 이 경우 9번의 수집 절차가 추가적으로 수행된 이후에나 비로소 메모리에서 제거될 가능성이있다. 만약 이 과정에서도 정리되지 못하면 2세대 객체가 되므로 약 100번의 가비지 수집 절차가 추가로 수행되는 경우에만 삭제 될 것이다.


결론 

.NET환경에서 비 관리 리로스를 해제하는 가장 좋은 방법은 Finalizer 를 활용하는 것이 아니라 IDisposable인터페이스와 표준 Dispose패턴을 활용하는 것이다. 

 

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

 

 

반응형

댓글