[JS] 가비지 컬렉션

MJ·2022년 9월 4일
0

Java Script

목록 보기
33/57
post-thumbnail

가비지 컬렉션

객체를 생성하면 메모리상 어딘가에 할당됩니다. 눈에 보이지 않는 메모리 공간들을
JS가 관리하는데, 참조할 수 없는 메모리 공간 은 가비지 컬렉션에 의해 삭제된다.


1) 가비지 컬렉션 기준

JS는 도달 가능성(reachability)이라는 개념을 사용해서 메모리를 관리합니다.
도달 가능성이라는 것은, 어떻게든 접근할 수 있고 사용할 수 있는 값을 의미 합니다.
이러한 값들은 삭제되지 않습니다.


2) 가비지 컬렉션이 삭제하지 않는 값들

도달이 가능한, 즉 참조가 가능한 값들은 가비지 컬렉션이 삭제하지 않습니다.
도달이 가능한 값들을 루트(root)라고 부릅니다. 루트 혹은 루트가 참조할 수 있는
값들은 가비지 컬렉션에 의해 삭제되지 않습니다. 대표적으로 아래 경우가 있습니다.

  • 현재 함수의 매개변수와 지역 변수
  • 중첩 함수의 체인에 있는 함수에서 사용되는 매개변수와 지역 변수
  • 전역 변수
  • 기타

🔔 중첩 객체도 삭제되지 않는다.

객체를 통해서 객체안의 객체에 접근할 수 있어, 이 값들도 참조가 가능하여 삭제되지
않습니다. 여기서 객체를 root라고 하며, 중첩 객체는 root가 참조하는 값이 됩니다.

🔔 지속적인 동작

JS 엔진 내부에서는 가비지 컬렉터가 지속적으로 동작합니다.
가비지 컬렉터는 모든 객체를 모니터링하고, 도달할 수 없는 객체는 삭제합니다.


3) 가비지 컬렉터 동작

let user = { name: "John" }; // 객체 선언, user 변수는 이 객체에 접근할 수 있다.


1) `user` 변수는 `{ name: "John" }` 객체를 참조한다.
2) 객체의 프로퍼티 키 `name`은 원시 값 `"John"`을 저장하고 있습니다.
3) user 변수를 다른 값으로 덮어 씌우면, { name: "john" } 객체는 접근할 수 없게 된다.


💘 객체를 참조할 수 있는 변수가 없다

user = null;

1) user에 다른 값을 덮어 씌우면, { name: "John" } 객체는 도달할 수 없는 값이 된다.
2) 가비지 컬렉션에서, 이 객체를 확인하고 메모리에서 삭제하게 된다.


4) 하나의 객체를 여러 변수가 참조

이번에는 객체 하나를 user 변수와 admin 변수가 참조하는 예시를 보겠습니다.


let user = { name: "John" };
let admin = user;	// user가 참조하는 객체의 참조 값을 복사한다.


이제 user 변수에 값을 다른 값으로 덮어씌우면 어떻게 될까요?


let user = null

admin 변수는 생성된 객체의 참조 값을 복사 했기 때문에, user 변수가 사라져도 객체는
접근할 수 있습니다. 가비지 컬렉션이 메모리에서 삭제하지 않게 되지요.



연결된 객체

객체끼리 서로 접근할 수 있게 연결 시킬 수 있습니다.
그리고 그 연결의 시작점을 근원 객체라고 합니다.


function marry(man, woman) {
 woman.husband = man;
 man.wife = woman;
  
 return { 
   father: man;
   mother: woman;
 }
  
}

let family = marry( 	// marry 함수의 인수 값으로 객체를 전달
  { name: "John" },		// 첫 번째 객체
  { name: "Ann" }		// 두 번째 객체
  );


/* */
(1)
marry 함수를 호출하고 인수 값으로 객체를 전달.
{name: "John"}, {name: "Ann} ( 두 객체는 서로 다른 객체입니다 )

                 
(2)
marry 함수에는 인자 값으로 두 객체를 참조하는 값이 복사됩니다.
man = { name: "John" } // woman = { name: "Ann" }
         
                 
(3)
woman.husband = man;
1. woman 객체에 husband 프로퍼티 생성 > 값으로는 man 객체의 참조 값 할당

man.wife = woman;
1. man 객체에 wife 프로퍼티 생성 > 값으로는 woman 객체의 참조 값 할당                 


(4)
return { father: man, mother: woman };
1. 반환 값으로 새로운 객체 생성 > 프로퍼티에는 father와 mother이 생성 됨
2. father은 man 객체를 참조할 수 있음
3. mother은 woman 객체를 참조할 수 있음 


(5)
let family 변수는 { father: man, mother: woman }; 객체를 참조한다.

그림으로 표시하면 위 같은 관계 형성도가 생성 됩니다.
family {} 객체는 모든 객체에 접근할 수 있고, manwoman객체는 서로 접근할
수 있는 관계가 되었네요. 여기서 family객체를 근원 객체라고 합니다.


1) 외부에서 접근할 수 없는 객체는 삭제 된다.

위 예제에서 family.father 프로퍼티와 woman.husband 프로퍼티를 삭제하면
man 객체는 어떻게 될까요? 외부에서 자신을 접근할 수 있는 객체가 없기에 삭제 됩니다.


delete family.father;			// 첫 번째 객체를 참조할 수 없어짐.
delete family.mother.husband;	// 두 번째 객체는 첫 번째 객체를 참조할 수 없어짐.


/* */
여기서 첫 번째와 두 번째 객체는 위 예시에 주석을 확인하시면 됩니다.
첫 번째 객체 { name: "John" }
두 번째 객체 { name: "Ann" }


man 객체는 외부에서 접근할 수 없기에, 자신이 woman 객체를 참조하고 있어도 가비지
컬렉터가 이를 삭제합니다. 가비지 컬렉터는 외부에서 자신을 참조할 수 있는 데이터가
없다면 도달 가능성이 없다고 판단하여 삭제합니다.

⚠️ 가비지 컬렉션은 외부로 나가는 값은 도달 가능한 값이라고 생각하지 않습니다.



2) family 객체를 제거하면?

객체들이 연결되어 섬같은 구조를 만듭니다. 이 섬에 도달할 수 있는 객체를 삭제하면
남은 객체들도 메모리에서 모두 삭제 됩니다.

여러 객체들의 시작점이 되는 객체를 근원 객체라고 합니다.

family = null;

/* */
이 섬에서 근원 객체는 family 입니다. 이 객체의 값을 없애버리면 나머지 객체들 또한
모두 삭제됩니다.

현재 메모리의 내부 상태입니다. { name: "john" }, { name: "Ann" } 객체는 모두 외부
에서 자신을 참조하지만, 이것만으로는 부족합니다. family 객체와 루트의 연결점이 사라지면
루트 객체를 참조하는 것이 아무것도 없게 됩니다. 즉, 저 객체들에 접근할 수 있는 방법이 없어
메모리상에서 삭제됩니다.

family {} 객체를 삭제하면 남은 객체들은 왜 삭제 될까요?

위 예제에서 family 객체를 제외한 man woman 객체는 함수 내부에서 생성 된
객체 입니다. 즉 전역공간에서는 함수 호출을 하지 않으면 자신에게 접근할 수 없습니다.
유일하게 family 객체가 자신들의 참조값을 가지고 있으므로, 전역 공간에서도 접근할
수 있게 됩니다. 모든 객체는 family.을 통해서 접근하게 되죠.
family.father.name 또는 family.mother.name

family 객체가 사라진다면 전역에서 자신을 접근할 수 없게 되므로 가비지 컬렉터로
인해서 메모리에서 삭제되게 됩니다.


내부 알고리즘

mark-and-sweep라고 불리는 가비지 컬렉션 기본 알고리즘에 대해 알아봅시다.
가비지 컬렉션은 대개 다음 단계를 거쳐서 수행합니다.

  • 가비지 컬렉터는 루트(root)정보를 수집하고 이를 mark(기억)합니다.
  • 루트가 참조하고 있는 모든 객체를 방문하고 이들을 mark 합니다.
  • mark 된 모든 객체에 방문해서, 그 객체들이 참조하고 있는 객체도 mark 합니다.
    한 번 방문한 객체는 mark 하기 때문에 다시 방문하지 않습니다.
  • 루트에서 도달 가능한 모든 객체를 방문할 때 까지 위 과정을 반복합니다.
  • mark 되지 않은 모든 객체는 메모리상에 삭제됩니다.


위와 같은 객체구조가 있다고 가정해봅시다.
우측에는 전역에서 접근할 수 없는 섬이 있습니다. 저 섬을 가비지 컬렉션이 어떻게 삭제를
하는지 과정을 살펴보겠습니다.


1. 도달할 수 있는 'root'를 모두 'mark' 합니다.

2. 이후 루트가 참조하고 있는 모든 것들을 'mark' 합니다.

3. 도달 가능한 모든 객체를 방문할 때 까지 'mark' 를 진행합니다.

4. 방문할 수 없었던 객체들은 메모리에서 삭제됩니다.



가비지 컬렉션 최적화 기법

generational collection(세대별 수집)

객체를 '새로운 객체'와 '오래된 객체'로 분리합니다. 생성된 이후 금방 역할이 종료되어
쓸모가 없어진 객체를 '새로운 객체'라고 하며 이런 객체들은 메모리에서 바로 제거합니다.
일정시간 동안 살아남은 객체는 '오래된 객체'로 분리하며 바로 제거하지 않고 덜 감시합니다.

incremental colleqction(점진적 수집)

방문해야할 객체가 많다면 모든 객체를 한 번에 방문하고 mark 하는데 상당한 시간이 소모
됩니다. 이를 해결하기 위해 가비지 컬렉션을 여러 부분으로 분리하고, 각 부분을 별도로 수행
합니다. 긴 지연시간을 짧은 지연시간 여러개로 분산시킬 수 있는 장점이 있습니다.

idle-time collection(유휴 시간 수집)

가비지 컬렉터를 실행할 때 발생하는 영향을 최소화하기 위해 CPU가 유휴 상태일 때만
가비지 컬렉션을 실행합니다.



정리

가비지 컬렉션은 JS 엔진이 자동으로 수행하므로 개발자는 이를 중단할 수 없다.


객체는 접근이 가능한 상태일때만 메모리상에 상주합니다.
서로 참조가 된다고 해도, 전역에서 참조할 수 없다면 도달할 수 없는 상태가 됩니다.

profile
프론트엔드 개발자가 되기 위한 학습 과정을 정리하는 블로그

0개의 댓글