>  기사  >  웹 프론트엔드  >  JavaScript의 메모리 관리에 대한 자세한 설명

JavaScript의 메모리 관리에 대한 자세한 설명

青灯夜游
青灯夜游앞으로
2021-01-06 10:14:352238검색

JavaScript의 메모리 관리에 대한 자세한 설명

관련 권장 사항: "javascript 비디오 튜토리얼"

대부분의 경우 JS 엔진이 이 문제를 처리해 주기 때문에 메모리 관리에 대한 지식을 이해하지 못한 채 개발을 진행합니다. 그러나 때때로 메모리 누수와 같은 문제가 발생하는 경우에는 메모리 할당이 어떻게 작동하는지 알아야만 이러한 문제를 해결할 수 있습니다.

이 글에서는 주로 메모리 할당가비지 수집 작동 방식과 일반적인 메모리 누수 문제를 방지하는 방법을 소개합니다.

캐시(메모리) 수명 주기

JS에서는 변수, 함수 또는 객체를 만들 때 JS 엔진이 메모리를 할당하고 더 이상 필요하지 않으면 해제합니다.

메모리 할당은 메모리 공간을 예약하는 프로세스인 반면, 메모리 해제는 다른 목적을 위해 공간을 확보합니다.

변수를 할당하거나 함수를 생성할 때마다 해당 변수의 저장은 동일한 단계를 거칩니다.

JavaScript의 메모리 관리에 대한 자세한 설명

메모리 할당

  • JS가 이를 처리합니다. JS는 우리가 생성하는 객체에 필요한 메모리를 할당합니다. .

메모리 사용

  • 메모리 사용은 코드에서 명시적으로 수행하는 작업입니다. 메모리를 읽고 쓰는 것은 실제로 변수를 읽고 쓰는 것입니다.

Release memory

  • 이 단계는 JS 엔진에서도 처리되며, 할당된 메모리가 해제되면 새로운 용도로 사용할 수 있습니다.

메모리 관리 맥락에서 "객체"에는 JS 객체뿐만 아니라 함수 및 함수 범위도 포함됩니다.

메모리 힙 및 스택

이제 우리는 JS에서 정의한 모든 것에 대해 엔진이 메모리를 할당하고 더 이상 필요하지 않을 때 해제한다는 것을 알았습니다.

내 마음에 떠오르는 다음 질문은 이 물건들이 어디에 저장될 것인가 하는 것입니다.

JS 엔진은 메모리 힙스택 두 곳에 데이터를 저장할 수 있습니다. 힙과 스택은 엔진에서 서로 다른 목적으로 사용하는 두 가지 데이터 구조입니다.

스택: 정적 메모리 할당

JavaScript의 메모리 관리에 대한 자세한 설명

스택은 JS에서 정적 데이터를 저장하는 데 사용하는 데이터 구조입니다. 정적 데이터는 엔진이 컴파일 타임에 크기를 알고 있는 데이터입니다. JS에서는 여기에는 객체와 함수를 가리키는 기본 값(stringsnumberbooleanundefinednull)과 참조 유형이 포함됩니다.

엔진은 크기가 변경되지 않는다는 것을 알고 있으므로 각 값에 대해 고정된 양의 메모리를 할당합니다.

실행 직전에 메모리를 할당하는 과정을 정적 메모리 할당이라고 합니다. 이러한 값과 전체 스택의 제한은 브라우저에 따라 다릅니다.

힙: 동적 메모리 할당

은 JS가 객체함수를 저장하는 데이터가 저장되는 또 다른 공간입니다.

스택과 달리 JS 엔진은 이러한 객체에 고정된 양의 메모리를 할당하지 않고 필요에 따라 공간을 할당합니다. 이러한 메모리 할당 방법을 동적 메모리 할당이라고도 합니다.

이 두 저장소의 특성은 아래에서 비교됩니다.

Stack Heap
기본 유형 및 참조를 저장합니다.
크기는 컴파일 타임에 알려집니다.
고정된 양의 메모리를 할당합니다.
Object 그리고 기능은
런타임에만 크기를 알 수 있습니다
제한이 없습니다

예제

이미지를 향상하기 위해 몇 가지 예를 들어보겠습니다.

const person = {
  name: 'John',
  age: 24,
};

JS는 힙에서 이 객체에 대한 메모리를 할당합니다. 실제 값은 여전히 ​​원래 값이므로 스택에 저장됩니다.

const hobbies = ['hiking', 'reading'];

배열도 객체이므로 힙에 저장됩니다.

let name = 'John'; // 为字符串分配内存
const age = 24; // 为字分配内存

name = 'John Doe'; // 为新字符串分配内存
const firstName = name.slice(0,4); // 为新字符串分配内存

초기 값은 불변이므로 JS는 원래 값을 변경하지 않고 새 값을 생성합니다.

JavaScript의 참조

모든 변수는 먼저 스택을 가리킵니다. 기본 값이 아닌 경우 스택에는 의 개체에 대한 참조가 포함됩니다. 堆栈包含对中对象的引用。

堆的内存没有按特定的方式排序,所以我们需要在堆栈中保留对其的引用。 我们可以将引用视为地址,并将堆中的对象视为这些地址所属的房屋。

请记住,JS 将对象函数存储在堆中。 基本类型和引用存储在堆栈中。

JavaScript의 메모리 관리에 대한 자세한 설명

这张照片中,我们可以观察到如何存储不同的值。 注意personnewPerson都如何指向同一对象。

事例

const person = {
  name: 'John',
  age: 24,
};

这将在堆中创建一个新对象,并在堆栈中创建对该对象的引用。

垃圾回收

现在,我们知道 JS 如何为各种对象分配内存,但是在内存生命周期,还有最后一步:释放内存

就像内存分配一样,JavaScript引擎也为我们处理这一步骤。 更具体地说,垃圾收集器负责此工作。

一旦 JS 引擎识别变量或函数不在被需要时,它就会释放它所占用的内存。

这样做的主要问题是,是否仍然需要一些内存是一个无法确定的问题,这意味着不可能有一种算法能够在不再需要那一刻立即收集不再需要的所有内存。

一些算法可以很好地解决这个问题。 我将在本节中讨论最常用的方法:引用计数标记清除算法。

引用计数

当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另外一个变量,则该值得引用次数加1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1

当这个值的引用次数变成 0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

我们看下面的例子。

JavaScript의 메모리 관리에 대한 자세한 설명

请注意,在最后一帧中,只有hobbies留在堆中的,因为最后引用的是对象。

周期数

引用计数算法的问题在于它不考虑循环引用。 当一个或多个对象互相引用但无法再通过代码访问它们时,就会发生这种情况。

let son = {
  name: 'John',
};

let dad = {
  name: 'Johnson',
}

son.dad = dad;
dad.son = son;

son = null;
dad = null;

JavaScript의 메모리 관리에 대한 자세한 설명

由于父对象相互引用,因此该算法不会释放分配的内存,我们再也无法访问这两个对象。

它们设置为null不会使引用计数算法识别出它们不再被使用,因为它们都有传入的引用。

标记清除

标记清除算法对循环依赖性有解决方案。 它检测到是否可以从root 对象访问它们,而不是简单地计算对给定对象的引用。

浏览器的rootwindow 对象,而NodeJS中的rootglobal

JavaScript의 메모리 관리에 대한 자세한 설명

该算法将无法访问的对象标记为垃圾,然后对其进行扫描(收集)。 根对象将永远不会被收集。

这样,循环依赖关系就不再是问题了。在前面的示例中,dad对象和son

힙의 메모리는 특정한 방식으로 정렬되지 않으므로 스택에서 이에 대한 참조를 유지해야 합니다. 참조를 주소로 생각하고, 힙에 있는 객체를 해당 주소가 속한 주택으로 생각할 수 있습니다.

JS는

objects

functions

을 힙에 저장한다는 점을 기억하세요. 기본 유형과 참조는 스택에 저장됩니다.

JavaScript의 메모리 관리에 대한 자세한 설명

이것 사진에서 우리는 다양한 값이 어떻게 저장되는지 관찰할 수 있습니다. personnewPerson이 모두 동일한 객체를 가리키는 방식에 주목하세요.

Example🎜
users = getUsers();
🎜이렇게 하면 힙에 새 객체가 생성되고 스택의 객체에 대한 참조가 생성됩니다. 🎜🎜Garbage Collection🎜🎜이제 JS가 다양한 객체에 메모리를 할당하는 방법을 알았지만 메모리 수명 주기에는 마지막 단계가 하나 있습니다: 🎜Release memory🎜. 🎜🎜메모리 할당과 마찬가지로 JavaScript 엔진도 이 단계를 처리합니다. 더 구체적으로 말하면 🎜Garbage Collector🎜가 이 작업을 담당합니다. 🎜🎜JS 엔진은 변수나 함수가 더 이상 필요하지 않다는 것을 인식하면 해당 변수나 함수가 차지했던 메모리를 해제합니다. 🎜🎜이것의 가장 큰 문제는 일부 메모리가 여전히 필요한지 여부는 결정할 수 없는 질문이라는 것입니다. 즉, 더 이상 필요하지 않은 순간 더 이상 필요하지 않은 모든 메모리를 즉시 수집할 수 있는 알고리즘을 갖는 것이 불가능하다는 의미입니다. 🎜🎜일부 알고리즘은 이 문제를 매우 잘 해결할 수 있습니다. 이 섹션에서는 가장 일반적인 방법인 Reference CountingMark Sweep 알고리즘에 대해 논의하겠습니다. 🎜🎜참조 횟수🎜🎜변수가 선언되고 해당 변수에 참조 유형 값이 할당되면 이 값에 대한 참조 횟수는 1입니다. 동일한 값이 다른 변수에 할당되면 해당 값의 참조 횟수가 1씩 증가합니다. 반대로, 이 값에 대한 참조를 포함하는 변수가 다른 값을 사용하는 경우 이 값에 대한 참조 수는 1만큼 감소합니다. 🎜🎜이 값에 대한 참조 횟수가 0이 되면 더 이상 이 값에 접근할 수 없다는 의미이므로 차지하는 메모리 공간을 회수할 수 있습니다. 이렇게 하면 다음에 가비지 수집기가 실행될 때 참조가 0인 값이 차지한 메모리를 해제하게 됩니다. 🎜🎜아래 예를 살펴보겠습니다. 🎜🎜JavaScript의 메모리 관리에 대한 자세한 설명🎜🎜부탁드립니다 마지막 참조는 개체에 대한 것이므로 마지막 프레임에서는 취미만 힙에 남아 있습니다. 🎜🎜주기 계산🎜🎜 참조 계산 알고리즘의 문제점은 순환 참조를 고려하지 않는다는 것입니다. 이는 하나 이상의 개체가 서로를 참조하지만 더 이상 코드를 통해 액세스할 수 없는 경우에 발생합니다. 🎜
window.users = null;
🎜JavaScript의 메모리 관리에 대한 자세한 설명🎜🎜 상위 개체가 서로를 참조하므로 알고리즘은 할당된 메모리를 해제하지 않으며 더 이상 두 개체에 모두 액세스할 수 없습니다. 🎜🎜null로 설정해도 참조 계산 알고리즘은 모두 수신 참조를 갖고 있으므로 더 이상 사용되지 않는다고 인식하지 않습니다. 🎜🎜Mark and Sweep🎜🎜Mark and Sweep 알고리즘에는 순환 종속성에 대한 솔루션이 있습니다. 단순히 주어진 객체에 대한 참조를 계산하는 것이 아니라 root 객체에서 액세스할 수 있는지 여부를 감지합니다. 🎜🎜브라우저의 루트window 객체이고, NodeJS의 루트전역입니다. 🎜🎜JavaScript의 메모리 관리에 대한 자세한 설명🎜🎜이것 알고리즘은 연결할 수 없는 개체를 가비지로 표시한 다음 이를 검색(수집)합니다. 루트 개체는 수집되지 않습니다. 🎜🎜이렇게 하면 순환 종속성은 더 이상 문제가 되지 않습니다. 이전 예에서는 dad 개체나 son 개체 모두 루트에서 액세스할 수 없습니다. 따라서 모두 쓰레기로 표시되어 수집됩니다. 🎜🎜이 알고리즘은 2012년 이후 모든 최신 브라우저에서 구현되었습니다. 성능과 구현만 개선되었을 뿐, 알고리즘의 핵심 아이디어는 동일하게 유지됩니다. 🎜🎜Trade 🎜🎜자동 가비지 수집을 통해 메모리 관리에 시간을 낭비하는 대신 애플리케이션 구축에 집중할 수 있습니다. 그러나 절충안이 있습니다. 🎜🎜메모리 사용량🎜🎜알고리즘은 메모리가 더 이상 필요하지 않은 시기를 정확히 알지 못하므로 JS 애플리케이션은 실제로 필요한 것보다 더 많은 메모리를 사용할 수 있습니다. 🎜🎜객체가 가비지로 표시되더라도 할당된 메모리를 수집할 시기와 수집 여부를 결정하는 것은 가비지 수집기에 달려 있습니다. 🎜

如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。

性能

收集垃圾的算法通常会定期运行以清理未使用的对象。

问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。

内存泄漏

在全局变量中存储数据,最常见内存问题可能是内存泄漏

在浏览器的 JS 中,如果省略varconstlet,则变量会被加到window对象中。

users = getUsers();

在严格模式下可以避免这种情况。

除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。

释放它很简单,把 null 给它就行了。

window.users = null;

被遗忘的计时器和回调

忘记计时器和回调可以使我们的应用程序的内存使用量增加。 特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。

被遗忘的计时器

const object = {};
const intervalId = setInterval(function() {
  // 这里使用的所有东西都无法收集直到清除`setInterval`
  doSomething(object);
}, 2000);

上面的代码每2秒运行一次该函数。 如果我们的项目中有这样的代码,很有可能不需要一直运行它。

只要setInterval没有被取消,则其中的引用对象就不会被垃圾回收。

确保在不再需要时清除它。

clearInterval(intervalId);

被遗忘的回调

假设我们向按钮添加了onclick侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。

不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。

const element = document.getElementById('button');
const onClick = () => alert('hi');

element.addEventListener('click', onClick);

element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);

脱离DOM引用

内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM元素时。

const elements = [];
const element = document.getElementById('button');
elements.push(element);

function removeAllElements() {
  elements.forEach((item) => {
    document.body.removeChild(document.getElementById(item.id))
  });
}

删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。

const elements = [];
const element = document.getElementById('button');
elements.push(element);

function removeAllElements() {
  elements.forEach((item, index) => {
    document.body.removeChild(document.getElementById(item.id));
    elements.splice(index, 1);
  });
}

由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。

总结

在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。

希望这篇对你有所帮助,我们下期再见,记得三连哦!

原文地址:https://felixgerschau.com/javascript-memory-management/

作者:Ahmad shaded

译文地址:https://segmentfault.com/a/1190000037651993

更多编程相关知识,请访问:编程入门!!

위 내용은 JavaScript의 메모리 관리에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제