가비지 수집은 JavaScript
의 숨겨진 메커니즘입니다. 일반적으로 가비지 수집에 대해 걱정할 필요가 없으며 기능 개발에만 집중하면 됩니다. 그러나 이것이 JavaScript
작성할 때 편안하게 앉아 있을 수 있다는 것을 의미하지는 않습니다. 우리가 구현하는 기능이 점점 더 복잡해지고 코드의 양이 누적될수록 성능 문제는 점점 더 두드러집니다. 더 빠르게 실행되고 더 적은 메모리를 차지하는 코드를 작성하는 방법은 프로그래머의 끊임없는 추구입니다. 훌륭한 프로그래머는 극히 제한된 자원으로 항상 놀라운 결과를 얻을 수 있습니다. 이것이 평범한 존재와 냉담한 신의 차이이기도 합니다.
? 컴퓨터 메모리에서 실행될 때 코드에서 정의한 모든 변수, 객체, 함수는 메모리에서 일정량의 메모리 공간을 차지합니다. 컴퓨터에서 메모리 공간은 매우 부족한 자원입니다. 결국, 메모리 모듈은 매우 비쌉니다. 변수, 함수 또는 객체가 생성 후 후속 코드 실행에 더 이상 필요하지 않은 경우 쓰레기라고 할 수 있습니다.
가비지의 정의를 직관적으로 이해하는 것은 매우 쉽지만, 컴퓨터 프로그램의 경우 현재 존재하는 변수, 함수 또는 객체가 미래에는 더 이상 사용되지 않을 것이라고 특정 순간에 결론을 내리기는 어렵습니다. 컴퓨터 메모리 비용을 줄이고 컴퓨터 프로그램의 정상적인 실행을 보장하기 위해 우리는 일반적으로 다음 조건 중 하나를 충족하는 개체나 변수를 가비지라고 규정합니다.
참조되지 않은 변수나 개체는 문이 없는 집과 동일하므로 사용할 수 없습니다. 접근할 수 없는 객체는 연결되어 있어도 여전히 외부에서 접근할 수 없으므로 다시 사용할 수 없습니다. 위의 조건을 만족하는 객체나 변수는 향후 프로그램 실행 시 다시는 사용되지 않으므로 안전하게 가비지 컬렉션으로 처리될 수 있습니다.
위의 정의를 통해 버려야 할 객체를 명확히 하면, 남은 변수와 객체에 쓰레기가 없다는 뜻인가요?
아니요! 현재 우리가 파악한 쓰레기는 전체 쓰레기 중 일부일 뿐이며, 위의 조건을 충족하지 않는 다른 쓰레기도 여전히 존재하지만, 다시는 사용되지 않습니다.
위의 정의에 맞는 쓰레기를 '절대 쓰레기'라고 하고, 프로그램에 숨어 있는 다른 쓰레기를 '상대 쓰레기'라고 할 수 있을까요?
가비지 수집 메커니즘( GC,Garbage Collection
)은 프로그램 실행 중에 쓸모없는 변수와 점유된 메모리 공간을 재활용하는 역할을 합니다. 객체가 다시 사용될 가능성이 없음에도 불구하고 여전히 메모리에 존재하는 현상을 메모리 누수 라고 합니다. 메모리 누수는 특히 장기 실행 프로그램에서 매우 위험한 현상입니다. 프로그램에 메모리 누수가 발생하면 메모리가 부족해질 때까지 점점 더 많은 메모리 공간을 차지하게 됩니다.
문자열, 개체 및 배열은 고정된 크기를 가지지 않으므로 해당 크기를 알고 있는 경우에만 동적 저장소 할당을 수행할 수 있습니다. JavaScript 프로그램이 문자열, 배열 또는 객체를 생성할 때마다 인터프리터는 엔터티를 저장하기 위해 메모리를 할당합니다. 이와 같이 메모리가 동적으로 할당될 때마다 다시 사용할 수 있도록 메모리를 해제해야 합니다. 그렇지 않으면 JavaScript 인터프리터가 시스템에서 사용 가능한 모든 메모리를 소비하여 시스템이 충돌하게 됩니다.
JavaScript
의 가비지 수집 메커니즘은 쓸모없는 변수와 개체(가비지)를 간헐적으로 확인하고 이들이 차지하는 공간을 해제합니다.
다양한 프로그래밍 언어는 서로 다른 가비지 수집 전략을 채택합니다. 예를 들어 C++
에는 가비지 수집 메커니즘이 없습니다. 모든 메모리 관리는 프로그래머 자신의 기술에 의존하므로 C++
익히기가 더 어렵습니다. JavaScript
도달 가능성을 사용하여 메모리를 관리합니다. 말 그대로 도달 가능성은 프로그램이 이러한 변수가 차지하는 메모리에 액세스하고 사용할 수 있음을 의미합니다.
JavaScript
도달 가능한 값의 고유한 집합을 지정하며, 집합의 값은 본질적으로 도달 가능합니다.
위의 변수는 연결 가능성 트리의 최상위 노드 입니다.
변수 또는 객체는 루트 변수에서 직접 또는 간접적으로 사용되는 경우 도달 가능한 것으로 간주됩니다.
즉, 루트를 통해 도달할 수 있는 값(예: Abcde
)에 도달할 수 있습니다.
let people = { 소년들:{ boys1:{이름:'샤오밍'}, boys2:{이름:'xiaojun'}, }, 여자:{ girls1:{이름:'xiaohong'}, girls2:{이름:'화와'}, }};
위의 코드는 객체를 생성하고 이를 people
변수에 할당합니다. people
변수에는 boys
와 girls
두 개의 객체가 포함되어 있고, boys
와 girls
에는 각각 두 개의 하위 객체가 포함되어 있습니다. 이는 또한 아래와 같이 3
수준의 참조 관계(기본 유형 데이터에 관계없이)를 포함하는 데이터 구조를 생성합니다.
그 중 people
노드는 전역변수이기 때문에 자연스럽게 접근이 가능하다. boys
및 girls
노드는 전역 변수에 의해 직접 참조되기 때문에 간접적으로 접근 가능합니다. boys1
, boys2
, girls1
및 girls2
도 전역 변수에 의해 간접적으로 사용되며 people.boys.boys
통해 액세스할 수 있으므로 도달 가능한 변수입니다.
위 코드 뒤에 다음 코드를 추가하면
people.girls.girls2
= people.girls.girls1 = people.boys.boys2;
그중 girls1
및 girls2
grils
노드와의 연결이 끊어져 도달할 수 없는 노드가 되었습니다. 이는 가비지 수집 메커니즘에 의해 재활용된다는 의미입니다.
그리고 이때 다음 코드를 실행하면
people.boys.boys2 = null;
참조 계층 다이어그램은 다음과 같습니다.
이때 boys
노드와 boys2
노드의 연결이 끊어지더라도 boys2
노드와 girls
노드 사이의 참조 관계로 인해 boys2
여전히 연결 가능하며 가비지 수집 메커니즘에 의해 재활용되지 않습니다.
위의 연관 다이어그램은 전역 변수 등가 값이 루트 라고 불리는 이유를 증명합니다. 연관 다이어그램에서 이러한 유형의 값은 일반적으로 관계 트리의 루트 노드로 나타나기 때문입니다.
사람들 = { 소년들:{ boys1:{이름:'샤오밍'}, boys2:{이름:'xiaojun'}, }, 여자:{ girls1:{이름:'xiaohong'}, girls2:{이름:'화와'}, }};people.boys.boys2.friend = people.girls.girls1; //boys2는 girls1people.girls.girls1.boyfriend = people.boys.boys2; //girls1은 boys2를 참조합니다.
위의 코드는 boys2
와 girls1
간의 상호 연관된 관계를 생성합니다.
이 시점에서 boys
와 boys2
사이의 연관을 끊으면,
people.boys.boys2를 삭제하면
객체 사이의 연관 다이어그램은 다음과 같습니다.
당연히 도달할 수 없는 노드는 없습니다.
이 시점에서 boyfriend
관계 연결을 끊으면
people.girls.girls1을 삭제하면
관계 다이어그램은 다음과 같습니다.
이때 boys2
와 girls1
사이에는 여전히 girlfriend
관계가 있지만 boys2
도달할 수 없는 노드가 되어 가비지 수집 메커니즘에 의해 회수됩니다.
사람들을 놓아두세요 = { 소년들:{ boys1:{이름:'샤오밍'}, boys2:{이름:'xiaojun'}, }, 여자:{ girls1:{이름:'xiaohong'}, girls2:{이름:'화와'}, }};delete people.boys;delete people.girls;
위 코드로 구성된 참조 계층 다이어그램은 다음과 같습니다.
현재 점선 상자 안의 개체 간에는 여전히 상호 참조 관계가 있지만 이러한 개체도 도달할 수 없으며 가비지 수집 메커니즘에 의해 삭제됩니다. 이러한 노드는 루트 와의 관계가 끊어져 연결할 수 없게 됩니다.
Reference Counting) 이름에서 알 수 있듯이 참조를 추가하면 1씩 증가하고, 참조를 삭제하면 1씩 감소합니다. 숫자가 0이 되면 쓰레기로 간주되어 객체를 삭제하여 메모리를 회수합니다.
예:
let user = {사용자 이름:'xiaoming'}; //객체는 사용자 변수 count +1에 의해 참조됩니다. user2 = 사용자라고 둡니다. //객체는 새 변수에 의해 참조되며 개수는 +1입니다. 사용자 = null; //변수는 더 이상 객체를 참조하지 않으며 개수는 -1입니다. 사용자2 = 널; //변수가 더 이상 개체를 참조하지 않습니다. 홀수 -1 //이때 객체 참조 개수는 0개이므로 삭제됩니다.
참조 카운팅 방법은 매우 합리적으로 보이지만 실제로 참조 카운팅 방법을 사용하는 메모리 재활용 메커니즘에는 명백한 허점이 있습니다.
예:
let boy = {}; 소녀 = {}를 보자; 소년.여자친구 = 소녀; 여자.남자친구 = 남자아이; 소년 = null; girl = null;
위의 코드에는 boy
와 girl
사이의 상호 참조가 있습니다. 계산하면 boy
와 girl
의 참조가 삭제되며 두 개체는 재활용되지 않습니다. 순환 참조가 있기 때문에 두 익명 개체의 참조 횟수는 결코 0으로 돌아가지 않아 메모리 누수가 발생합니다.
C++
에는 스마트 포인터 ( shared_ptr
)라는 개념이 있습니다. 프로그래머는 스마트 포인터를 사용하여 개체 소멸자를 사용하여 참조 카운트를 해제할 수 있습니다. 그러나 순환 참조의 경우 메모리 누수가 발생합니다.
다행스럽게도 JavaScript
메모리 누수 위험을 더 많이 방지하는 또 다른 안전한 전략을 채택했습니다.
마크 mark and sweep
(Mark and Sweep)은 JavaScript
엔진이 채택한 가비지 수집 알고리즘입니다. 기본 원칙은 루트 에서 시작하여 변수 간의 참조 관계를 너비 우선으로 순회하고 순회된 변수에 표시(优秀员工徽章
)를 표시하는 것입니다. . 표시되지 않은 개체는 최종적으로 삭제됩니다.
알고리즘의 기본 프로세스는 다음과 같습니다.
2
신규 우수직원이 없을 때까지예:
아래와 같이 프로그램에 객체 참조 관계가 있는 경우:
전체 그림의 오른쪽에는 루트 부터 시작하여 결코 도달할 수 없는 섬이 있음을 분명히 알 수 있습니다. 하지만 가비지 컬렉터는 우리와 같은 신의 관점을 갖고 있지 않습니다. 그들은 단지 알고리즘에 따라 루트 노드를 뛰어난 직원으로 표시할 뿐입니다.
그런 다음 우수 직원부터 시작하여 위 그림의 점선 상자 안의 3개 노드와 같이 우수 직원이 인용한 모든 노드를 찾습니다. 그런 다음 새로 발견된 노드를 뛰어난 직원으로 표시합니다.
발견된 모든 노드가 성공적으로 표시될 때까지 찾기 및 표시 프로세스가 반복됩니다.
마지막으로 아래 그림과 같은 효과가 달성됩니다.
오른쪽에 있는 섬은 알고리즘 실행 주기가 끝난 후에도 여전히 표시가 해제되어 있으므로 이러한 노드는 가비지 수집기 작업에서 도달할 수 없으며 결국에는 지워집니다.
데이터 구조와 알고리즘을 공부한 어린이는 이것이 연결된 그래프 알고리즘과 유사한 그래프 순회라는 사실에 놀랄 수도 있습니다.
가비지 수집은 대규모 작업입니다. 특히 코드 양이 매우 많은 경우 가비지 수집 알고리즘을 자주 실행하면 프로그램 실행이 크게 저하됩니다. JavaScript
알고리즘은 재활용 작업의 정상적인 실행을 보장하면서 프로그램이 효율적으로 실행될 수 있도록 가비지 수집을 많이 최적화했습니다.
성능 최적화를 위해 채택된 전략에는 일반적으로 다음 사항이 포함됩니다.
JavaScript
프로그램은 실행 중에 상당한 수의 변수를 유지하며 이러한 변수를 자주 검색하면 상당한 오버헤드가 발생합니다. 그러나 이러한 변수들은 생명주기에 있어서 고유한 특성을 가지고 있는데, 예를 들어 지역 변수는 자주 생성되어 빠르게 사용되었다가 폐기되는 반면, 전역 변수는 오랫동안 메모리를 차지합니다. JavaScript
두 가지 유형의 개체를 별도로 관리합니다. 빠르게 생성, 사용 및 삭제되는 로컬 변수의 경우 가비지 수집기는 이러한 변수가 사용되지 않은 후 신속하게 정리되도록 자주 검색합니다. 장기간 메모리를 보유하는 변수의 경우 확인 빈도를 줄여 일정량의 오버헤드를 절약할 수 있습니다.
증분 아이디어는 성능 최적화에서 매우 일반적이며 가비지 수집에도 사용될 수 있습니다. 변수의 수가 매우 많으면 모든 변수를 한 번에 순회하고 뛰어난 사원 표시를 발행하는 데 시간이 많이 걸리므로 프로그램 실행 중에 지연이 발생합니다. 따라서 엔진은 가비지 수집 작업을 여러 하위 작업으로 나누고 프로그램 실행 중에 각각의 작은 작업을 점진적으로 실행합니다. 이로 인해 특정 복구 지연이 발생하지만 일반적으로 명백한 프로그램 지연이 발생하지 않습니다.
CPU
복잡한 프로그램에서도 항상 작동하지 않습니다. 이는 주로 CPU
매우 빠르게 작동하고 주변 IO
가 몇 배 더 느린 경우가 많기 때문입니다. 따라서 CPU
작동 중일 때 가비지 수집 전략을 마련하는 것이 좋습니다. 유휴 이것은 매우 효과적인 성능 최적화 방법이며 기본적으로 프로그램 자체에 부정적인 영향을 미치지 않습니다. 이 전략은 시스템의 유휴 시간 업그레이드와 유사하며 사용자는 백그라운드 실행을 전혀 인식하지 못합니다.
이 글의 주요 임무는 단순히 가비지 수집 메커니즘, 일반적으로 사용되는 전략 및 최적화 방법을 종료하는 것입니다. 이는 모든 사람에게 엔진의 백그라운드 실행 원리에 대한 심층적인 이해를 제공하기 위한 것이 아닙니다.
이 기사를 통해 다음 사항을 이해해야 합니다.
JavaScript
의 기능 중 하나입니다.