-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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 不变 |
# for free
to join this conversation on GitHub.
Already have an account?
# 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: