변수와 메모리 동작
1. 변수의 정의
2. 변수가 필요한 이유
3. 자바스크립트 메모리 접근
4. 자바스크립트 변수의 동작
변수의 정의
변수란 하나의 값을 저장하기 위해 확보한 메모리 공간 자체, 또는 그 공간을 식별하기 위해 붙인 이름입니다.
변수가 필요한 이유
1 | 10 + 20; |
다음과 같은 식을 코드에서 실행한다고 생각해보겠습니다.
자바스크립트 엔진이 위의 식을 해석하면 10과 20은 컴퓨터의 메모리에 담기고 +연산자는 cpu에서 실행되게 됩니다. 이 때 메모리에 10과 20, 그리고 연산 결과 30은 2진수의 형태로 메모리 셀에 저장되게 됩니다.
이렇게 30이라는 값을 연산하여 얻어냈다는 것은 그 값을 사용해 무엇인가를 하겠다는 의도가 있을 것입니다. 30이라는 값을 재사용하기 위해서는 저장된 메모리 공간에 대한 접근이 필요합니다.
자바스크립트 메모리 접근
자바스크립트는 메모리에 직접적인 접근을 허용하지 않는다!
저장된 값을 사용하기 위해서 메모리에 접근하는 것이 필요하다 했습니다. 그러나 메모리에 직접적으로 접근하는 것은 치명적인 오류를 발생시킬 수 있습니다. 만약 실수로 운영체제가 사용하고 있는 값을 변경하게 되면 시스템이 멈출 수도 있습니다.
또한 자바스크립트는 값이 저장될 메모리 주소를 코드가 실행될 때마다 메모리 상황에 따라 임의로 결정합니다. 따라서 동일한 코드를 실행해도 실행마다 값이 저장되는 메모리 주소가 변경되고, 이전 값이 저장된 주소를 알수도 없으며, 알려주지도 않기 때문에 값이 저장된 메모리를 직접적으로 찾는 것은 올바른 방법이 아닙니다.
자바스크립트는 변수를 통해 간접적으로 메모리에 접근한다!
따라서 자바스크립트에서는 메모리에 접근할 때 변수라는 요소를 통해 값이 저장된 메모리에 간접적으로 접근하여 안전하게 사용할 수 있습니다. 이러한 메모리 관리 방식을 가진 언어를 매니지드 언어라고 합니다.
매니지드 언어 VS 언매니지드 언어
프로그래밍 언어는 메모리 관리 방식에 따라서 매니지드 언어와 언매니지드 언어로 분류됩니다. 두 메모리 관리 방식은 성능과 생산성에 있어 장단점을 지닙니다.
매니지드 언어
자바스크립트와 같은 매니지드 언어는 메모리의 할당이나 해제를 언어 자체에서 담당하고 개발자의 직접적인 메모리 접근을 허용하지 않습니다. 즉, 개발자가 명시적으로 메모리를 제어할 수 없습니다. 메모리 접근은 변수를 통해 간접적으로 이루어지며, 메모리의 해제 또한 ‘가비지 컬렉터’를 통해 자동적으로 이루어집니다.
이와 같은 특성을 지닌 매니지드 언어인 자바스크립트는 개발자가 직접 제어해야 하는 부분이 줄어든 만큼 일정한 생산성을 확보할 수 있다는 장점이 있지만 성능 면에서는 손실을 감수해야합니다.
언매니지드 언어
C 언어와 같은 언매니지드 언어는 개발자가 명시적으로 메모리를 제어합니다. 개발자 주도로 메모리를 제어하기 때문에 개발자의 역량에 따라 최적의 성능을 확보할 수 있다는 장점이 있지만 반대의 경우 오히려 성능의 최적화가 힘들 수 있습니다. 또한 매니지드 언어에 비해 부족한 생산성이 단점이 될 수 있습니다.
자바스크립트 변수의 동작
변수 선언
변수를 사용하기 위해 변수를 선언해야 합니다. 먼저 선언을 통해 자바스크립트 엔진에 식별자의 존재를 알립니다. 이 때 자바스크립트 엔진은 식별자가 참조할 메모리 공간을 확보하게 됩니다. 이후에 자바스크립트 엔진은 실행 컨텍스트에 식별자를 등록하게 됩니다.
메모리의 공간을 확보한다 했으니 해당 메모리가 비어있다고 생각할 수 있지만, 사실 메모리는 비어있는 것이 아닌 이전에 다른 어플리케이션에서 사용하고 참조가 끊어진 쓰레기 값(garbage value)가 들어있으며 자바스크립트 엔진은 암묵적으로 undefined를 할당하여 초기화하게 됩니다.
왜 undefined로 초기화하는 과정을 거칠까?
초기화 과정을 거치지 않는다면 이전에 썼던 쓰레기 값이 들어있는 메모리셀을 참조하게 되기 때문에 할당 직전 값을 참조하게 될 경우 원하지 않는 동작이 일어날 수 있습니다.
1 | var test; |
위 코드를 살펴보겠습니다. test라는 식별자에 어떠한 값도 할당하지 않고 1을 더한 연산한 결과를 출력했기 때문에 숫자가 아니라는 NaN 값이 나오게됩니다. 하지만 초기화를 하지 않아 쓰레기 값으로 ‘1’과 같은 값이 들어있다고 가정해보면 자바스크립트의 암묵적 타입 변환을 통해 ‘11’이라는 값이 출력되게 됩니다.
즉 초기화 과정을 거치지 않게 되면 코드 동작의 일관성을 저해하게 되며 개발자가 예상하지 못하는 결과를 출력할 수 있게 되기 때문에 초기화 과정이 필요합니다.
변수 선언의 시점
자바스크립트 엔진은 코드를 읽기 전에 가장 먼저(런타임 이전) 전체 코드에서 선언문을 전부 가져와 실행컨텍스트에 등록을 하게 됩니다. 이 후 변수 선언 키워드에 따라 초기화와 할당의 시점이 달라지게 됩니다. 변수 선언이 코드를 읽기 전에 가장 먼저 실행된다는 것을 다음 코드를 통해 확인할 수 있습니다.
1 | console.log(test); // output: undefined |
만약 코드가 실행되는 런타임에 변수가 선언되었다면, 선언문 위에 있는 console.log(test)는 아직 선언 이전이기 때문에 ‘식별자를 참조하려 했으나 식별자를 찾을 수가 없어!’라는 뜻의 Reference 에러를 내야합니다. 그러나 코드가 정상 동작하는 것을 위 코드에서 확인할 수 있습니다.
이것을 통해 변수의 선언은 코드가 실행되는 런타임이 아닌 그 이전에 먼저 실행된다는 것과 undefined로 초기화까지 일어난다는 것을 알 수 있습니다. 이러한 현상을 ‘호이스팅’이라고 합니다. 자바스크립트의 모든 변수, 함수, 클래스는 이렇게 런타임 이전 식별자 정보를 실행컨텍스트에 먼저 등록하는 과정을 거치게 됩니다.'호이스팅'에 대해서는 차후 다른 포스팅에서 자세히 다루겠습니다.
1 | console.log(test1); // ReferenceError |
그러나 위의 코드의 경우(let과 const 키워드를 사용한 변수 선언) ReferenceError를 내보내게 됩니다. 그 이유는 var 키워드와 ‘초기화 시점’이 다르기 때문입니다.
let과 const의 경우 초기화 과정과 할당 과정이 선언과 분리되어 코드가 직접 실행되는 런타임에 이루어집니다. 위에서 말했듯이 초기화는 이전 쓰레기 값의 참조를 방지하기 위해 꼭 필요한 작업입니다. 따라서 초기화 시점이 다른 let과 const의 경우 초기화가 되기 이전 상황에서는 메모리에 접근할 수 없기 때문에 이러한 오류를 내보내게 됩니다.
변수 할당
1 | var score = 100; |
위 코드는 하나의 문처럼 보이지만 사실 두개의 문의 단축 표현입니다. 즉 자바스크립트는 변수 선언과 값의 할당을 2개의 문으로 나누어 각각 실행합니다. 따라서 단축 표현시에도 식별자 선언 후 undefined가 먼저 먼저 할당되어 초기화, 그 이후 100이라는 값이 할당이 되는 것은 변함이 없습니다.
1 | var score; |
변수 초기화와 할당 시 메모리 동작
변수에 값이 할당될 때, undefined가 저장되어있는 메모리 공간을 지우고 그 메모리 공간에 할당 값 80을 다시 넣어주는 것이 아닙니다. 값을 할당할 때에는 변수가 참조할 메모리 영역을 새로 확보하고 그 메모리 영역을 변수가 새로이 참조하게 됩니다.
왜 undefined로 초기화 된 영역을 업데이트하는 것이 아니라 새로운 영역을 확보하고 값을 할당할까? 자바스크립트가 개발자의 메모리 제어를 허용하지 않기 때문이다. 메모리를 확보하고 그 메모리의 값을 바꾼다는 것 자체가 메모리를 제어하게 되는 것이다. 엔진은 자바스크립트 기본원리에 따라 매번 값의 생성시에 임의로 새로운 메모리 주소를 부여한다. 이 때문에 메모리 동작에 있어서 매니지드 언어는 성능 상 손해를 감수할 수 밖에 없다.
변수 할당 전 undefined로 초기화 되는 과정은 변수의 값이 저장될 공간을 미리 확보하는 것이 아닌 할당 전 변수에 접근했을 때 예상치 못한 결과를 막기 위한 것을 다시 한번 기억해야합니다.
재할당의 경우도 마찬가지입니다. score의 값을 200으로 재할당 한다면 기존 100이 저장된 메모리의 값을 변경하는 것이 아니라 새로운 메모리 공간을 확보하여 값을 저장합니다.
가비지 컬렉터
변수의 동작 과정에서 초기화, 할당, 재할당 시 새로운 메모리 공간을 확보하게 된다고 했습니다. 이 때 변수가 더 이상 참조하지 않게 되는 메모리 공간은 어떻게 될까요?
가비지 컬렉터가 메모리를 해제하여 메모리 누수를 방지합니다. 언제 가비지 컬렉터가 메모리를 해제하는지는 예측할 수 없습니다. 또한 가비지 컬렉터는 단순히 확보한 메모리 (더 이상 이 메모리를 참조할 수 없는 상태)를 해제할 뿐이지 메모리에 저장된 값 자체를 삭제하지는 않습니다.