-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
4种JavaScript内存泄漏浅析及如何用谷歌工具查内存泄露 #1
Labels
Comments
Thank you for sharing, very useful. |
good~ |
转的至少注明下出处吧 |
已注明出处。 |
赞 |
讲的真模糊 |
“在JavaScript中,“window”对象是可以充当根的全局变量的示例。窗口对象总是存在的,所以垃圾回收器可以考虑它和它的所有的孩子总是存在(即不是垃圾)。 ” |
如果上面的示例代码,如何修改才不会导致内存泄漏? |
1 similar comment
如果上面的示例代码,如何修改才不会导致内存泄漏? |
👍 |
翻译的有点看不懂.看英文可以... |
mark 写的详细 |
貌似已经过时了,现在的工具中pipeline貌似不存在~ |
最新浏览器已经不会内存泄漏了?复现不了 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="nodes"></div>
<script>
var x = [];
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment();
for (;i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow,1000);
}
grow()
</script>
</body>
</html> DOM nodes 一直在增加,但 js heep size 不变 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
1、介绍
内存泄漏是每个开发人员都要面临的问题。 即使使用内存管理的语言,也存在内存泄漏的情况。 内存泄漏是导致迟缓,崩溃,高延迟的根本原因,甚至会导致其他应用问题。
2、什么是内存泄露
实质上,内存泄漏可以定义为应用程序不再需要的内存,因为某种原因其不会返回到操作系统或可用内存池。编程语言有不同的管理内存的方式。这些方法可以减少泄漏内存的机会。然而,某一块内存是否未被使用实际上是一个不可判定的问题。 换句话说,只有开发人员才能明确是否可以将一块内存返回到操作系统。 某些编程语言提供了帮助开发人员执行此操作的功能。
3、JavaScript的内存管理
JavaScript是垃圾回收语言之一。 垃圾回收语言通过定期检查哪些先前分配的内存是否“可达”来帮助开发人员管理内存。 换句话说,垃圾回收语言将管理内存的问题从“什么内存仍可用? 到“什么内存仍可达?”。区别是微妙的,但重要的是:虽然只有开发人员知道将来是否需要一块分配的内存,但是不可达的内存可以通过算法确定并标记为返回到操作系统。
4、JavaScript的内存泄露
垃圾回收语言泄漏的主要原因是不需要的引用。要理解什么不需要的引用,首先我们需要了解垃圾回收器如何确定一块内存是否“可达”。
Mark-and-sweep
大多数垃圾回收器使用称为标记和扫描的算法。该算法由以下步骤组成:
5、四种常见的JavaScript 内存泄漏
5.1、意外的全局变量
JavaScript背后的目标之一是开发一种看起来像Java的语言,容易被初学者使用。 JavaScript允许的方式之一是处理未声明的变量:对未声明的变量的引用在全局对象内创建一个新的变量。 在浏览器的情况下,全局对象是窗口。 换一种说法:
事实上:
如果bar应该只在foo函数的范围内保存对变量的引用,并且您忘记使用var来声明它,那么会创建一个意外的全局变量。 在这个例子中,泄漏一个简单的字符串可能没什么,但有更糟糕的情况。
为了防止这些错误发生,添加'use strict'; 在您的JavaScript文件的开头。 这使得能够更严格地解析JavaScript以防止意外的全局变量。
5.2、被遗忘的计时器或回调函数
setInterval的使用在JavaScript中是很常见的。大多数这些库在它们自己的实例变得不可达之后,使得对回调的任何引用不可达。在setInterval的情况下,但是,像这样的代码是很常见的:
此示例说明了挂起计时器可能发生的情况:引用不再需要的节点或数据的计时器。 由节点表示的对象可以在将来被移除,使得区间处理器内部的整个块不需要了。 但是,处理程序(因为时间间隔仍处于活动状态)无法回收(需要停止时间间隔才能发生)。 如果无法回收间隔处理程序,则也无法回收其依赖项。 这意味着someResource,它可能存储大小的数据,也不能被回收。
关于对象观察者和循环引用:
5.3、脱离 DOM 的引用
有时,将DOM节点存储在数据结构中可能很有用。 假设要快速更新表中多行的内容。 在字典或数组中存储对每个DOM行的引用可能是有意义的。 当发生这种情况时,会保留对同一个DOM元素的两个引用:一个在DOM树中,另一个在字典中。 如果在将来的某个时候,您决定删除这些行,则需要使这两个引用不可访问。
5.4、闭包
JavaScript开发的一个关键方面是闭包:从父作用域捕获变量的匿名函数。 Meteor开发人员发现了一个特定的情况,由于JavaScript运行时的实现细节,可能以一种微妙的方式泄漏内存:
Meteor的博文解释了如何修复此种问题。在replaceThing的最后添加originalThing = null。
垃圾回收器的不直观行为:
Google在他们的JavaScript内存分析文档中提供了这种行为的一个很好的例子,next!!!。
6、Chrome内存分析工具概述
Chrome提供了一组很好的工具来分析JavaScript代码的内存使用情况。 有两个与内存相关的基本视图:时间轴视图和配置文件视图。
6.1、TimeLine
TimeLine对于在代码中发现异常内存模式至关重要。 如果我们正在寻找大的泄漏,周期性的跳跃,收缩后不会收缩,就像一个红旗。 在这个截图中,我们可以看到泄漏对象的稳定增长可能是什么样子。 即使在大收集结束后,使用的内存总量高于开始时。 节点计数也较高。 这些都是代码中某处泄露的DOM节点的迹象。
6.2、Profiles
这是你将花费大部分时间看的视图。 Profiles允许您获取快照并比较JavaScript代码的内存使用快照。 它还允许您记录分配的时间。 在每个结果视图中,不同类型的列表都可用,但是对于我们的任务最相关的是summary(概要)列表和comparison(对照)列表。
summary(概要)列表为我们概述了分配的不同类型的对象及其聚合大小:浅大小(特定类型的所有对象的总和)和保留大小(浅大小加上由于此对象保留的其他对象的大小 )。 它还给了我们一个对象相对于它的GC根(距离)有多远的概念。
comparison(对照)给了我们相同的信息,但允许我们比较不同的快照。 这对于查找泄漏是非常有用的。
7、示例:使用Chrome查找泄漏
由于明显的原因,当它们是周期性的时更容易发现泄漏。这些也是最麻烦的:如果内存在时间上增加,这种类型的泄漏将最终导致浏览器变慢或停止脚本的执行。不是周期性的泄漏可以很容易地发现。这通常会被忽视。在某种程度上,发生一次的小泄漏可以被认为是优化问题。然而,周期性的泄漏是错误并且必须解决的。
当调用grow时,它将开始创建div节点并将它们附加到DOM。它还将分配一个大数组,并将其附加到全局变量引用的数组。这将导致使用上述工具可以找到的内存的稳定增加。
7.1、了解内存是否周期性增加
Timeline非常有用。 在Chrome中打开示例,打开开发工具,转到Timeline,选择Memory,然后点击录制按钮。 然后转到页面并单击按钮开始泄漏内存。 一段时间后停止录制,看看结果:
此示例将继续每秒泄漏内存。停止录制后,在grow函数中设置断点,以停止脚本强制Chrome关闭页面。在这个图像有两个大的迹象,表明我们正在记录泄漏。节点(绿线)和JS堆(蓝线)的图。节点正在稳步增加,从不减少。这是一个大的警告标志。
7.2、现在确定有泄漏。 让我们找到它。
1、获取两个快照
要查找泄漏,我们现在将转到Chrome的开发工具的profiles部分。要将内存使用限制在可管理的级别,请在执行此步骤之前重新加载页面。我们将使用Take Heap Snapshot函数。
重新加载页面,并在完成加载后立即获取堆快照。 我们将使用此快照作为我们的基线。之后,再次点击最左边的Profiles按钮,等待几秒钟,并采取第二个快照。捕获快照后,建议在脚本中设置断点,以防止泄漏使用更多内存。
有两种方法可以查看两个快照之间的分配。 选择summary(摘要),右侧选择 Objects allocated between Snapshot 1 and Snapshot 2,或者筛选菜单选择 Comparison。在这两种情况下,我们将看到在两个快照之间分配的对象的列表。
在这种情况下,很容易找到泄漏:他们很大。看看 (string) 的 Size Delta Constructor,8MB,58个新对象。 这看起来很可疑:新对象被分配,但是没有释放,占用了8MB。
如果我们打开 (string) Constructor的分配列表,我们将注意到在许多小的分配之间有一些大的分配。大者立即引起我们的注意。如果我们选择其中的任何一个,我们可以在下面的retainers部分得到一些有趣的东西。
我们看到我们选择的分配是数组的一部分。反过来,数组由全局窗口对象内的变量x引用。这给了我们从我们的大对象到其不可收回的根(窗口)的完整路径 我们发现我们的潜在泄漏和被引用的地方。
到现在为止还挺好。但我们的例子很容易:大分配,例如在这个例子中的分配不是常态。幸运的是,我们的例子也泄漏了DOM节点,它们更小。使用上面的快照很容易找到这些节点,但在更大的网站,会变得更麻烦。 最新版本的Chrome提供了一个最适合我们工作的附加工具:记录堆分配功能。
2、Record heap allocations查找泄漏
禁用之前设置的断点,让脚本继续运行,然后返回Chrome的开发工具的“个人档案”部分。现在点击Record Heap Allocations。当工具运行时,您会注意到在顶部的图中的蓝色尖峰。这些代表分配。每秒大的分配由我们的代码执行。让它运行几秒钟,然后停止它(不要忘记再次设置断点,以防止Chrome吃更多的内存)。
在此图像中,您可以看到此工具的杀手锏:选择一段时间线以查看在该时间段内执行的分配。我们将选择设置为尽可能接近一个大峰值。列表中只显示了三个构造函数:其中一个是与我们的大漏洞((string))相关的构造函数,下一个与DOM分配相关,最后一个是Text构造函数(叶子DOM节点的构造函数 包含文本)。
#####从列表中选择一个 HTMLDivElement constructor,然后选择Allocation stack。
我们现在知道分配该元素的位置(grow - > createSomeNodes)。如果我们密切注意图中的每个尖峰,我们将注意到 HTMLDivElement constructor被调用了许多次。如果我们回到我们的快照比较视图,我们将注意到这个constructor显示许多分配,但没有删除。 换句话说,它正在稳定地分配内存,而没有被GC回收。从而我们知道这些对象被分配的确切位置(createSomeNodes函数)。现在回到代码,研究它,并修复漏洞。
3、另一个有用的功能
在堆分配结果视图中,我们可以选择Allocation视图。
这个视图给了一个与它们相关的函数和内存分配的列表。我们可以立即看到grow和createSomeNodes。当选择grow时,看看相关的object constructor。 可以注意到(string),HTMLDivElement和Text泄露了。
这些工具的组合可以大大有助于发现内存泄漏。在生产站点中执行不同的分析运行(理想情况下使用非最小化或模糊代码)。看看你是否能找到比他们应该保留更多的泄漏或对象(提示:这些更难找到)。
8、请深入阅读
9、总结
内存泄漏可以并且确实发生在垃圾回收语言,如JavaScript。这些可以被忽视一段时间,最终他们将肆虐你的网站。因此,内存分析工具对于查找内存泄漏至关重要。分析运行应该是开发周期的一部分,特别是对于中型或大型应用程序。开始这样做,为您的用户提供最好的体验。
The text was updated successfully, but these errors were encountered: