스택 프레임
메소드가 호출되면 스택(Stack) 영역에는 해당 메소드만을 위한 독립적인 공간인 스택 프레임이 생성된다.
-
구성 요소: 메소드로 전달된 매개 변수, 메소드 내의 지역 변수 그리고 작업이 끝난 후 돌아갈 복귀 주소(Return Address)가 포함된다.
-
생명 주기: 메소드가 실행을 시작할 때 스택에 쌓이고 종료되는 순간 즉시 제거된다.
-
심층 분석: 스택 프레임 덕분에 재귀 호출이 가능해지며, 각 호출은 서로 독립적인 지역 변수 공간을 가진다. 하지만 깊은 호출은 StackOverflowException 예외를 유발한다.
메모리 레이아웃과 할당
클래스와 구조체는 메모리에 배치되는 모양새부터 근본적으로 다르다.
-
클래스(Class): 힙 메모리에 할당되며, 객체 데이터 외에도 메모리 오버헤드가 존재한다.
-
객체 해더(Object Header): 동기화 블록 정보 등을 담는다.
-
메소드 테이블 포인터(Type Handle): 해당 객체의 타입을 식별한다.
-
-
구조체(Struct): 데이터 필드만 메모리에 연속적으로 배치된다. 오버헤드가 없으므로 매우 가볍고 다른 클래스의 필드로 선언되면 해당 클래스의 메모리 안에 인라인(Inline)되어 들어간다.
구조체 상속 미지원
값 타입의 고정된 크기 때문이다.
-
다형성의 전제: 상속과 다형성이 가능하려면 자식 객체가 부모 변수에 담길 수 있어야 한다. 하지만 자식 클래스는 부모보다 더 많은 필드를 가질 수 있어 크기가 가변적이다.
-
값 타입의 한계: 구조체는 스택이나 인라인으로 할당될 때 컴파일 타임에 크기가 정확히 확정되어야 한다. 만약 상속을 허용한다면 부모 구조체 변수에 더 큰 자식 구조체 값을 복사할 때 데이터가 잘려나가는 슬라이싱(Slicing) 문제가 발생하며, 이는 메모리 구조가 파괴되는 결과를 낳는다.
구조체는 상속 대신 인터페이스 구현을 통해 다형성을 확보할 수 있지만 이 경우 박싱(Boxing)이 발생하므로 주의해야 한다.
캐시 지역성
캐시 지역성(Cache Locality)은 현대 CPU 메모리에서 데이터를 가져올 때 주변 데이터도 함께 캐시에 올리는 것을 말한다.
-
구조체 배열: 메모리에 데이터가 빈틈없이 붙어있어, CPU 캐시 적중률(Cache Hit)이 높다. 수만 개의 데이터를 순회할 때 클래스보다 수십 배 빠를 수 있다.
-
클래스 배열: 배열에는 객체의 주소만 모여있고, 실제 데이터는 힙에 흩어져 있다. 데이터를 읽을 때마다 메모리 주소를 찾아가야 하므로 캐시 미스(Cache Miss)가 빈번하게 발생한다.
메모리 정렬과 패딩
구조체 내부에서도 CPU가 읽기 편하도록 데이터를 배치하는 정렬 작업이 일어난다. 따라서 다음과 같은 경우에만 구조체 사용을 권장한다.
-
하나, 복사 비용의 문제로 데이터의 크기가 16바이트 이하일 때만 사용한다.
-
둘, 데이터가 한 번 생성되면 변하지 않는 불변(Immutable)의 성격을 가질 때만 사용한다.
-
셋, 논리적으로 하나의 값으로 취급되는 것이 자연스러울 때만 사용한다.
-
넷, 가비지 컬렉터의 부하를 줄여야 하는 초고성능 루프 내부일 때만 사용한다.