이전 면접을 보던 중 여러 가지 질문을 받았지만, 그중 인상 깊었던 질문인 “Stack이 무한히 커질 수 있다면, Heap 메모리는 불필요할까?”에 대해 간단하게 정리하며 기록해 두고자 한다.
이런 류의 질문은 정답이 있다기보다는, 질문을 받은 사람의 관점과 경험에 따라 답변의 깊이가 달라지는 생각을 필요로하는 질문이라고 생각한다. 때문에 해당 글에서는 다른 사람들의 모범 답안이 아닌 당시에 내가 이 질문에 어떻게 접근했고 어떤 논리로 질문을 풀어나갔는지 그 과정을 정리해 보려 한다.
Stack 과 Memory
이러한 질문에 답변하기 위해서는, 기본적으로 Stack과 Heap이 무엇인지 서로 간의 역할과 차이에 대해 알고 있어야한다.
다만, 이 두 가지의 개념은 끝이 없을 정도로 깊기 때문에 모든 것을 설명하는 것은 어렵다고 판단하였고,
애초에 이러한 질문을 처음 받았을 때, 해당 질문은 자료구조적인 관점 뿐 아닌 시스템 전체적인 이해도를 파악하려는 의도라고 생각했기 때문에 당시에는 아래 세 가지의 키워드를 중점으로 답장하고자 하였었다.
1. 속도
단순히 메모리 할당과 해제 속도만을 비교하면 Stack이 Heap보다 압도적으로 빠르다.
Stack 은 기본적으로 LIFO 구조로 동작해 메모리를 할당하고 해제할 때 단순히 스택 포인터를 이동하는 연산만을 수행한다. 하지만 Heap Memory 는 빈 공간을 찾는 복잡한 과정을 필요로하며, SpringBoot 의 가비지 컬렉터처럼 사용이 끝난 메모리를 주기적으로 정리하는 과정에서 오버헤드나 추가적인 비용이 발생하기 때문이다.
( 이는 어디까지나 단순 메모리 할당/해제에 국한된 내용이며, 특정 상황에서는 Heap 메모리가 더욱 빠른 경우가 존재하는데, 이는 아래에서 추가적으로 설명하도록 하겠다. )
2. 안정성
Stack 은 프로세스 내에서 각 스레드마다 독립적으로 할당되는 성격을 가지고 있다. 하나의 스레드는 다른 스레드의 Stack 영역에 접근할 수 없기 때문에 일반적인 동시성 문제에서 안전하다 라고 말할 수 있지만, 반대로 생각하면 스레드 간에 데이터를 공유할 수 없다는 치명적인 단점이 되기도 한다.
또한, Java 와 같은 객체 지향 언어에서는 기본적으로 참조 타입인 String, Integer 등을 Heap Memory 에서 정의하고 활용하고 있기 때문에 Stack 메모리만을 활용해야하는 경우, 이러한 타입들을 직접 정의하고 활용하는 과정에서 문제가 발생할 수 있기 때문에 이러한 공유자원이 필요한 상황에서는 모든 스레드가 접근할 수 있는 Heap Memory 가 더욱 안전하다고도 할 수있다.
3. 효율성
Stack 은 함수가 호출되는 시점에 할당될 데이터의 크기를 명확하게 결정해야만 하고, 이렇게 생성된 모든 데이터는 함수가 종료되는 순간 모두 소멸되게 된다.
이는 정적이고 예측 가능한 시스템에서는 메모리 누수 없이 자원을 효율적으로 관리할 수 있다는 장점이 된다. 하지만 반대로 동적인 시스템에서는 데이터 크기가 변경되거나, 다른 함수나 계층으로 객체 등을 전달해야 하는 등의 생명주기를 늘려야하는 상황에서는 이가 오히려 비효율적인 방법이 될 수 있다.
대표적으로 SpringBoot 에서는 기본적으로 Controller → Service → Repository 의 계층으로 객체를 전달해야만 하는데, Stack 만을 활용하는 경우 각 계층에 전달할 때마다 기존의 Stack 값을 복사하여 넘겨야하는 비효율적인 방식을 채택해야만 하기 때문에 이러한 과정 없이 주소 값만을 복사하여 기존 객체를 활용할 수 있는 Heap Memory 가 효율성, 안정성, 속도 측면에서 Stack 보다 더 효율적인 방법이라고 할 수 있다.
결론
상황, 시스템의 요구사항에 따라 Stack 이 무한히 커질 수 있다면, Heap 은 불필요할 수도 있다. Stack 만을 활용하여 코드를 작성하게 되면, 특정 값에 대한 변경 사항이 모두 하나의 함수에 존재하기 때문에 추적이 용이해지며 크지 않은 서비스에서는 유지보수 비용 또한 줄어들 것이라 생각한다.
다만 확장성, 객체 지향 및 단일 책임 원칙을 중요시하는 서비스에서는, Heap 메모리가 필수적으로 활용되어야 한다 생각한다.
최근 많은 서비스들이 거대한 단일 어플리케이션을 분리하여 관리하는 MSA 구조를 채택하곤 하는데, Stack 이 아닌 Heap 메모리를 사용해야만 하는 이유는 이러한 MSA 로 분리했을 때의 이점과 상당 부분 겹치지 않을까 생각한다.
즉, Stack 이 무한히 커질 수 있다면 Heap 메모리는 불필요할 수 있으나 많은 상황에서 Heap 메모리를 활용하는 것이 유리할 것이라 생각한다.