
以下是JS遇到内存泄漏时应采用的方法的汇总.
随着当前编程语言的功能变得越来越成熟和复杂,内存管理很容易被忽略. 本文将讨论JavaScript中的内存泄漏以及如何处理它们,以便每个人都可以更好地处理JavaScript编码时由内存泄漏引起的问题.
概述
像C语言这样的编程语言具有简单的内存管理功能,例如malloc()和free(). 开发人员可以使用这些功能来显式分配和释放系统内存.
在创建对象,字符串等时,JavaScript将分配内存并在不再使用内存时自动释放内存. 这种机制称为垃圾收集. 此资源释放似乎是“自动的”,但本质上是令人困惑的,这也给JavaScript(和其他高级语言)的开发人员以错误的印象,即他们不关心内存管理. 实际上,这是一个很大的错误.
即使使用高级语言,开发人员也应该了解内存管理的知识. 有时,自动内存管理存在问题(例如垃圾收集器中的错误或实现限制等),开发人员必须理解这些问题才能正确处理它们.
内存生命周期
无论您使用哪种编程语言,内存生命周期都几乎相同:

以下是在内存生命周期的每个步骤中发生的事情的概述:
分配内存-内存是由操作系统分配的,允许程序使用它. 在简单的编程语言中,此过程是开发人员应处理的显式操作. 但是,在高级编程语言中,系统将帮助您完成此操作. 内存使用量-这是程序使用内存申请之前的一段时间,您的代码将使用分配的变量
读取和写入内存.
释放内存-释放不再需要的内存以确保其可用并可以再次使用的操作. 与内存分配操作类似,此操作需要使用简单的编程语言进行显示操作. 什么是记忆?
在硬件级别,计算机的内存由大量触发器组成. 每个触发器都包含一些晶体管,并且可以存储一位数据. 单个触发器可以通过唯一的标识符进行寻址,因此我们可以读取和覆盖它们. 因此,从概念上讲,我们可以将整个计算机内存视为可以读取和写入的很大空间.
很多东西都存储在内存中
程序使用的所有变量和其他数据. 该程序的代码,包括操作系统的代码.
编译器和操作系统一起工作以处理大多数内存管理,但是我们需要了解本质上正在发生什么.
在编译代码时,编译器将检查原始数据类型并事先计算它们需要多少内存,然后在调用堆栈空间中将所需的内存分配给程序. 为这些变量分配的空间称为堆栈空间. 调用该函数时,内存将添加到现有内存中. 终止后,将按照LIFO(后进先出)的顺序删除空间. 例如,以下语句:
int n; // 4个字节 int x [4]; // 4个元素的数组,每一个占4个字节 double m; // 8个字节
编译器插入与操作系统交互的代码,以请求堆栈中所需的字节数来存储变量.
在上面的示例中,编译器知道每个变量的确切内存地址. 实际上,每当我们写入此变量n时,它将在内部转换为“内存地址4127963”.
请注意,如果尝试访问x [4],我们将访问与m关联的数据. 这是因为我们正在访问数组中不存在的元素-它比数组中最后一个数据实际分配的元素多4个字节x [3],并且可能最终会读取(或覆盖)大约m位. 这将对其他人产生不利影响.


当一个函数调用其他函数时,每个函数在调用时都会获得自己的堆栈块. 它将保留所有局部变量和程序计数器,并记录执行位置. 该功能完成后,其存储块将被释放并可再次用于其他目的.
动态分配
如果我们在编译时不知道变量所需的内存量,事情将会变得复杂. 假设我们要执行以下操作:
int n = readInput(); //读取用户的输入 ... //用“n”个元素创建一个数组
在编译时,编译器不知道数组需要多少内存,因为它由用户提供的输入值确定.
因此,它无法为堆栈上的变量分配空间. 相反,我们的程序需要在运行时从操作系统显式请求适当的空间. 该内存是从堆空间分配的. 下表总结了静态和动态内存分配之间的区别:

在JavaScript中分配内存
现在让我们解释一下如何在JavaScript中分配内存.
JavaScript使开发人员摆脱了处理内存分配的工作.
var n = 374; // allocates memory for a number
var s = 'sessionstack'; // allocates memory for a string
var o = {
a: 1,
b: null
}; // allocates memory for an object and its contained values
var a = [1, null, 'str']; // (like object) allocates memory for the
// array and its contained values
function f(a) {
return a + 3;
} // allocates a function (which is a callable object)
// function expressions also allocate an object
someElement.addEventListener('click', function() {
someElement.style.backgroundColor = 'blue';
}, false);
某些函数调用也可能导致对象分配:
var d = new Date(); // allocates a Date object
var e = document.createElement('div'); // allocates a DOM element
方法可以分配新值或对象:
var s1 = 'sessionstack'; var s2 = s1.substr(0, 3); // s2 is a new string // Since strings are immutable, // JavaScript may decide to not allocate memory, // but just store the [0, 3] range. var a1 = ['str1', 'str2']; var a2 = ['str3', 'str4']; var a3 = a1.concat(a2); // new array with 4 elements being // the concatenation of a1 and a2 elements
使用JavaScript中的内存
基本上在JavaScript中使用分配的内存,这意味着对其进行读写.
这可以通过读取或写入变量或对象属性的值,甚至将参数传递给函数来实现.
在不再需要时释放内存
大多数内存泄漏问题是在此阶段生成的. 此阶段最困难的问题是确定何时不再需要分配的内存. 通常,它需要开发人员确定程序的哪一部分不再需要内存并释放它.
高级语言嵌入了一个称为垃圾收集器的函数,该函数的作用是跟踪内存分配和使用情况,以便可以在不再需要它时自动释放它.

不幸的是,此过程无法如此精确地完成,因为不再需要算法来解决诸如不再需要的某些内存之类的问题.
大多数垃圾收集器通过收集无法访问的内存来工作,例如指向该变量的变量超出范围. 但是,此方法只能收集内存空间的近似值,因为在内存中的某些位置可能仍然有指向它的变量,但是将不会再次访问它.
由于无法确定是否“不再需要”某些内存,因此垃圾回收机制具有一定的局限性. 主要垃圾收集算法的概念及其局限性将在下面说明.
内存参考
垃圾回收算法所依赖的主要概念之一是内存引用.
在内存管理的上下文中,如果对象访问变量(可以是隐式或显式的),则说该对象引用了另一个对象. 例如,一个JavaScript对象具有对原始对象的引用(隐式引用)及其属性值(显式引用).
在这种情况下,“对象”的概念扩展到比普通JavaScript对象更广泛的范围,并且还包括函数的范围.
引用计数垃圾收集
这是最简单的垃圾收集算法. 如果对此的引用为零,则该对象将被视为“垃圾收集”.
看下面的代码:
var o1 = {
o2: {
x: 1
}
};
// 2 objects are created.
// 'o2' is referenced by 'o1' object as one of its properties.
// None can be garbage-collected
var o3 = o1; // the 'o3' variable is the second thing that
// has a reference to the object pointed by 'o1'.
o1 = 1; // now, the object that was originally in 'o1' has a
// single reference, embodied by the 'o3' variable
var o4 = o3.o2; // reference to 'o2' property of the object.
// This object has now 2 references: one as
// a property.
// The other as the 'o4' variable
o3 = '374'; // The object that was originally in 'o1' has now zero
// references to it.
// It can be garbage-collected.
// However, what was its 'o2' property is still
// referenced by the 'o4' variable, so it cannot be
// freed.
o4 = null; // what was the 'o2' property of the object originally in
// 'o1' has zero references to it.
// It can be garbage collected.
周期导致问题
周期有限制. 例如,在下面的示例中,创建两个对象并互相引用将创建一个循环引用. 调用函数后,它们将超出范围,因此它们实际上是无用的,可以释放. 但是,引用计数算法认为,由于两个对象中的每个对象都至少被引用了一次,因此垃圾回收机制无法回收它们.
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f( );

标记和扫描算法
为了确定是否需要某个对象,标记和扫描算法确定该对象是否处于活动状态.
标记和扫描算法需要经历以下三个步骤:
roots: 通常,root是代码中引用的全局变量. 例如,在JavaScript中,可以充当根目录的全局变量是“窗口”对象. Node.js中的同一对象称为“全局”. 所有根的完整列表由垃圾收集器构建. 然后,该算法将检查所有根及其子级,并将其标记为活动状态(也就是说,它们不是垃圾). 根无法到达的任何内容都将被标记为垃圾. 最后,垃圾收集器释放所有未标记为活动的内存块,并将内存返回给操作系统.

此算法优于引用计数垃圾收集算法. 在JavaScript垃圾收集领域(代码/增量/并行/并行垃圾收集)所做的所有改进都是对此标记和扫描算法的实现改进,而不是垃圾收集算法本身的实现.
周期不再是问题

在上面的相互引用示例中,在函数调用返回之后,两个对象不再被全局对象可访问的对象引用. 因此,它们将被垃圾收集器发现并恢复.

即使对象之间存在引用,也无法从根目录访问它们,因此它们将被视为垃圾并被收集.
抵制垃圾回收器的直观行为
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-312040-1.html
o