Interview
문자열
유니코드
- 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다.
- 인코딩을 통해 문자나 기호를 컴퓨터가 이해할 수 있는 신호로 만들고, 이 신호를 해석하는 디코딩 과정을 통해 처리된다.
ASCLL
- 영문 알파벳을 사용하는 대표적인 문자 인코딩으로 7 비트로 모든 영어 알파벳을 표현할 수 있다.
- 52개의 영문 알파벳 대소문자와, 10개의 숫자, 32개의 특수 문자, 그리고 하나의 공백 문자를 포함한다.
UTF-8과 UTF-16의 차이점
- UTF-8과 UTF-16은 인코딩 방식의 차이를 의미합니다. UTF-8은 Universal Coded Character Set + Transformation Format – 8-bit의 약자로, UTF- 뒤에 등장하는 숫자는 비트(bit)이다.
UTF-8 특징: 가변 길이 인코딩
- UTF-8은 유니코드 한 문자를 나타내기 위해 1 byte(= 8 bits)에서 4 bytes까지 사용한다.
예를 들어, 코 라는 문자의 유니코드는 U+CF54 (16진수, HEX)로 표현됩니다. 이 문자를 이진법(binary number)으로 표시하면, 1100-1111-0101-0100 이 됩니다. 이 문자를 UTF-8로 표현하면, 다음과 같이 3byte의 결과로 표현된다.
1 2 3 4 5 6 7 8
1110xxxx 10xxxxxx 10xxxxxx // # x 안에 순서대로 값을 채워넣습니다. 11101100 10111101 10010100 let encoder = new TextEncoder(); // 기본 인코딩은 'utf-8' encoder.encode('코') // Uint8Array(3) [236, 189, 148] (236).toString(2) // "11101100" (189).toString(2) // "10111101" (148).toString(2) // "10010100"
- ASCII 코드는 7비트로 표현되고, UTF-8에서는 다음과 같이 1 byte의 결과로 만들 수 있다.
1 2 3 4
0xxxxxxx 01100010 encoder.encode('b') // Uint8Array [98] (98).toString(2) // "1100010"
UTF-8 특징 : 바이트 순서 고정
- UTF-16에 비해 바이트 순서를 따지지 않고, 순서가 정해져 있다.
UTF-16 특징 : 코드 그대로 바이트로 표현 가능, 바이트 순서의 다양성
- UTF-16은 유니코드 코드 대부분(U+0000부터 U+FFFF; BMP) 을 16 bits로 표현한다.
- 대부분에 속하지 않는 기타 문자는 32 bit(4 bytes)로 표현하므로 UTF-16도 가변 길이라고 할 수 있으나, 대부분은 2 바이트로 표현한다.
- U+ABCD라는 16진수를 있는 그대로 이진법으로 변환하면 1010-1011-1100-1101 이다. 이 이진법으로 표현된 문자를 16 bits(2 bytes)로 그대로 사용하며, 바이트 순서(엔디언)에 따라 UTF-16의 종류도 달라진다.
- UTF-8에서는 한글은 3 바이트, UTF-16에서는 2 바이트를 차지한다.
가비지 컬렉션
- 가비지 컬렉션은 프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것이다.
- 이 기능을 가진 언어(혹은 엔진)는 자바, C#, 자바스크립트 등이 있다.
- C 언어 같은 저수준 언어에서는 메모리 관리를 위해 malloc()과 free()를 사용해 개발자가 스스로 메모리를 할당하고 해제해야 한다.
- 그러나 JavaScript는 C언어와는 반대로 고수준 언어로서, 객체가 생성되었을 때 자동으로 메모리를 할당하고 필요하지 않다면 자동으로 해제하는 가비지 컬렉션이 내장되어 있다.
메모리 생존주기
- 필요할 때 개발자가 할당한다.
- 할당된 메모리를 사용한다.
- 메모리가 더이상 필요하지 않으면 해제한다.
메모리 할당
- JavaScript는 프로그래머 대신, 값을 선언할 때 자동으로 메모리를 할당해준다.
- 변수를 선언하고 배열을 할당하여 안에 요소를 집어넣을 때, 그 배열을 담을 메모리의 크기를 고려하지 않는데, 이 부분을 JavaScript가 배열과 배열에 담긴 값들을 위한 메모리 크기 할당을 알아서 진행했기 때문이다.
- 정수, 문자열, 함수, 객체 모든 부분에서 자동적으로 일어난다.
할당된 메모리 사용
- 기본적으로 할당된 메모리를 읽고 쓰는 것을 의미한다.
- 변수나 객체 속성의 값을 읽고 쓰거나, 함수 호출 시에 함수에 인수를 전달하여 수행하는 방식으로 일어난다.
메모리 해제
- 할당된 메모리가 더이상 필요 없다면 해제를 해야 앱의 성능을 저하시키지 않는다.
- 저수준 언어는 개발자가 직접 결정하고 해제하는 방식을 사용하며, 개발자가 직접 관여하기 때문에 개발자의 제어 정도가 굉장히 높은 편이다.
- 가비지 컬렉션의 목적은 메모리 할당을 추적하고, 할당된 메모리 블록이 더이상 필요하지 않게 되었는지를 “스스로” 판단하여 필요하지 않다고 판단이 된다면 해당 메모리를 해제한다.
대표적인 가비지 컬렉션의 방법
- 참조(reference)
- 명시적이든, 암묵적이든 관계없이 메모리 관리 관점에서 어떤 객체가 다른 객체에 접근할 수 있다면 다른 객체를 참조한다고 말한다.
- avaScript 객체는 자신의 프로토타입(prototype)에 대해 암묵적인 참조를 갖고 있고, 자신의 속성(property) 값에 대한 명시적 참조도 가지고 있다.
- 렉시컬 스코핑(lexical scoping)
- 변수 이름이 중첩된 함수에서 해석되는 방식을 정의하는 것으로, 중첩되어 있는 더 안쪽의 함수는 부모 함수가 값을 반환한 다음에도 부모 함수의 스코프를 포함하고 있다.
레퍼런스 카운팅(참조 횟수 계산)
- 한 객체를 참조하는 변수의 수를 추적하는 방법으로 가장 단순한 형태의 가비지 컬렉션 알고리즘이다.
- 객체를 참조하는 변수는 처음에는 특정 메모리에 대해 레퍼런스가 하나뿐이지만, 변수의 레퍼런스가 복사될 때마다 레퍼런스 카운트가 늘어난다.
- 객체를 참조하고 있던 변수의 값이 바뀌거나, 변수 스코프를 벗어나면 레퍼런스 카운트는 줄어든다.
- 레퍼런스 카운트가 0이 되면, 그 객체와 관련한 메모리는 비울 수 있다.
레퍼런스 카운트가 0이 된다는 말은 아무도 그 객체에 대한 레퍼런스를 가지고 있지 않다는 말과 같다.
1 2 3 4 5 6 7 8
function reference() { var obj1 = {}; var obj2 = {}; obj1.p = obj2; obj2.p = obj1; } reference();
- 두 객체가 생성되고 서로를 참조하고 있는 형태이기 때문에 순환 참조가 발생한다.
- 레퍼런스 카운팅 알고리즘에서는 두 객체가 적어도 한 번은 참조한 것으로 간주되기 때문에 둘 다 가비지컬렉션이 될 수 없게 된다.
트레이싱
- 한 객체에 flag를 두고, 가비지 컬렉션 사이클마다 flag에 표시 후 삭제하는 mark and sweep 방법이다.
- 객체에 in-use flag를 두고, 사이클마다 메모리 관리자가 모든 객체를 추적해서 사용 중인지 아닌지를 표시(mark)한다.
- 표시되지 않은 객체를 삭제(sweep)하는 단계를 통해 메모리를 해제한다.
- 객체가 필요한지 결정하기 위해 해당 객체에 닿을 수 있는지 (reachable)을 판단하며, 3단계를 거친다.
- 루트
- 일반적으로 루트는 코드에서 참조되는 전역 변수이다.
- 자바스크립트에서 루트로 동작할 수 있는 전역 변수는 window 객체이다.
- 가비지컬렉터는 모든 루트의 완전한 목록을 만들어낸다.
- 그런 다음 모든 루트와 그 자식들을 검사해서 활성화 여부를 표시한다.
- 루트가 닿을 수 없는 것들은 가비지로 표시된다.
- 마지막으로 가비지컬렉터는 활성으로 표시되지 않은 모든 메모리를 OS에 반환한다.
- 루트
메모리 누수
- 예상치 못한 참조는 개발자는 더 이상 사용되지 않을 것이라 생각했지만, 어떠한 이유로 활성화 상태인 루트 트리 안에 존재하는 메모리 조각들이다.
- 더이상 사용되지 않지만 코드 상 어딘가에 유지되어 해제되지 못한 변수들이다.
- 우발적으로 생성된 전역변수
- DOM 외부에서의 참조
- 클로저의 잘못된 사용