js study #객체, GC

Oak_Cassia·2023년 11월 25일
0

js 스터디 2023

목록 보기
2/5

js

  • plain object, Array, Date, Error ...

객체 (plain object)

  • 원시값

    • 문자형, 숫자형, Bigint, boolean, symbol, null, undefined
  • 객체

    • 프로퍼티(key value 쌍)로 구성

      • key: 문자형
      • value: 모든 자료형
      • 순서
        • 정수 프로퍼티는 오름차순
        • 나머지 프로퍼티는 추가된 순
    • 생성

      • let obj = new Object();
      • let obj ={}; // 객체 리터럴
        let hwi = {
             name: "hi",
             age: 20,
             "is human": true, 
         };
    • 수정

      • hwi.name = 'hwi';
      • hwi["is human] = false
    • 삭제

      • delete hwi.name
      • delete hwi["is human"]
    • 동적인 키

      • 변수 입력된 값으로 접근할 수 있음

         let key = "is human";
          hwi["is human"] = true;
        
      • 동적으로 key를 사용할 때 대괄호 입력

        function dog(name) {
            let obj = {
                [name]: 5,
            };
         }
      • key와 value 값이 같으면 단축 가능

        return {
             name, 
             age,
         }
    • key는 문자형이기 때문에 예약어와 같은 이름 가능

      • __proto__ 는?
    • in

      • 객체에 프로퍼티가 있는지 확인
        • 존재하지 않는 프로퍼티에 접근하면 undefined 반환
        • in 연산자를 사용하여 확실하게 프로퍼티가 있는지 확인
      • for 문에 써서 foreach 처럼 반복 할 수 있음
        • for (key in obj) { }
    • 메서드

      • 함수는 값으로 간주되어 프로퍼티로 추가 가능
      • this
        • 메서드 내부에서 객체 접근
        • runtime에 결정되어 일반 함수 내에 사용되어도 객체를 통해 호출하면 참조될 수 있음
          • 점 앞의 객체 참조
    • 생성자

      • new 이후 첫글자 대문자 new Dog();
        • this에 빈 객체 할당 (암시적)
        • 생성자 실행 및 this에 프로퍼티 추가
        • this 반환 (암시적)
      • 함수 앞에 new 를 붙여서 실행하면 위 알고리즘 진행
      • 리터럴은 코드 재사용성이 낮음
      • new.target : 함수 내부에서 사용, boolean 반환, new로 호출되었으면 true
      • return
        • 객체 반환 시 this 대신 반환
        • 원시형(및 return;) 반환 시 return 문 무시 (this 반환)
      • this.val에 함수를 할당해 메서드로 사용
    • optional chaining

      • ?.
      • ?앞의 대상이 undefined, null 이라면 즉시 undefined 반환
      • null 참조를 막을 수 있음

심볼형

  • 문자형과 함께 객체 프로퍼티의 키로 사용

  • 유일한 키

    • Symbol("key"): 같은 문자열로 심볼을 만들어도 다른 심볼이면 서로 다름
  • let key1 = Symbol("key");
     let key2 = Symbol("key");
     // key1 == key2  (flase)
     
     let dog = {
       name: "arrr",
       [key2]: 55,
     };
    
     dog[key1] = 1; // 사용 예시
     
  • 심볼은 확장을 위한 기능

  • 심볼형은 암시적 변환 X

  • 심볼을 키로 만든 프로퍼티는 hidden property

    • key 값이 같아서 생기는 충돌 방지
    • for let key in obj: 반복문에 포함되지 않음
    • Object.keys(): 배열에 포함되지 않음
    • Object.assign(): 객체에 포함됨
  • global symbol registry

    • 이름이 같을 때 같은 심볼을 가리키려면
      • Symbol.for("key"): 심볼이 없으면 생성, 있으면 읽음
      • Symbol.keyFor(symbol): 심볼을 넘겨주고 문자열 반환, 전역 심볼이 아니면 undefined
  • system symbol: 내장 심볼

참조

  • 객체는 대입 연산을 하면 참조로 작동
  • 복사하려면
    • 순회
    • Object.assign
      • 객체 프로퍼티는 얇은 복사

객체 -> 원시형 캐스팅

  • boolean
    • 항상 true
  • 숫자
  • 문자
  • hint
    • string, number, default
    • 객체에 Symbol.toPrimitive(hint)메서드가 있으면 호출 (시스템 심볼)
    • 없고 string이면 toString 또는 valueOf 호출 (toString이 없으면 valueOf)
    • 위 사항이 모두 아니고 number, default면 valueOf 또는 toString 호출
  • Symbol.toPrimitive 함수는 hint를 받아 이에 따른 적절한 행동
  • 기본 toString, valueOf 반환값
    • [object Object]
    • valueOf는 객체 자신을 반환 (호출 무시?)
  • 와일드카드?
    • Symbol.toPrimitive와 valueOf가 없으면 toString이 모든 형 변환 처리
  • 항상 hint에 명시된 자료형을 반환해주는 것은 아님(항상 원시값 반환은 맞음)
  • 객체가 피연산자일 때
    1. 앞서 본 규칙에 따라 원시형으로 변환
    2. 변환 후 타입이 적절치 않은 경우 한 번 더 형변환

GC

  • reachability 기준으로 메모리 관리
  • root 가 될 수 있는 값
    • 항상 도달할 수 있는 것
      • 지역, 매개, 전역 변수 등...
  • 스택에서 루트를 검색
    • 지역 변수가 가리키는 객체는 루트 객체
    • 전역 객체는 루트 객체
  • mark and sweep
    • root를 mark
    • root가 참조하는 것 mark
    • mark 한 객체가 참조한느 것 mark
    • 갱신되지 않을 때 까지 반복
    • mark 안된 것 삭제
  • 최적화
    • generational collection: 오래 남은 것이 지속적으로 사용된 것
    • incremental collection: 작업량 줄이기 위해 GC를 여러 부분으로 분리 후 각 부분 별도 수행
    • idle-time collection: cpu가 idle 일 때 수집

힙 구조

  • V8
    • new-space: 대부분의 객체. 작고 GC 되기 쉽게 설계, 다른 공간과 독립적
    • old-pointer-space: 다른 객체에 대한 포인터를 가질 수 있는 대부분의 객체. new-space에서 살아남으면 옮겨짐
    • old-data-space: 원시 값. 다른 객체에 대한 포인터 없음. new-space에서 살아남으면 옮겨짐
    • large-object-space: 다른 공간의 크기를 넘어선 객체 포함. 옮겨지지 않음 (오버헤드 크기 때문에)
    • code-sapce: JIT 컴파일된 코드 객체. 유일한 실행 가능한 메모리(large-object-space에도 코드가 할당되고 실행될 수 있다.)
    • cell-space, property-cell-space, map-space: Cell, PropertyCell, Map 모두 같은 크기의 오브젝트이고 가리키는 객체에 제약이 있어 collection 단순화
  • 각 공간은 페이지로 구성 (large-object-space 제외 1MB)
  • 페이지는 객체를 저장하기 위해 header(flags, meta-data), marking bitmap(위에서 다룬 mark 객체 표현) 포함
    • slot buffer: 각 페이지에 할당되어 저장된 개체를 가리키는 리스트 만들 때 사용

포인터 식별

  • GC는 포인터와 데이터를 구별할 수 있어야 함
    • Conservative: word 들을 포인터라고 가정.
      • 데이터를 포인터로 잘못 분류할 수 있음
      • 메모리 압축과 객체 이동을 제한
        • 포인터라고 생각하고 데이터를 변경할 수 있기 때문
    • Compiler hints: 정적 언어는 컴파일러가 정보를 제공할 수 있음
      • 각 클래스 내의 포인터를 찾음
      • js는 동적이라 불가능 (모든 필드에 포인터나 데이터가 포함될 수 있음)
    • Tagged pointers: word에 tag bit를 추가하여 포인터인지 데이터 인지 명시적으로 표현
  • V8은 Tagged pointers 사용
    • 정수는 32비트 워드와 낮은 비트가 0으로 설정
    • 포인터는 1

Generational collection

  • 대부분의 객체는 수명이 짧고 적은 수의 오브젝트많이 수명이 길다.
  • new space: 공간이 다 차면 scavenge라 불리는 빠른 GC 사이클이 객체 정리, 할당 비용이 작음, (1MB ~ 8MB)
    • to-space from-space로 한 번 더 나눔
    • to가 가들 차면 from과 교체
    • 모든 객체는 from space에 있음
    • 살아있는 객체는 to-space로 옮기거나 old로 승격

Write Barrier

  • old-space 포인터 -> new-space 객체
    • new-space 객체를 지우기 위해 old-space 를 검색하는 것은 비효율적
  • write barrier는 store 후 실행되는 작은 코드
  • write barrier가 포인터를 기록하여 new-space가 collected 될 때 사용
    • 비용이 크지만 GC가 live 객체를 식별하는 데 필요
    • 최적화 기법
      • 정적으로 new-space에 있는 것을 증명할 수 있다면 (write barrier 생략)
      • 로컬 참조가 아닌 참조가 없을 때 스택에 객체 할당 (write barrier 생략)
      • 페이지 헤더에 어느 space에 있는지 체크

GC Pauses

  • incremental marking
    • 힙이 5~10ms의 작은 pause로 보이게 함
    • 힙이 임계값에 도달하면 활성화
    • 활성화되면
  • lazy sweeping
    • 도달 가능한 객체를 파악 후 바로 collect 하지 않고 페이지가 교체될 때 collect

참조
https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection

profile
어떻게 살아야 하나

0개의 댓글