Study JavaScript 0502 - 1hr 복사와 가비지 컬렉션

변승훈·2022년 5월 2일
0

Study JavaScript

목록 보기
11/43

1. 참조에 의한 객체 복사

원시형과 객체의 차이는 참조에 의해 저장되고 복사된다는 것이다.
원시형은 값 그대로 저장, 할당되고 복사된다.

  • 원시형: 아래의 예시를 실행하면 두 개의 독립된 변수에 각각 문자열이 저장된다.
    let message = "Hello!"
    let pharse = message;
  • 참조형: 객체의 동작 방식은 위의 원시형과 다르게 변수에 객체가 그대로 저장되는 것이 아닌, 객체가 저장되어있는 '메모리 주소'인 객체에 대한 '참조 값'이 저장된다. 아래의 예시에서 변수는 두 개지만 각 변수에 동일 객체에 대한 참조 값이 저장된다.
    쉽게 말하자면 객체가 저장된 '메모리 주소'를 두 변수가 바라보게 된다고 이해하면 된다.
    let user = {
      name: 'Hun'
    }
    let admin = user;

따라서 객체에 접근하거나 객체를 조작할 때 여러 변수를 사용할 수 있다.

    let user = {
      name: 'Hun'
    }

    let admin = user;

    admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨

    console.log(user.name); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함
  • 참조에 의한 비교
    객체 비교 시 동등 연산자 '=='와 일치 연산자 '===' 는 동일하게 동작한다.
    let a = {};
    let b = a; // 참조에 의한 복사
    // console.log( a == b ); // true, 두 변수는 같은 객체를 참조
    // console.log( a === b ); // true

다른 예시로 두 객체가 비어있는 상태를 비교해보면 false가 나온다.
그 이유는 생긴건 같아 보이지만 실제로 바라보고 있는 주소가 다르기 때문이다.

    let a = {};
    let b = {}; // 독립된 두 객체

    console.log( a == b ); // false
    console.log( a === b ); // false

객체 복사, 병합과 Object.assign

기존에 있던 객체와 똑같으면서 독립적인 객체를 만들고 싶다면 새로운 객체를 만든 다음 기존의 객체의 프로퍼티들을 반복문으로 원시 수준까지 복사하면 된다.

    let user = {
      name: "Hun",
      age: 27
    };

    let clone = {}; // 새로운 빈 객체

    // 빈 객체에 user 프로퍼티들을 반복문으로 넣는다.
    for (let key in user) {
      clone[key] = user[key];
    }

    clone.name = "Pete"; // clone의 데이터를 변경한다.
    console.log( user.name ); // 기존 객체에는 여전히 Hun이 있다.

다른 방식으로 Object.assign(dest, [src1, src2, src3...])을 이용하는 방법이 있다.
첫 번째 인수 dest는 목표로 하는 객체이며 이어지는 인수들은 복사하고자 하는 객체이다.
dest를 제외한 인수(객체)의 프로퍼티들은 전부 첫 번째 인수로 복사되며 마지막에 dest를 반환한다.
이를 이용하면 반복문 없이 간단하게 객체를 복사할 수 있다.

    let user = {
      name: "Hun",
      age: 27
    };

    let permissions1 = { 
      canView: true 
    };
    let permissions2 = { 
      canEdit: true 
    };

    // permissions1과 permissions2의 프로퍼티를 user로 복사한다.
    Object.assign(user, permissions1, permissions2);

    // user = { name: "Hun", age: 27,canView: true, canEdit: true }

중첩 객체 복사

프로퍼티가 다른 객체에 대한 참조 값일 경우 프로퍼티를 복사하는 것만으론 객체를 복사할 수 없다.

    let user = {
      name: "Hun",
      sizes: {
        height: 182,
        width: 50
      }
    };

이 문제를 해결하려면 user[key]의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 한다.
이런 방식을 '깊은 복사(deep cloning)'라고 하며 javascript library lodash의 메소드인 .cloneDepp(obj)를 사용하면
알고리즘을 구현하지 않고도 깊은 복사 처리를 할 수 있다.

2. 가비지 컬렉션

javascript는 눈에 보이지 않는 곳에서 메모리 관리를 수행한다.
원시 값, 객체, 함수 등 우리가 만든 모든 것들은 메모리를 차지하는데 이때 더는 쓸모 없어지게 된 것들은 어떻게 처리가 될까?

자바스크립트 엔진 내에선 가비지 컬렉터(garbage collector)가 끊임없이 동작한다. 가비지 컬렉터는 모든 객체를 모니터링하고, 도달할 수 없는 객체는 삭제한다.

  • 가비지 컬렉션 기준
    javascript는 도달 가능성(reachability) 이라는 개념을 사용해 메모리 관리를 수행한다.
    ‘도달 가능한(reachable)’ 값은 쉽게 말해 어떻게든 접근하거나 사용할 수 있는 값을 의미하며 도달 가능한 값은 메모리에서 삭제되지 않는다.
  1. 아래의 값들은 그 태생부터 도달 가능하기 때문에, 명백한 이유 없이는 삭제되지 않는다.
  • 현재 함수의 지역 변수와 매개변수
  • 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
  • 전역 변수
  • 기타 등등

이런 값은 루트(root) 라고 부른다.

  1. 루트가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 된다.

전역 변수에 객체가 저장되어있다고 가정해 보자. 이 객체의 프로퍼티가 또 다른 객체를 참조하고 있다면, 프로퍼티가 참조하는 객체는 도달 가능한 값이 된다.

이 객체가 참조하는 다른 모든 것들도 도달 가능하다고 여겨지는데 예시를 통해 살펴보자.

    let user = {
      name: "Hun"
    };

    user = null;

user가 name: "Hun"을 바라보고 있다가 null로 값을 덮어쓰면서 바라보지 않는 상태가 되었다.
가비지 컬렉터는 name: "Hun"을 메모리에서 삭제한다.

만약 다른 변수도 참조를 하고 있다면 아직 바라보고 있기에 가비지 컬렉터가 삭제하지 않는다.

연결된 객체로 또 하나의 예시를 보자.

    function marry(man, woman) {
      woman.husband = man;
      man.wife = woman;

      return {
        father: man,
        mother: woman
      }
    }

    let family = marry({
      name: "John"
    }, {
      name: "Ann"
    });
    // 현재 모든 객체가 도달 가능한 상태인데 참조 2개를 지워보자
    delete family.father;
    delete family.mother.husband;

최종적으로 name: "John"을 바라보게 되는 것이 없으므로 가비지 컬렉션이 정리해준다.

내부 알고리즘 (mark-and-sweep)

'가비지 컬렉션’은 대개 다음 단계를 거쳐 수행된다.

  1. 가비지 컬렉터는 루트(root) 정보를 수집하고 이를 ‘mark(기억)’ 한다.
  2. 루트가 참조하고 있는 모든 객체를 방문하고 이것들을 ‘mark’ 한다.
  3. mark 된 모든 객체에 방문하고 그 객체들이 참조하는 객체도 mark 한다. 한번 방문한 객체는 전부 mark 하기 때문에 같은 객체를 다시 방문하는 일은 없다.
  4. 루트에서 도달 가능한 모든 객체를 방문할 때까지 위 과정을 반복한다.
  5. mark 되지 않은 모든 객체를 메모리에서 삭제한다.
  • 최적화 기법
  1. generational collection(세대별 수집) – 객체를 '새로운 객체’와 '오래된 객체’로 나눈다. 객체 상당수는 생성 이후 제 역할을 빠르게 수행해 금방 쓸모가 없어지는데, 이런 객체를 '새로운 객체’로 구분한다. 가비지 컬렉터는 이런 객체를 공격적으로 메모리에서 제거한다. 일정 시간 이상 동안 살아남은 객체는 '오래된 객체’로 분류하고, 가비지 컬렉터가 덜 감시한다.
  2. incremental collection(점진적 수집) – 방문해야 할 객체가 많다면 모든 객체를 한 번에 방문하고 mark 하는데 상당한 시간이 소모된다. 가비지 컬렉션에 많은 리소스가 사용되어 실행 속도도 눈에 띄게 느려지겠죠. 자바스크립트 엔진은 이런 현상을 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 각 부분을 별도로 수행한다. 작업을 분리하고, 변경 사항을 추적하는 데 추가 작업이 필요하긴 하지만, 긴 지연을 짧은 지연 여러 개로 분산시킬 수 있다는 장점이 있다.
  3. idle-time collection(유휴 시간 수집) – 가비지 컬렉터는 실행에 주는 영향을 최소화하기 위해 CPU가 유휴 상태일 때에만 가비지 컬렉션을 실행한다.
profile
잘 할 수 있는 개발자가 되기 위하여

0개의 댓글