메모리는 크게 Stack, Heap 두 영역으로 나뉜다.
그리고 데이터에 종류에 따라서 어떤 영역을 활용하는지가 다르다.
Stack
정적 메모리 할당으로 일단 빠르다.
CPU가 직접 관리하면서 LIFO 방식으로 동작한다.
빠르게 데이터를 관리하기 위해 정적인 데이터만 다룬다.
Number, String, Boolean과 같은 원시 타입이 주로 들어가고
javascript에 경우 실행 컨텍스트에 함수가 호출될 때 생성되는 지역 변수와 매개 변수가 들어간다.
다만 효율적인 관리가 들어가는 만큼 저장 공간이 제한적이다.
만약 사용 공간을 초과하면 한번쯤 들어본 Stack Overflow가 발생한다.
Heap
동적 메모리 할당으로 크기가 수시로 변할 수 있는 복잡한 데이터를 저장한다.
객체와 같은 Object, Array, Function 등의 참조 타입이 대상이다.
가비지 컬렉터의 주요 정리 대상이다.
const 객체의 속성 변경
우리가 const로 선언한 변수는 변경 할 수 없다.
이때 변경 불가능의 기준은 Stack이다.
const a = 10
에 경우에서 a라는 변수에 10이라는 원시 데이터를 선언하면,
b=a 과정에서 값이 복사되어 서로 독립적이다.
이때 메모리 할당 방식은 Stack에 값 자체를 저장하는 방식이다.
const a = {x:1}
위 경우에서는 우리는 a 자체는 변경이 불가능 하지만,
a에 할당된 x에 값은 변경이 가능하다.
이는 a 변수 자체는 Stack에 기록되어있지만,
값이 아닌 주소를 저장하기 떄문이다.
주소를 저장하기에 다른 변수 주소로 변경은 불가능 하지만,
값은 Heap에 저장되어 있기에 내부 속성에 대한 변경은 가능하다.
Thread의 상호작용
Stack이 실행 컨텍스트와 연관이 있다는 것을 보면 알 수 있지만,
Stack은 메서드의 호출과 실행을 관리한다.
Thread와 연계되면 스레드마다 스택이 하나씩 생성되며 서로간에 공유되지 않는다.
이때 스택의 수명은 함수의 라이프 사이클과 같이 동작하기에,
만약 내부적으로 문제가 생겨 자원에 대한 해제나 스레드 관리가 안되는 경우
메모리 해제가 안되서 메모리 누수가 발생한다.
스택에 용량은 한계가 있기에 재귀 호출이 깊어지거나 메서드 호출이 무한 루프에 빠지면 Stack OverFlow가 발생하는데,
스레드를 다룰 때, 각각의 Task를 간단하게 가져가는 이유가 여기에 있다.
Stack과 Heap의 연결
public void myMethod() {
int age = 30;
Person person = new Person();
}
위 코드에서 age는 정적 변수이기 때문에 Stack에 저장된다.
pserson 변수에 대해서는 먼저 Heap부터 시작한다.
Person 클래스의 인스턴스가 생성되고 접근 메모리 주소가 할당 된다.
Stack 영역에 pserson 이라는 로컬 변수가 생성되면 아까 생성된 메모리 주소(힙 주소)를 저장한다.
즉 Stack은 Heap에 있는 데이터를 조작하기 위한 참조를 쥐고 있는 셈이다.
Stack과 다르게 Heap은 다른 스레드끼리 접근이 가능한데,
해당 주소지를 바탕으로 여러 스레드가 동시에 pserson 객체를 참조해 수정하려하는 경우 동시성 이슈가 발생할 수 있다.