C# 메모리 구조 설명

C# 메모리 구조 설명



데이터의 저장 공간. 이 데이터 저장 공간에는 여러 데이터 타입들 각각의 저장 공간이 존재한다. 보통 Data, Stack, Heap 정도로 분류된다.

C# 의 자료형에는 값 형식 ( value type )과 참조 형식 ( reference type ) 두 가지 형식(type)이 존재한다.


값 형식(Value Type)

   - 해당 변수에 실제 데이터 값을 저장하고 있음.
   - byte, short, int, long, double, float, char, bool, enum(열거형), struct(구조체)
참조 형식(Reference Type)
   - 해당 변수는 값이 저장되어 있는 곳의 위치(주소 값)를 저장하고 있음.
   - object(instance), class, string, interface, delegate


Heap 메모리

   - 위치가 정해지지 않으며 용량이 큰 대신 느리다. 변수 선언 시 데이터가 저장되어있는 메모리를 가리킨다.


Stack 메모리

  - 정렬되어 있으며 사용 제한이 있는 대신 빠르다. 변수 선언 시 데이터가 저장되어있는 메모리를 반환한다.


Static 메모리(Data)

  - 프로그램이 실행되었을 때 처음 한 번만 할당되어 프로그램이 종료될 때까지 메모리 영역에 존재하게 된다. 따라서 static 변수는 다른 메소드(Method)에서 공용 변수로 사용할 수 있게 된다. 프로그램이 실행될 때 한 번만 할당된다. 따라서 static 변수의 Life cyccle 은 프로그램이 종료되기 전까지 이다. static 변수의 scope는 전역변수의 scope와 같다.


Stack

  - 자료가 상자처럼 쌓이는 구조를 의미한다. 따라서 LIFO (Last In First Out) 특징을 가진다.
  - 특정 함수를 호출(실행)하게 되면 특정 함수만을 위한 메모리 공간이 할당되는데 이 공간을 stack (스택) 이라고 한다. 다시 말해, 함수의 메모리 공간 (stack) 에는 값(지역변수)들 형식의 데이터들이 저장된다고 할 수 있다.

  - 함수 수행이 완료되고 결과 값이 반환 되면 함수가 사용했던 메모리 공간도 함께 사라진다. 따라서 함수 속에 선언되어있던 변수들 (지역변수) 또한 함께 해제된다. 코드 블럭이 끝나면 해제된다는 뜻이다.



Heap

  - 프로그램에서 사용하는 동적 메모리 공간을 뜻한다. 이는 객체(Object)가 생성될 때 사용하는 공간이다. 즉, 클래스(Class)가 생성될 때 해당 클래스를 실제로 사용할 수 있도록(실체화) 메모리 공간을 할당받는다. 이 메모리 공간을 힙(Heap)이라고 한다. 힙은 동적으로 할당되며 사용이 끝나면 메모리 해제를 해야한다. 이를 가비지 컬렉터(garbage collector)가 자동으로 처리해 준다. 이말은 GC에서 처리해줄 때까지 코드 블럭이 끝나도 해제되지 않는다 것을 의미한다.

  - 클래스가 힙 메모리 공간에 생성된 상태를 인스턴스(Instance)라고 하며 이는 생성된 클래스의 객체(Object)를 의미하기도 한다. 실체화된 클래스를 인스턴스(Instance)라고 하며 클래스를 가리키는 클래스형 변수를 '참조 변수'라고 한다.

  - 클래스가 생성될 때마다 인스턴스는 매번 다른 메모리 공간을 갖는다. 이는 멤버 변수를 저장하는 공간이 매번 따로 생긴다는 의미이다. 또한 힙 메모리에 생성된 인스턴스는 각각 가상의 주소값을 갖게 된다. 이 가상의 주소값(=참조값)을 Hash Code (해시코드) 라고 하며 참조 변수에 저장된다.

  - 아래 코드와 같이 클래스 생성자를 호출하면 인스턴스가 힙 메모리에 저장된다. 생성된 클래스를 humanA변수에 대입하면, humanA변수는 인스턴스가 저장된 메모리를 가리키게 된다.

Human humanA = new Human(); 
 /* 다시 말해, Human 클래스 자료형으로 humanA 변수를 선언하고, new Human()로 Human 클래스를 생성하여 참조변수인 humanA에 대입한다는 의미이다. 이 참조변수 humanA는 생성된 인스턴스(Instance)를 가리킨다. */



// 클래스 파일 

class Human
{
    public string _szName;
    public int _nAge;
    public float _fHeight;

    public void Eat()
    {
        Debug.Log("Eat, 먹자");
    }

    public void Play()
    {
        Debug.Log("Play, 놀자");
    }

    public void PutOn()
    {
        Debug.Log("PutOn, 옷을 입으세요!");
    }
}


// Object (Instance) 파일 

public class ObjectExample : MonoBehaviour
    // Start is called before the first frame update 
    void Start()
    {
        Human humanA = new Human();
        humanA .name = "Duck";
        print("myHeight : " + humanA .height);
        humanA .PutOn();
        humanA .Play();
        humanA .Eat();
    }
}



GC (Garbage Collector)


  - 사용하지 않는 메모리 영역을 해제하는 역할을 한다. C# 은 기본적으로 메모리를 자동으로 관리를 해준다.

  - Stack 메모리는 함수 사용이 종료되는 시점에서 변수의 메모리 할당이 해제되기 때문에 특별한 관리가 필요하진 않는다.

  - Heap 메모리 사용은 두 가지로 나뉘며, Unmanaged Heap과 Managed Heap 두 가지로 분류된다.


* Unmanaged Heap

대표적으로 C, C++로 메모리의 할당과 해제를 통해 직접 메모리를 관리하여 메모리 누수가 없게 관리해줘야 하지만 Managed비해 속도가 빠르다.


* Managed Heap

대표적으로 C#, Java 등으로 메모리의 할당과 해제를 통해 메모리 관리 없이 언어 자체적으로 메모리를 관리한다. 또한, 메모리를 구체적인 관리를 할 수 없기 때문에 프로그래밍의 자유도가 낮으며 비 정기적인 메모리 정리가 이뤄진다.


메모리 할당

  - C#은 선형적인 Heap 메모리를 가지고 있다. 메모리 할당 시 필요한 메모리 크기만큼 증가시키고 0세대 값으로 설정한다. 메모리에는 메모리 할당을 위한 포인터가 있으며 이 포인터는 메모리가 증가된 크기만큼 이동한다. 할당을 위한 메모리가 부족한 경우 GC가 호출되며, 호출 후에도 메모리 공간이 부족한 경우 Heap 메모리가 확장된다 (2배 확장). 그럼에도 불구하고 사용 가능한 메모리 공간이 부족한 경우 앱의 강제 종료가 발생한다.



메모리 해제

  - Heap 메모리 상에서 사용되지 않는 메모리는 해제시키고, 사용 중인 메모리는 재배치시킨다. 또한 Managed Heap에 할당된 객체의 메모리 허용 임계 값을 초과했을 경우 GC.Collect 매서드가 호출되며 메모리가 해제된다.


GC 매서드가 호출되고 해제되지 않았던 0세대 메모리들은 1세대로 분류되며, 기존에 해제되지 않았던 1세대 메모리들은 2세대로 재분류된다. 한편, 메모리 군은 0세대 부터 2세대까지 존재하며 메모리 관리는 0세대 부터 시작한다. 즉, 메모리 공간이 유효하면 0세대군으로 메모리가 계속 할당되며 메모리 공간이 부족할 시 0세대 메모리 부터 체크한다.


cf. 85 KB 를 기준으로 Small Object Heap (SOH)과 Large Object Heap (LOH)으로 분류된다. LOH는 2세대로 할당이 시작된다.


댓글