cf) 저수준 언어의 경우
- C 언어 같은 저수준 언어(기계 친화적 언어)에서는 메모리 관리를 위해
malloc()
과free()
를 사용해 개발자가 스스로 메모리를 할당하고 해제해야 한다.
- 필요할 때 개발자가 할당.
- 할당된 메모리를 사용. (Read and Write)
- 메모리가 더이상 필요하지 않으면 해제.
👉🏽 2번은 모든 언어에서 명시적
👉🏽 1, 3번은 저수준 언어에서는 명시적, 자바스크립트 등 고수준 언어에서는 암묵적으로 작동.
let arr = [100, 200, 300, 400]
- JavaScript가 배열과 배열에 담긴 값들을 위한 메모리 크기 할당을 알아서 진행.
참조(reference)
- 명시적이든, 암묵적이든 관계없이 메모리 관리 관점에서 어떤 객체가 다른 객체에 접근할 수 있다면 다른 객체를 참조한다고 말한다.
- ex) JavaScript 객체는 자신의 프로토타입(prototype)에 대해 암묵적인 참조를 갖고 있고, 자신의 속성(property) 값에 대한 명시적 참조도 가지고 있다.
- '객체를 참조한다'고 말할 때, 객체는 일반적으로 자바스크립트 객체를 의미하지만 넓게는 함수 스코프(function scope)나 글로벌 렉시컬 스코프(global lexical scope)까지도 포함한다.
- 렉시컬 스코핑(lexical scoping) : 변수 이름이 중첩된 함수에서 해석되는 방식을 정의하는 것으로, 중첩되어 있는 더 안쪽의 함수는 부모 함수가 값을 반환한 다음에도 부모 함수의 스코프를 포함하고 있다.
레퍼런스 카운팅 순서
1) 객체를 참조하는 변수는 처음에는 특정 메모리에 대해 레퍼런스가 하나뿐이지만, 변수의 레퍼런스가 복사될 때마다 레퍼런스 카운트가 늘어난다.
2) 객체를 참조하고 있던 변수의 값이 바뀌거나, 변수 스코프를 벗어나면 레퍼런스 카운트는 줄어든다.
3) 레퍼런스 카운트가 0이 되면, 그 객체와 관련한 메모리는 비울 수 있다.✅ 레퍼런스 카운트가 0이 된다는 말은 아무도 그 객체에 대한 레퍼런스를 가지고 있지 않다는 말과 같다.
순환참조 예시
function reference() { var obj1 = {}; var obj2 = {}; obj1.p = obj2; obj2.p = obj1; } reference();
- 두 객체가 생성되고 서로를 참조하고 있는 형태이기 때문에 순환 참조가 발생
- 이 객체들은 함수 호출 뒤에는 스코프를 벗어나게 되므로 실질적으로 쓸모가 없게 된다.
- 그래서 이들이 차지하던 메모리는 반환될 수 있지만, 레퍼런스 카운팅 알고리즘에서는 두 객체가 적어도 한 번은 참조한 것으로 간주되기 때문에 둘 다 가비지컬렉션이 될 수 없게 된다.
트레이싱 순서
1) 객체에 in-use flag를 두고, 사이클마다 메모리 관리자가 모든 객체를 추적해서 사용 중인지 아닌지를 표시(mark).
2) 그 후 표시되지 않은 객체를 삭제(sweep)하는 단계를 통해 메모리를 해제한다.(세분화된 순서)
0) mark and sweep 알고리즘이 객체가 필요한지 결정하기 위해 해당 객체에 닿을 수 있는지(reachable)를 판단한다.
1) 가비지컬렉터는 모든 루트의 완전한 목록을 만들어낸다.
2) 그 다음 모든 루트와 그 자식들을 검사해서 활성화 여부를 표시한다
(활성 상태이면 가비지가 아님 / 루트가 닿을 수 없는 것들은 가비지로 표시)
3) 마지막으로 가비지컬렉터는 활성으로 표시되지 않은 모든 메모리를 OS에 반환.** 루트(Roots): 일반적으로 루트는 코드에서 참조되는 전역 변수. 예를 들어 자바스크립트에서 루트로 동작할 수 있는 전역 변수는 window 객체. Node.js에서 이와 동일한 객체는 global.
예상치 못한 참조
- 개발자는 더 이상 사용되지 않을 것이라 생각했지만, 어떠한 이유로 활성화 상태인 루트 트리 안에 존재하는 메모리 조각들.
- 자바스크립트에서 예상치 못한 참조는 더이상 사용되지 않지만 코드 상 어딘가에 유지되어 해제되지 못한 변수들 (거의 대부분 개발자의 실수...)
- 자바스크립트에서 발생할 수 있는 일반적인 메모리 누수 형태들을 이해하기 위해서는 흔히 까먹기 쉬운 참조들을 먼저 알 필요가 있다.