코규리
article thumbnail

 

 

OS 메모리 구조를 다시 봤다.

찬찬히 Stack과 Heap 차이점을 보다가 생긴 의문점이 제목과 같아 차근히 서술해보려고한다.

 

 

1️⃣ Stack과 Heap, 각각에 대하여


Stack의 크기 결정

  • 컴파일 시점에 최대 크기에 대한 결정이 이루어질 수 있다
  • LIFO(Last In, First Out) 방식으로 운영되며, 데이터는 함수 호출과 종료에 따라 자동으로 할당되고 해제된다.

Heap의 크기 결정

  • 런타임 시점에 프로그래머에 의해 동적으로 설정된다

 

2️⃣ Stack과 Heap의 비교


✔ Stack & Heap 공통점

  • 컴퓨터 자체 리소스 크기 영향을 크게 받는다

🧚‍♂️ 음.. 현대의 컴퓨터는 감당할 만큼 커지지 않았나요?

현대에서는 당연히 매우 큰 메모리를 지원하고 있다. 하지만 사용 가능한 물리적 제한이라는 것은 존재하며, 가상 메모리에 의한 제한도 존재한다. 무한이란 건 없다.

✔ Stack & Heap 차이점

  • Stack은 함수나 지역변수가 저장되어 있으며, Heap은 클래스와 같은 참조 타입 데이터가 저장된다
  • Stack은 OS와 Linker의 관리를 받지만, Heap은 개발자가 필요에 따라 메모리를 동적 관리한다
  • 즉, Stack은 운영체제가 자동으로 관리하며, Heap은 개발자가 수동으로 관리한다

 

3️⃣ Stack과 Heap의 접근 속도 차이


✔ 접근 속도 측면

Stack이 Heap보다 접근 속도가 빠르다. 그 이유는 Stack 자체가 프로그램의 실행을 관리하기 위해 '예약'된 메모리 영역이기 때문이다. 이 실행을 위하여 접근 방식 자체가 매우 빠르고 효율적인 방향으로 설계되어 있다

🧚‍♂️ 그 효율적인 방법이 뭔가요?

Stack에 메모리 할당 시, 운영체제는 새 할당을 위하여 Pointer를 사용한다
이를 통해 동적 메모리 관리가 필요하지 않은, 그저 주소값을 가리키는 곳의 변화를 통하여 빠르게 작업이 진행된다.

🧚‍♂️ Heap의 접근 속도는 왜 상대적으로 느린 걸까요?

Heap은 Stack에 비하여 더 복잡한 연산과 과정을 반복한다.
프로그램의 OS로부터 메모리 요청을 받은 후, 그 메모리를 스스로 관리하고 메모리를 해제하는 작업을 계속해야 한다. 가령, C++에서는 'new' 연산자를 통해 메모리 주소가 포인터 변수에 저장되었다가, 메모리 해제는 'delete' 연산자를 사용하여 수행한다. 이 과정이 자동으로 관리되지 않고 명시적으로 할당 및 해제한다는 점에서 느려진다.

🧚‍♂️ Java에서도 프로그래머가 명시적으로 관리하나요?

JVM의 큰 특징인 Garbage Collection은 이를 자동으로 관리한다. 가비지 컬랙션은 JVM에 의해 관리되며, 특정 시점(메모리 할당 요청이 있을 때와 같이)에 자동으로 실행된다. 객체가 더 이상 필요 없게 됐을 때, 즉 어떤 참조 변수도 객체를 가리키지 않을 때 가비지 컬렉터에 의해 메모리가 회수된다. 

 

4️⃣ 헷갈렸던 것, 지역변수가 참조 타입일 수 있잖아.


Stack은 함수나 지역변수가 저장되며, Heap은 참조 타입 객체가 저장된다고 했다. 하지만 지역변수에는 참조 타입 객체가 될 수도 있는 건데, 정확히 구분을 어떻게 지어야 하는 걸까?

MyClass obj = new MyClass;

간단하게도, 이 소스 코드로 이해할 수 있다.

  • obj는 Stack에 저장된다
  • new MyClass는 Heap에 저장된다

즉, 실제 인스턴스는 Heap에 저장되고 객체를 가리키는 참조만이 Stack에 저장되는 것이다. 이러한 메커니즘으로 메모리 관리의 효율성과 프로그램의 유연성이 높아지게 된다.

4️⃣ 실은 더 헷갈리는 것, Stack이 컴파일 시점에 결정된다고?


어디서는 Stack의 크기는 '컴파일 시점에 결정된다'라고 하며, 어디서는 '런타임에 동적으로 결정된다'라는 대립한 두 말이 존재한다. 이는 간단하게 오해를 풀 수 있다.

'Stack의 크기는 런타임에 동적으로 결정된다'라는 말은 컴파일 시 고정된 크기로 설정되는 것을 부정하는 게 아니다. 단, 실제로 사용되는 메모리양이 함수의 호출과 종료에 따라 동적으로 변한다는 것을 의미한다. 즉, 메모리 크기가 런타임에 확장되거나 축소되지는 않지만, 실제 사용되는 Stack의 양이 런타임에 변경된다는 것이다.