가비지 컬렉터란?
- 가비지 컬렉션
=> 시스템에서 더이상 사용하지 않는 동적 할당된 메모리(Garbage)를 찾아
자동으로 다시 사용가능한 자원으로 회수하는 것
- 가비지 컬렉터
=> 시스템에서 가비지컬렉션을 수행하는 부분
=> 할당된 힙 메모리 공간이 부족할 때 GC가 힙 메모리를 돌며 사용하지 않는 메모리를 회수한다.
- 메모리 관리법
- C, C++ 언어는 메모리 관리를 위해 프로그래머가 객체의 생성뿐 아니라 삭제도 담당해야 한다.
이때 삭제를 제대로 하지 않으면 메모리 누수나 버그가 발생한다.
메모리 누수란?
메모리의 힙 영역에 할당된 부분이 참조되지 않는데도
메모리가 해제되지 않은 채로 계속 점유중인 것
이를 해결하기 위해 가비지컬렉터가 존재.
가비지 컬렉터의 장점과 단점
장점
1. 개발자가 동적으로 할당된 메모리 전체를 관리할 필요가 없어진다.
2. 자동으로 메모리를 관리해주기 때문에 자동 메모리 관리와 이로 인한 메모리 누수 방지에 장점을 가지고 있다.
단점
1. 프로그램 실행 시간에 작업(가비지 컬렉션)을 하는 이상 성능 하락이 발생한다
2. 가비지컬렉터가 존재해도 더 이상 접근이 불가능한 메모리만 회수하므로 메모리 누수는 발생할 수 있다.
3. 자동으로 동작하므로 최적화에 한계가 존재한다.
-> 한정된 메모리를 효율적으로 사용할 수 있는 코드를 작성하는 것은 개발자의 몫이다
4. 가비지 컬렉션이 수행되는 정확한 시점을 알 수 없다.
5. 가비지 컬렉션이 실행될 때는 반드시 애플리케이션을 중지시키는 Stop The World가 수행되고 이는 오버헤드를 일으킨다. 오버헤드는 성능 저하의 원인이 될 수 있다.
6. 프로그램이 예측 불가능하게 일시 정지 될 수 있기 때문에 실시간 시스템에 적합하지 않다.
가비지 컬렉터의 세대 개념
Young Generation
새로 생성된 객체가 위치하며, 주기적으로 Minor GC가 발생.
가비지 컬렉터를 한 번도 겪지 않은 객체들이 위치 (단명 객체)
Old Generation
Young Generation에서 살아남은 객체가 이동하며, Major GC가 발생.
PermGen
클래스 메타데이터와 메소드 정보를 저장
가비지 컬렉션을 여러 번 겪고도 살아남은 오래된 객체
젊은 객체와 오래된 객체를 분리하여 각각에 최적화된 가비지 컬렉션 방식을 적용함으로써 애플리케이션의 성능을 개선할 수 있다.
박싱, 언박싱을 사용할 때 주의해야 할 점
1. 성능 저하
박싱과 언박싱 과정은 CPU 시간을 소모하며, 빈번하게 발생한다면 성능 저하의 원인이 될 수 있다.
따라서 불필요한 박싱, 언박싱은 피해야 한다.
2. 타입 안정성
언박싱 시 잘못된 타입으로 캐스트를 시도하면 InvalidCastException이 발생할 수 있다.
박싱되기 전 객체의 원래 타입을 정확히 알고 있어야 한다.
3. 힙 메모리 사용
박싱된 값은 힙에 저장된다. 이때 박싱이 과도하게 발생하면 가비지 컬렉션에 많은 부담이 가해져
메모리 사용량 증가와 성능 저하 원인이 된다.
=> 박싱, 언박싱을 하는 과정에서 데이터를 메모리의 다른 위치로 이동시키는데,
이때, 추가적인 메모리 할당 및 해제 과정에서 힙에 쓰레기 메모리가 쌓여 GC에 부하를 줄 수 있다.
오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유
- 가비지 컬렉션의 부담을 줄이고 성능을 향상시킬 수 있기 때문이다.
- 오브젝트 풀
=> 자주 사용되는 객체를 미리 생성해두고 필요할 때 재사용하여 객체 생성의 소멸의 비용을 줄이는 전략
=> 즉 오브젝트 풀을 사용하면 객체를 사용할 때마다 생성 & 소멸시키지 않으므로
GC의 역할이 많이 줄어든다
예시
using System;
public class Logger
{
public string LogMessages { get; private set; }
public Logger()
{
LogMessages = string.Empty;
}
public void Log(string message)
{
LogMessages += message + "\n";
}
}
public class Program
{
public static void Main()
{
Logger logger = new Logger();
for (int i = 0; i < 10000; i++)
{
logger.Log("This is log message number " + i);
}
Console.WriteLine("Logging completed. Total log length: " + logger.LogMessages.Length);
}
}
1. 위의 코드가 문제가 되는 이유를 메모리 관점에서 설명
문자열 연결은 많은 메모리 할당과 해제가 발생한다.
특히 위의 코드와 같이 반복문 내에서 (10000번) 수행하는 것은 심각한 성능 저하를 일으킬 수 있다.
2. 아래와 같이 string이 아닌 StringBuilder가 권장되는 이유는 무엇일까요?
public class Logger
{
private StringBuilder logMessages;
public Logger()
{
logMessages = new StringBuilder();
}
public void Log(string message)
{
logMessages.Append(message).Append("\n");
}
public override string ToString()
{
return logMessages.ToString();
}
}
- String과 달리 StringBuilder는 값을 변경할 수 있다.
즉 문자열을 변경할 때 새로운 객체를 생성하지 않아도 기존 객체를 수정할 수 있어
String이 아닌 StringBuilder 를 사용하면 메모리 사용을 줄이고 성능을 향상시킬 수 있다.
- 문자열 연결 작업을 위의 코드와 같이 10000번 수행하는 것은 많은 메모리 할당과 해제가 발생하는 안좋은 방법이다. StringBuilder는 필요할 때마다 내부 버퍼를 사용하여 해당 작업을 효율적으로 처리한다. (위의 Log 함수)
'Study > 개념 정리' 카테고리의 다른 글
[Study] LinkedList (0) | 2024.07.10 |
---|---|
[Study] 제네릭 & 람다 & LINQ & Reflection (0) | 2024.07.09 |
[Study] 상속과 인터페이스 (0) | 2024.07.05 |
[Study] 스택 메모리 , 힙 메모리 (0) | 2024.07.03 |
[Study] 콜백, delegate, event (0) | 2024.07.03 |