이 기사는 가비지 수집과 관련된 문제를 주로 소개하는 javascript에 대한 관련 지식을 제공합니다. 가비지 수집은 JavaScript의 숨겨진 메커니즘에 대해 모두에게 도움이 되기를 바랍니다.
[관련 권장 사항: javascript 비디오 튜토리얼, web front-end]
가비지 수집은 JavaScript
의 숨겨진 메커니즘입니다. 가비지 수집에 신경을 써야 합니다. 기능 개발에만 집중하세요. 그러나 이것이 JavaScript
를 작성할 때 편안하게 앉아 있을 수 있다는 의미는 아닙니다. 우리가 구현하는 기능이 점점 더 복잡해지고 코드의 양이 쌓일수록 성능 문제는 더욱 심각해집니다. 더 빠르게 실행되고 더 적은 메모리를 차지하는 코드를 작성하는 방법은 프로그래머의 끊임없는 추구입니다. 훌륭한 프로그래머는 극히 제한된 자원으로 항상 놀라운 결과를 얻을 수 있습니다. 이것이 평범한 존재와 냉담한 신의 차이이기도 합니다. JavaScript
的隐藏机制,我们通常无需为垃圾回收劳心费力,只需要专注功能的开发就好了。但是这并不意味着我们在编写JavaScript
的时候就可以高枕无忧了,伴随着我们实现的功能越来越复杂,代码量越积越大,性能问题就变的越来越突出。如何写出执行速度更快,而且占用内存更小的代码是程序员永无止歇的追求。一个优秀的程序员总是能在极其有限的资源下,实现惊人的效果,这也正式芸芸众生和高高在上的神祗之间的区别。
代码执行在计算机的内存中,我们在代码中定义的所有变量、对象、函数都会在内存中占用一定的内存空间。在计算机中,内存空间是非常紧张的资源,我们必须时时刻刻注意内存的占用量,毕竟内存条非常贵!如果一个变量、函数或者对象在创建之后不再被后继的代码执行所需要,那么它就可以被称作垃圾。
虽然从直观上理解垃圾的定义非常容易,但是对于一个计算机程序来说,我们很难在某一时刻断定当前存在的变量、函数或者对象在未来不再使用。为了降低计算机内存的开销,同时又保证计算机程序正常执行,我们通常规定满足以下任一条件的对象或者变量为垃圾:
没有被引用的变量或者对象相当于一座没有门的房子,我们永远都无法进入其中,因此不可能在用到它们。无法访问到的对象之间虽然具备连通性,但是仍然无法从外部进入其中,因此也无法再次被利用。满足以上条件的对象或者变量,在程序未来执行过程中绝对不会再次被采用,因此可以放心的当作垃圾回收。
当我们通过以上定义明确了需要丢弃的对象,是否就意味着剩余的变量、对象中就没有垃圾了呢?
不是的!我们当前分辨出的垃圾只是所有垃圾的一部分,仍然会有其他垃圾不满足以上条件,但是也不会再次被使用了。
这是否可以说满足以上定义的垃圾是“绝对垃圾”,其他隐藏在程序中的为“相对垃圾”呢?
垃圾回收机制(GC,Garbage Collection
)负责在程序执行过程中回收无用的变量和内存占用的空间。一个对象虽然没有再次使用的可能,但是仍然存在于内存中的现象被称为内存泄漏。内存泄漏是非常危险的现象,尤其在长时间运行的程序中。如果一个程序出现了内存泄漏,它占用的内存空间就会越来越多,直至耗尽内存。
字符串、对象和数组没有固定的大小,所以只有当它们大小已知时才能对它们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都要分配内存才存储这个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便它们能够被再次利用;否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
JavaScript
的垃圾回收机制会间歇性的检查没有用途的变量、对象(垃圾),并释放条它们占用的空间。
不同的编程语言采用不同的垃圾回收策略,例如C++
就没有垃圾回收机制,所有的内存管理靠程序员本身的技能,这也就造成了C++
比较难以掌握的现状。JavaScript
采用可达性管理内存,从字面意思上看,可达的意思是可以到达,也就是指程序可以通过某种方式访问、使用的变量和对象,这些变量所占用的内存是不可以被释放的。
JavaScript
🎜위의 정의를 통해 버려야 할 객체를 명확히 하면, 남은 변수와 객체에 쓰레기가 없다는 뜻인가요? 🎜🎜안돼! 현재 우리가 파악한 쓰레기는 전체 쓰레기 중 일부일 뿐이며, 위의 조건을 충족하지 않는 다른 쓰레기도 여전히 존재하지만, 다시는 사용되지 않습니다. 🎜🎜위의 정의에 맞는 쓰레기를 "절대 쓰레기"라고 하고, 프로그램에 숨겨진 다른 쓰레기를 "상대 쓰레기"라고 말할 수 있나요? 🎜🎜3. 가비지 컬렉션🎜🎜 가비지 컬렉션 메커니즘(
GC, Garbage Collection
)은 프로그램 실행 중에 사용되는 불필요한 변수와 메모리 공간을 재활용하는 역할을 합니다. 객체가 다시 사용될 가능성이 없음에도 불구하고 여전히 메모리에 존재하는 현상을 메모리 누수라고 합니다. 메모리 누수는 특히 장기 실행 프로그램에서 매우 위험한 현상입니다. 프로그램에 메모리 누수가 발생하면 메모리가 부족해질 때까지 점점 더 많은 메모리 공간을 차지하게 됩니다. 🎜🎜문자열, 개체 및 배열에는 고정된 크기가 없으므로 해당 크기를 알고 있는 경우에만 동적 저장소 할당을 수행할 수 있습니다. JavaScript 프로그램이 문자열, 배열 또는 객체를 생성할 때마다 인터프리터는 엔터티를 저장하기 위해 메모리를 할당합니다. 이와 같이 메모리가 동적으로 할당될 때마다 다시 사용할 수 있도록 메모리를 해제해야 합니다. 그렇지 않으면 JavaScript 인터프리터가 시스템에서 사용 가능한 모든 메모리를 소비하여 시스템이 충돌하게 됩니다. 🎜🎜JavaScript
의 가비지 수집 메커니즘은 쓸모없는 변수와 개체(가비지)를 간헐적으로 확인하고 이들이 차지하는 공간을 해제합니다. 🎜🎜4. 도달성🎜🎜다른 프로그래밍 언어는 다른 가비지 수집 전략을 채택합니다. 예를 들어 C++
에는 가비지 수집 메커니즘이 없습니다. C++
는 상대적으로 마스터하기 어려운 상황이 되었습니다. JavaScript
는 접근성을 사용하여 메모리를 관리합니다. 말 그대로 도달가능성이란 프로그램이 어떤 방식으로든 변수와 객체에 액세스하고 사용할 수 있다는 의미입니다. 이러한 변수가 차지하는 메모리는 해제될 수 없습니다. 🎜🎜JavaScript
는 고유한 도달 가능한 값 집합을 지정하며, 집합의 값은 본질적으로 도달 가능합니다. 🎜라고 합니다. 변수나 개체가 루트 변수에 의해 직접 또는 간접적으로 적용되는 경우 해당 변수는 도달 가능한 것으로 간주됩니다.
즉, 루트를 통해 값에 액세스할 수 있는 경우(예: A.b.c.d.e
) 이 값에 도달할 수 있습니다.
5. 도달 가능성의 예A.b.c.d.e
),那么这个值就是可达的。
let people = { boys:{ boys1:{name:'xiaoming'}, boys2:{name:'xiaojun'}, }, girls:{ girls1:{name:'xiaohong'}, girls2:{name:'huahua'}, }};
以上代码创建了一个对象,并赋值给了变量people
,变量people
中包含了两个对象boys
和girls
,boys
和girls
中又分别包含了两个子对象。这也就创建了一个包含了3
层引用关系的数据结构(不考虑基础类型数据的情况下),如下图:
其中,people
节点由于是全局变量,所以天然可达。boys
和girls
节点由于被全局变量直接引用,构成间接可达。boys1
、boys2
、girls1
和girls2
由于被全局变量间接应用,可以通过people.boys.boys
访问,因此也属于可达变量。
如果我们在以上代码的后面加上以下代码:
people.girls.girls2 = null;people.girls.girls1 = people.boys.boys2;
那么,以上引用层次图将会变成如下形式:
其中,girls1
和girls2
由于和grils
节点断开连接,从而变成了不可达节点,意味着将被垃圾回收机制回收。
而如果此时,我们再执行以下代码:
people.boys.boys2 = null;
那么引用层次图将变成如下形式:
此时,虽然boys
节点和boys2
节点断开了连接,但是由于boys2
节点和girls
节点之间存在引用关系,所以boys2
仍然属于可达的,不会被垃圾回收机制回收。
以上关联关系图证明了为何称全局变量等值为根,因为在关联关系图中,这一类值通常作为关系树的根节点出现。
let people = { boys:{ boys1:{name:'xiaoming'}, boys2:{name:'xiaojun'}, }, girls:{ girls1:{name:'xiaohong'}, girls2:{name:'huahua'}, }};people.boys.boys2.girlfriend = people.girls.girls1; //boys2引用girls1people.girls.girls1.boyfriend = people.boys.boys2; //girls1引用boys2
以上代码在boys2
和girls1
之间创建了一个相互关联的关系,关系结构图如下:
此时,如果我们切断boys
和boys2
之间的关联:
delete people.boys.boys2;
对象之间的关联关系图如下:
显然,并没有不可达的节点出现。
此时,如果我们切断boyfriend
关系连接:
delete people.girls.girls1;
关系图变为:
此时,虽然boys2
和girls1
之间还存在girlfriend
关系,但是,boys2
let people = { boys:{ boys1:{name:'xiaoming'}, boys2:{name:'xiaojun'}, }, girls:{ girls1:{name:'xiaohong'}, girls2:{name:'huahua'}, }};delete people.boys;delete people.girls;
boys
두 개체를 포함하는 변수 people
에 할당합니다. 및 girls
가 생성되고 boys
와 girls
에는 각각 두 개의 하위 개체가 포함됩니다. 이는 또한 아래와 같이 3
레이어 참조 관계(기본 유형 데이터를 고려하지 않고)를 포함하는 데이터 구조를 생성합니다.
그 중 people
노드는 전역 변수이기 때문에 자연스럽게 접근 가능합니다. . boys
및 girls
노드는 전역 변수에 의해 직접 참조되므로 간접적으로 접근할 수 있습니다. boys1
, boys2
, girls1
및 girls2
는 전역 변수에 의해 간접적으로 사용되며 people을 통해 전달될 수 있습니다. .boys .boys
액세스이므로 접근 가능한 변수이기도 합니다.
위 코드 뒤에 다음 코드를 추가하면: let user = {username:'xiaoming'};
//对象被user变量引用,计数+1
let user2 = user;
//对象被新的变量引用,计数+1
user = null;
//变量不再引用对象,计数-1
user2 = null;
//变量不再引用对象,奇数-1
//此时,对象引用数为0,会被删除
girls1
과 girls2
는 grils 노드는 연결이 끊어지고 연결할 수 없는 노드가 됩니다. 이는 가비지 수집 메커니즘에 의해 재활용된다는 의미입니다. <strong></strong>그리고 이때 다음 코드를 실행하면:
let boy = {}; let girl = {}; boy.girlfriend = girl; girl.boyfriend = boy; boy = null; girl = null;
그러면 참조 계층 다이어그램은 다음과 같은 형태가 됩니다:
이때 boys
노드와 boys2
노드는 그러나 boys2
노드와 girls
노드 사이의 참조 관계로 인해 boys2
는 여전히 연결 가능하며 재활용되지 않습니다. 가비지 수집 메커니즘.
🎜상호 관련: 🎜rrreee🎜위 코드는위 연관 다이어그램은 전역 변수의 등가 값이
root라고 불리는 이유를 증명합니다. 연관 다이어그램에서 이러한 유형의 값은 일반적으로 관계 트리의 루트 노드로 나타나기 때문입니다.
boys2
와 girls1
사이에 상호 연관된 관계를 생성합니다. 관계 구조 다이어그램은 다음과 같습니다. 🎜🎜🎜🎜이 시점에서 를 잘라내면 소년들과 boys2
사이의 연관성: 🎜rrreee🎜객체들 사이의 연관성 다이어그램은 다음과 같습니다: 🎜🎜🎜🎜분명히 도달할 수 없는 노드는 없습니다. 🎜🎜이 시점에서 남자친구
관계 연결을 끊으면: 🎜rrreee🎜관계 다이어그램은 다음과 같습니다: 🎜🎜🎜🎜지금은 boys2
와 사이에 <code>여친이 아직 남아있지만 girls1
관계는 boys2
이며 연결할 수 없는 노드가 되어 가비지 수집 메커니즘에 의해 회수됩니다. 🎜🎜🎜접근 가능한 섬: 🎜🎜rrreee🎜위 코드로 구성된 참조 계층 다이어그램은 다음과 같습니다. 🎜🎜🎜🎜🎜 이때 점선 상자 안의 개체 간에는 여전히 상호 참조 관계가 있지만 이러한 개체는 또한 액세스할 수 없으며 가비지 수집 메커니즘에 의해 삭제됩니다. 이 노드는 🎜root🎜와의 관계가 끊어져 연결할 수 없게 되었습니다. 🎜🎜6. 가비지 수집 알고리즘🎜🎜🎜참조 카운트🎜🎜🎜 소위 참조 카운트는 이름에서 알 수 있듯이 개체가 참조될 때마다 카운트되며 1씩 증가합니다. 참조가 삭제되면 1씩 감소합니다. 참조 번호가 변경되면 0인 경우 가비지로 간주되어 해당 개체를 삭제하여 메모리를 회수합니다. 🎜🎜예: 🎜rrreee🎜참조 카운팅 방법이 매우 합리적으로 보이지만 실제로 참조 카운팅 방법을 사용하는 메모리 재활용 메커니즘에는 명백한 허점이 있습니다. 🎜🎜예: 🎜let boy = {}; let girl = {}; boy.girlfriend = girl; girl.boyfriend = boy; boy = null; girl = null;
以上代码在boy
和girl
之间存在相互引用,计数删掉boy
和girl
内的引用,二者对象并不会被回收。由于循环引用的存在,两个匿名对象的引用计数永远不会归零,也就产生了内存泄漏。
在C++
中存在一个智能指针(shared_ptr
)的概念,程序员可以通过智能指针,利用对象析构函数释放引用计数。但是对于循环引用的状况就会产生内存泄漏。
好在JavaScript
已经采用了另外一种更为安全的策略,更大程度上避免了内存泄漏的风险。
标记清除(mark and sweep
)是JavaScript
引擎采取的垃圾回收算法,其基本原理是从根出发,广度优先遍历变量之间的引用关系,对于遍历过的变量打上一个标记(优秀员工徽章
),最后删除没有标记的对象。
算法基本过程如下:
2
步,直至无新的优秀员工加入;举个栗子:
如果我们程序中存在如下图所示的对象引用关系:
我们可以清晰的看到,在整个图片的右侧存在一个“可达孤岛”,从根出发,永远无法到达孤岛。但是垃圾回收器并没有我们这种上帝视角,它们只会根据算法会首先把根节点打上优秀员工标记。
然后从优秀员工出发,找到所有被优秀员工引用的节点,如上图中虚线框中的三个节点。然后把新找到的节点同样打上优秀员工标记。
反复执行查找和标记的过程,直至所有能找到的节点都被成功标记。
最终达到下图所示的效果:
由于在算法执行周期结束之后,右侧的孤岛仍然没有标记,因此会被垃圾回收器任务无法到达这些节点,最终被清除。
如果学过数据结构和算法的童鞋可能会惊奇的发现,这不就是图的遍历吗,类似于连通图算法。
垃圾回收是一个规模庞大的工作,尤其在代码量非常大的时候,频繁执行垃圾回收算法会明显拖累程序的执行。JavaScript
算法在垃圾回收上做了很多优化,从而在保证回收工作正常执行的前提下,保证程序能够高效的执行。
性能优化采取的策略通常包括以下几点:
JavaScript
程序在执行过程中会维持相当量级的变量数目,频繁扫描这些变量会造成明显的开销。但是这些变量在生命周期上各有特点,例如局部变量会频繁的创建,迅速的使用,然后丢弃,而全局变量则会长久的占据内存。JavaScript
把两类对象分开管理,对于快速创建、使用并丢弃的局部变量,垃圾回收器会频繁的扫描,保证这些变量在失去作用后迅速被清理。而对于哪些长久把持内存的变量,降低检查它们的频率,从而节约一定的开销。
增量式的思想在性能优化上非常常见,同样可以用于垃圾回收。在变量数目非常大时,一次性遍历所有变量并颁发优秀员工标记显然非常耗时,导致程序在执行过程中存在卡顿。所以,引擎会把垃圾回收工作分成多个子任务,并在程序执行的过程中逐步执行每个小任务,这样就会造成一定的回收延迟,但通常不会造成明显的程序卡顿。
CPU
는 복잡한 프로그램에서도 항상 작동하지 않습니다. 이는 주로 CPU
가 매우 빠르게 작동하고 주변 장치 IO
때문입니다. >은 종종 몇 배나 느려지므로 CPU
가 유휴 상태일 때 가비지 수집 전략을 마련하는 것은 매우 효과적인 성능 최적화 방법이며 기본적으로 프로그램 자체에 부정적인 영향을 미치지 않습니다. 이 전략은 시스템의 유휴 시간 업그레이드와 유사하며 사용자는 백그라운드 실행을 전혀 인식하지 못합니다. CPU
即使是在复杂的程序中也不是一直都有工作的,这主要是因为CPU
工作的速度非常快,外围IO
往往慢上几个数量级,所以在CPU
空闲的时候安排垃圾回收策略是一种非常有效的性能优化手段,而且基本不会对程序本身造成不良影响。这种策略就类似于系统的空闲时间升级一样,用户根本察觉不到后台的执行。
本文的主要任务是简单的结束垃圾回收的机制、常用的策略和优化的手段,并不是为了让大家深入了解引擎的后台执行原理。
通过本文,你应该了解:
JavaScript
JavaScript
의 기능 중 하나이므로 걱정할 필요가 없습니다. 표시 전략을 명확하게 지정하면 도달 가능한 섬으로 인한 메모리 누수를 피할 수 있습니다. 끝]
위 내용은 JavaScript 숨겨진 메커니즘의 가비지 수집 지식 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!