web应用程序中的内存泄漏非常普遍,而且难以调试。泄漏通过增加垃圾收集成本降低响应能力,甚至可能导致浏览器选项卡崩溃。以前为传统应用程序设计的泄漏检测方法在浏览器环境中无效。目前,追踪泄密需要web开发人员进行大量的手工工作,但往往不成功。
本文介绍了Browser Leak debugger(浏览器泄漏调试器),它是第一个自动调试web应用程序中的内存泄漏的系统。凄惨的算法利用观察,在现代web应用程序中,用户经常重复返回相同的(近似的)视觉状态(例如,Gmail中的收件箱视图)。往返行程之间的持续增长是内存泄漏的一个强烈指标。为了使用荒凉,开发人员编写了一个简短的脚本(在我们的基准测试中17-73 LOC)来驱动web应用程序往返到相同的视觉状态。然后,凄凉自动生成一个泄漏列表,连同它们的根本原因,按投资回报排名。在黯淡的指导下,我们识别和修复超过50个流行的库和应用程序的内存泄漏,包括Airbnb, AngularJS,谷歌Analytics,谷歌地图SDK和jQuery。萧氏的中位精度为100%;修复它识别的泄漏可以平均减少94%的堆增长,每次往返节省0.5MB到8MB。
浏览器是智能手机和桌面平台上最流行的应用程序之一。它们还以消耗大量内存而闻名。为了解决这个问题,浏览器供应商花费了大量精力来减少浏览器的内存占用5,11以及构建跟踪特定浏览器组件内存消耗的工具。4,10
web应用程序中的内存泄漏只会进一步增加浏览器的内存占用,从而加剧这种情况。这些泄漏发生在应用程序引用不需要的状态时——阻止垃圾收集器收集它。Web应用程序内存泄漏可以有多种形式,包括未能处理不需要的事件监听器、重复注入iframes和CSS文件以及未能调用第三方库中的清理例程。泄漏对开发人员来说是一个严重的问题,因为它们会导致更高的垃圾收集频率和开销。它们降低了应用程序的响应能力,甚至可能通过耗尽可用内存而触发浏览器选项卡崩溃。
尽管web应用程序中的内存泄漏是一个众所周知的普遍问题,但没有有效的自动化工具可以发现它们。原因是现有的内存泄漏检测技术在浏览器中无效:web应用程序中的漏洞与传统C、C语言中的漏洞有本质的不同+ +,和Java程序。基于过时的技术假设泄漏的内存很少被触及,2,6,12,14,16但是web应用程序经常与泄漏的状态交互(例如,通过事件监听器)。基于增长的技术假设泄漏的对象是惟一拥有的,或者在堆图中形成强连接的组件。9,16在web应用程序中,泄漏的对象通常有多个所有者,由于对全局作用域的广泛引用,整个堆图通常是强连接的(窗口
).
面对这种缺乏自动化工具支持的情况,开发人员目前被迫手动检查堆快照,以定位应用程序错误地保留的对象。1,8不幸的是,这些快照不一定提供可操作的信息(见第2.1节)。它们同时提供了太多的信息(堆上的每个对象),却没有足够的信息来实际调试这些泄漏(没有连接到负责泄漏的代码)。由于JavaScript是动态类型化的,快照中的大多数对象都被标记为对象或数组,这在定位泄漏源方面提供了很少的帮助。结果是,即使是专业的开发者也无法找到漏洞:例如,谷歌的开发者关闭了谷歌Maps SDK漏洞(有117颗星和62条评论),因为他们“不确定在多少地方(它)泄漏了”,所以这是“不可思议的”修复。1
我们使用凄凉(浏览器泄漏调试器)来解决这些挑战,这是第一个自动调试web应用程序中的内存泄漏的系统。“荒凉”利用了以下事实:在一个单一的会话中,用户反复返回到现代网站中相同的视觉状态,如Facebook, Airbnb和Gmail。例如,Facebook用户反复返回消息源,Airbnb用户反复返回列出给定区域所有属性的页面,Gmail用户反复返回收件箱视图。
我们观察到这些往返可以看作是识别泄漏的指示。每次web应用程序返回到相同的视觉状态时,它应该消耗大约相同的内存量。因此,在往返过程中持续的内存增长是内存泄漏的明确指标。凄凉直接建立在观察的基础上,以发现web应用程序中的内存泄漏,这(如第6节所示)是普遍和严重的。
为了使用荒凉,开发人员提供了一个简短的脚本(在我们的基准测试中17-73 LOC)来驱动一个web应用程序在一个特定的视觉状态中进行循环。然后,黯淡自动进行,识别内存泄漏,对它们进行排序,并在源代码中定位其根本原因。萧山首先使用堆差异来定位在每次往返之间有持续增长的堆中的位置,它将这些位置识别为泄漏根。为了直接确定增长的根本原因,凄凉使用JavaScript重写来锁定泄漏根,并在泄漏根增长时收集堆栈跟踪。最后,在向开发人员展示结果时,使用一种名为LeakShare的新指标,根据投资回报对泄漏根进行排名,该指标通过在保留共享泄漏对象的泄漏根中平均分配“荣誉”,以最少的努力释放最多内存的泄漏进行优先级排序。这个排名将开发人员的工作集中在最重要的泄漏上。
在黯淡的指导下,我们识别和修复了超过50个流行的JavaScript库和应用程序,包括Airbnb, AngularJS, jQuery,谷歌Analytics,谷歌Maps SDK的内存泄漏。凄凉的中位数精度为100%(平均97%)。它对泄漏的根本原因的精确识别使我们能够相对直接地修复几乎所有我们识别的泄漏(除了一个)。修复这些泄漏平均可以减少94%的堆增长,每次返回相同的可视状态可以节省0.5MB到8MB。我们已经向应用程序开发人员提交了所有这些漏洞的补丁;在撰写本文时,16个已经被接受,4个正在进行代码评审。
本文的贡献如下:
在介绍凄凉及其算法之前,我们首先描述了一个使用凄凉发现的代表性内存泄漏(参见图1),并讨论为什么之前的技术和工具在调试web应用程序中的泄漏时存在不足。
图1。这段来自Firefox调试器的代码(为了可读性而被截断),每当开发人员打开源文件(章节2)时,就会泄露0.5MB。
这个内存泄漏发生在Firefox的调试器中,它在所有浏览器中都像正常的web应用程序一样运行。第6-9行在调试器的文本编辑器上注册四个事件监听器(codeMirror
)及其GUI对象(包装器
)每次用户查看源文件。发生泄漏是因为当视图关闭时,代码未能删除侦听器。每个事件侦听器都泄漏这
的一个实例预览
.
2.1.通过堆快照泄漏调试
目前还没有用于识别web应用程序中的内存泄漏的自动化技术。当前的技术状态是手动处理堆快照。正如我们所展示的,这种方法不能有效地识别泄漏对象或提供有用的诊断信息,因此它对帮助开发人员定位和修复内存泄漏几乎没有帮助。
手工调试内存泄漏最流行的方法是通过三堆快照技术Gmail团队介绍。8开发人员在网页上重复一个任务两次,并检查第一次运行任务时创建的仍然存在的对象。假设每次运行都会清除上次运行中创建的大多数对象,只留下泄漏的对象;但实际上并非如此。
为了将这种技术应用到Firefox的调试器,开发人员在加载调试器之后进行堆快照,在打开源文件之后进行第二个快照,在关闭并重新打开源文件之后进行第三个快照。然后,开发人员筛选第三个堆快照,只关注在第一个和第二个堆快照之间分配的对象。
此过滤视图,如图所示图2一个,不能清楚地识别内存泄漏。这些对象中的大多数只是从任务的前一次执行中重用的,实际上并不是泄漏,但是开发人员必须手动检查这些对象,然后才能得出结论。第一项,数组
,将应用程序中的所有数组合并到一个标题下,因为JavaScript是动态类型的。令人困惑的是,条目(数组
)就在它下面,它指的是V8的内部数组,它们不受应用程序的直接控制。开发商不太可能怀疑预览
对象为主要泄漏,因为它在列表中显示较低,且保留的大小较小。
图2。手动内存泄漏调试过程:目前,开发人员通过首先检查堆快照来查找泄漏对象(a)来调试泄漏。然后,他们尝试使用保留路径来定位负责的代码(b)。不幸的是,这些路径与代码没有连接,因此开发人员必须在他们的代码库中搜索路径中引用的标识符(见第2.1节)。这个过程可能很耗时,最终会无果而终。通过自动检测和定位导致内存泄漏的代码,凄凉为开发人员节省了大量精力。
即使开发人员在快照中识别出泄漏对象,诊断和修复仍然具有挑战性,因为快照不包含与代码的关系。快照只在堆中提供保留路径,这些路径通常由第三方库或浏览器本身控制。作为图2 b如图所示,泄漏的保留路径预览
对象派生自一个数组和一个未识别的DOM对象。使用这些保留路径定位导致泄漏的代码需要沿着路径遍历代码中的标识符实例。这一任务通常因两个因素而进一步复杂化:(1)第三方库的存在,必须手动检查;(2)统一的常用用法,它通过将大多数变量名和一些对象属性简化为单个字母,有效地混淆了代码和堆路径。
本节概述了凄凉自动检测、排序和诊断内存泄漏所使用的技术。我们通过展示如何使用黯淡来调试第2节中介绍的Firefox内存泄漏来说明这些问题。
输入脚本:开发人员提供了一个简单的脚本,通过特定的可视状态在循环中驱动web应用程序。一个视觉状态是用户执行操作(如单击链接或提交表单)后GUI的静止状态。开发人员将循环指定为对象数组,其中每个对象表示特定的视觉状态,包括(1)a检查
函数检查处于该状态的前提条件,以及(2)转换函数下一个
它与页面交互以导航到循环中的下一个可视状态。循环数组中的最后一个可视状态转换回第一个可视状态,形成一个循环。
图3一为Firefox调试器提供一个循环,用于在调试器的文本编辑器中打开和关闭源文件。当编辑器中没有打开任何选项卡时(第8行),并且应用程序加载了正在调试的应用程序中的文档列表(第10行),就会出现第一个可视状态;这是调试器首次加载时的默认状态。一旦应用程序处于第一种可视状态,循环通过单击将应用程序转换到第二种可视状态main.js
在文本编辑器中打开它(第12行)。的内容之后,应用程序进入第二种可见状态main.js
(第18行)。循环然后关闭包含main.js
(第24行),转换回到第一个视觉状态。
图3。使用凄凉的自动内存泄漏调试:开发人员需要提供给凄凉的唯一输入是一个简单的脚本,驱动目标web应用程序在一个循环中(a)。凄凉然后自动运行,产生一个内存泄漏的排序列表,栈跟踪指向负责泄漏的代码(b)。
定位泄漏:从这一点开始,《荒凉》将完全自动进行下去。凄凉使用开发人员提供的脚本在一个循环中驱动web应用程序。因为对象实例可以在不同的快照之间发生变化,所以暗淡进行跟踪路径而不是对象,让它发现泄漏,甚至当一个变量或对象属性定期更新一个新的更大的对象。例如,历史=历史。concat (newItems)
覆盖历史
使用一个新的更大的数组。
在对循环中的第一个可视状态的每次访问期间,荒凉将获取一个堆快照,并从持续增长的GC根跟踪特定路径。如果由该路径标识的对象获得更多的传出引用(例如,当数组扩展或当属性添加到对象中时),则凄凉将该路径视为增长路径。
对于Firefox调试器,凄凉注意到四个堆路径在每次往返中都在增长:(1)codeMirror
包含滚动
事件监听器,以及(2)的内部浏览器事件监听器列表鼠标移至
, (3)mouseup
, (4)mousedown
事件发生在包含文本编辑器的DOM元素上。因为这些对象在多个循环迭代中继续增长(默认设置为8),所以凄凉标记这些项为泄漏根因为它们似乎在无限制地生长。
排名泄漏:荒凉使用最后的堆快照和泄漏根列表,使用一种新颖但直观的度量标准,根据投资回报对泄漏进行排名LeakShare(第4.3节),它优先考虑那些以最少的努力释放最多内存的内存泄漏。LeakShare删除非泄漏根可访问的图中的对象,然后在保留它们的泄漏根中平均分配剩余对象的功劳。不像保留大小(所有现有堆快照工具使用的标准度量),它只考虑对象唯一拥有的通过泄漏根,LeakShare正确分配泄漏的信用预览
对象之间的四个不同泄漏根,因为它们所有必须拆卸以消除泄漏。
诊断泄漏:凄凉接下来重新加载应用程序,并使用它的代理透明地重写页面上的所有JavaScript,将堆中隐藏的边作为对象属性公开。凄凉使用JavaScript反射检测已识别的泄漏根,以捕获堆栈跟踪当他们长大而且当它们被覆盖时(不仅仅是它们的分配)。有了这个工具,凄凉使用开发人员提供的脚本来运行循环的最后一次迭代,以收集堆栈跟踪。这些堆栈跟踪直接指向导致泄漏增长的代码。
输出:最后,BLEAK输出其诊断报告:泄漏根的排序列表(按LeakShare排序),以及保留它们的堆路径和负责它们增长的堆栈跟踪。图3 b显示Firefox调试器的凄凉输出的一个片段,它直接指向造成内存泄漏的代码图1.有了这些信息,我们能够快速开发一个修复程序,在用户关闭文档时删除事件监听器。此修复已被合并到调试器的最新版本中。
本节正式描述了用于检测(第4.1节)、诊断(第4.2节)和泄漏排序(第4.3节)的黯淡核心算法的操作。
4.1.内存泄漏检测
凄惨的内存泄漏检测算法的输入是在同一可视状态下收集的一组堆快照,输出是一组路径从遍布所有快照的GC根。我们称之为路径泄漏的根源。萧尔认为这是一条道路日益增长的如果该路径上的对象有比它在前一个快照中更多的输出引用。为了使算法易于处理,凄凉只考虑到每个特定堆项的最短路径。
每个堆快照都包含一个堆图G= (N,E)和一组节点N表示堆和边中的项E其中每条边(n1,n2,l)∈E表示来自节点的引用n1来n2与标签l一个标签l包含边的类型和名称的元组。每条边的类型都是a闭包变量或者一个对象属性。边的名称对应于闭包变量或对象属性的名称。例如,对象O = {foo: 3}
有优势e从O到带标签的数字3l= (财产、“foo”)。一个路径P是一个简单的边列表(e1,e2、……en),e1从根节点(G.root).2
对于第一个堆快照,渺茫保守地将每个节点标记为增长。对于后续的快照,会运行图4)将增长标志从前一个快照传播到新快照,并丢弃前一个快照。在第2行,PROPAGATEGROWTH将新图中的每个节点初始化为没有成长为了防止在算法的下一个运行中将新的增长错误地标记为增长。由于算法只考虑到特定节点的最短路径,因此它能够将增长信息与终端节点关联起来,终端节点表示堆中的特定路径。
图4。传播节点的生长状态(n.growing)之间的堆快照。如果路径上的节点不断增加其输出边的数量,则凄凉认为堆中的路径是不断增长的。
PROPAGATEGROWTH在两个图中的共享路径上执行宽度优先遍历,从包含全局作用域(窗口
)和DOM。该算法将新图中的节点标记为日益增长的如果前一个图中相同路径上的节点既在增长,又有更少的输出边(第8行)。因此,算法只会将一个堆路径标记为泄漏根,前提是它在每个快照之间持续增长,并且从第一个快照开始就存在。
PROPAGATEGROWTH只访问两个图之间共享的路径(第11行)。在给定的路径上,算法考虑一条出方向的边en在旧的图中e”n在新图中,如果它们有相同的标签。换句话说,这些边必须对应于该路径上对象的相同属性名,或者对应于该路径上函数捕获的具有相同名称的闭包变量。
在将增长标志传播到最后的堆快照之后,萧山运行FINDLEAKPATHS (图5)来记录堆中不断增长的路径。这个遍历访问边缘捕获到指向增长节点的所有唯一边的最短路径。例如,如果一个正在生长的物体O位于窗口。O
作为变量p在函数中window.L.z
, FINDLEAKPATHS将报告两个路径。这个属性对于诊断泄漏非常重要,我们将在第4.2节中讨论。
图5。FINDLEAKPATHS,它返回通过堆到泄漏节点的路径。该算法将每条路径编码为由元组(t).
凄凉接受FINDLEAKPATHS的输出,并按每个路径的终端节点对其进行分组。每个组对应于一个特定的泄漏根。这组泄漏根构成了排序算法的输入。
4.2.诊断泄漏
给定泄漏根的列表以及每个根的指向根的堆路径列表,当应用程序执行以下操作时,会通过运行的钩子来诊断泄漏:
凄凉运行应用程序的一个循环迭代,所有的钩子都已安装。此过程生成负责增长每个泄漏根的堆栈跟踪列表。
4.3.泄漏根级别
萧尔采用了一种新的度量标准,根据投资回报率对泄漏根源进行排名LeakShare。LeakShare将保留共享泄漏对象的“功劳”平均分配给保留共享泄漏对象的泄漏根,从而对那些以最少的努力释放最多内存的内存泄漏进行优先级排序。
LeakShare首先标记通过止于泄漏根的广度优先遍历可从非泄漏访问的堆中的所有项。后续遍历将忽略这些节点。然后,LeakShare从每个泄漏根执行宽度优先遍历,在所有可达节点上增加一个计数器。一旦这个过程完成,每个节点都有一个计数器,其中包含可以到达它的泄漏根的数量。最后,该算法计算每个泄漏根的LeakShare,方法是将每个可达节点的大小除以其计数器,这将在所有可以到达该节点的泄漏根中分配该节点的“信用”。我们的PLDI论文给出了LeakShare的完整算法。15
凄凉由三个主要组件组成,它们一起工作来自动调试内存泄漏(参见图6):(1)驱动程序编排泄漏调试过程;(2)代理透明地在目标web应用程序上实时执行代码重写;并且(3)应用程序中嵌入的代理脚本公开用于泄漏检测的隐藏状态和用于泄漏诊断的增长事件。我们在这里简要描述这些组件是如何工作的;我们的PLDI文件提供了进一步的细节。15
图6。黯淡系统概述。白色项是凄凉组件,灰色项是代理在泄漏诊断期间重写的,黑色项是未修改的。
为了启动泄漏调试,凄凉驱动程序启动凄凉的代理和谷歌Chrome浏览器,其中有一个空缓存、一个新的用户配置文件和一个使用凄凉代理的配置。驱动程序通过标准的Chrome DevTools协议连接到浏览器,导航到目标web应用程序,并使用开发人员提供的配置文件在一个循环中驱动应用程序。在每次重复访问循环中的第一个可视状态时,驱动程序通过远程调试协议获取堆快照并运行PROPAGATEGROWTH (图4)在堆快照之间传播增长信息。
在可配置的循环迭代次数(默认为8)结束时,驱动程序切换到诊断模式。驱动程序运行FINDLEAKPATHS来定位所有泄漏根的所有路径(图5),配置代理来执行代码重写以进行诊断,并重新加载页面以拉入转换后的web应用程序版本。驱动程序在单个循环迭代中运行应用程序,然后触发凄凉代理插入诊断钩子,在FINDLEAKPATHS报告的所有路径上收集堆栈跟踪。然后,在从代理检索堆栈跟踪之前,驱动程序在最后一个循环中运行应用程序。最后,驱动程序运行LeakShare(章节4.3)对泄漏根进行排序,并生成一个内存泄漏报告。
我们通过在生产web应用程序上运行来评估凄凉。我们的评估针对以下问题:
我们的评估发现59个不同的内存泄漏在五个网络应用中,所有这些都是应用程序开发人员所不知道的。其中,27个是已知但未修复的JavaScript库依赖内存泄漏,其中只有6个是独立诊断的,并有等待修复的补丁。我们向相关开发人员报告了所有32个新的内存泄漏以及我们的修复;16个现在已经修复,4个在代码评审中已经修复。我们在一些流行的应用程序和库中发现了新的漏洞,包括Airbnb、Angular JS (1.x)、谷歌Maps SDK、谷歌Tag Manager和谷歌Analytics。
我们在每个web应用程序上通过特定的可视状态运行了8个往返,以生成一个凄凉泄漏报告,如图所示图3 b.我们只使用17-73 LOC来描述这些循环。在我们的评估机器(一台配备2.9GHz英特尔酷睿i5和16GB RAM的MacBook Pro)上,每个应用程序耗时不到15分钟。对于每个应用程序,我们分析报告的泄漏,为每个真正的泄漏编写修复,测量修复泄漏的影响,并将LeakShare与其他排名指标进行比较。
6.1.应用程序
因为目前还没有web应用程序内存泄漏检测的基准库,所以我们创建了一个。我们的语料库由五个流行的web应用程序组成,它们都包含大量的代码库,而且它们的总体内存使用量似乎在随着时间的推移而增长。我们主要关注开源web应用程序,因为它更容易为原始源代码开发修复程序;这代表了开发人员的正常用例。我们还包括一个封闭源码网站,Airbnb,以证明萧伯纳的能力诊断网站的生产。我们展示了每个web应用程序,突出显示了它们使用的库的选择,并描述了我们在评估中使用的视觉状态循环:
Airbnb:Airbnb是一个提供短期租赁和其他服务的网站,它使用React、谷歌地图SDK、谷歌分析、Criteo OneTag Loader和谷歌标签管理器。阴郁在书页间循环/ /所有
上面列出了Airbnb上提供的所有服务/ /家庭
该网站只列出了待租房屋和房间。
Piwik 3.0.2:广泛使用的开源分析平台;我们在显示分析结果的浏览器仪表板上运行惨淡。仪表板主要使用jQuery和AngularJS。凄惨反复访问主仪表板页面,显示一个小部件网格。
Loomio 1.8.66:一个开源的团队决策协作平台。Loomio使用AngularJS, LokiJS和谷歌标签管理器。在组页面和该页面上列出的第一个线程之间循环运行Loomio,该组页面列出该组中的所有线程。
Mailpile vl.0.0:开源邮件客户端。Mailpile使用jQuery。萧山以循环的方式运行Mailpile的演示,访问收件箱和收件箱中的前四封邮件。
Firefox调试器(提交91f5c63):一个用React编写的开源JavaScript调试器,可以在任何web浏览器中运行。当调试器附加到运行Mozilla SensorWeb的Firefox实例时,我们运行它。凄凉在一个循环中运行调试器,打开和关闭SensorWeb的main.js
在调试器的文本编辑器中。
6.2.精度和准确性
为了确定BLEAK的泄漏检测精度及其诊断的准确性,我们手动检查最终报告中每个由blek报告的泄漏,以确认(1)它无限制地增长,以及(2)堆栈跟踪正确地报告了导致增长的代码。图8总结我们的结果。
凄惨的平均精度为96.8%,中位数精度为100%在我们的评估申请上。只有三个假阳性。它们都指向一个持续增长的对象,直到某个阈值或超时发生;使用荒凉的开发人员可以通过增加往返次数来避免这些误报。三个误报中的两个实际上是位于谷歌标签管理器JavaScript库中的相同对象。
除了一个例外,凄凉准确地识别了所有真正泄露的代码。凄凉报告堆栈跟踪,直接识别出负责每个泄漏的代码。如果多个独立的源位置产生相同的泄漏根,则凄凉报告所有相关的源位置。对于一个特定的内存泄漏,凄凉无法记录堆栈跟踪。根据凄惨的泄漏报告,我们能够修复每一个内存泄漏。修复每个内存泄漏大约需要15分钟。
6.3.泄漏的影响
为了确定黯淡报告的内存泄漏的影响,我们在10次循环迭代中测量每个应用程序的实时堆大小,无论是否使用了我们的修复。我们使用荒凉的HTTP/HTTPS代理直接将内存泄漏修复程序注入到应用程序中,这让我们可以在像Airbnb这样的闭源网站上测试修复程序。除了Airbnb,我们在每个配置中运行每个应用程序5次(由于6.4节讨论的原因,我们在每个配置中只运行Airbnb一次)。
为了计算泄漏对整体堆增长的综合影响,我们计算有和没有修复的循环迭代之间的平均活堆增长,并取其差值(growth Reduction)。对于这个指标,我们忽略了前5个循环迭代,因为由于应用程序启动,这些循环迭代是有噪声的。图7而且8展示结果。
图7。修复使用黯淡工具发现的内存泄漏的影响:图在往返过程中显示实时堆大小;误差条表示95%置信区间。修复报告的泄漏平均可以消除93%的堆增长。
图8。cold可以精确地找到有影响的内存泄漏:平均而言,cold可以以95%以上的精度找到这些泄漏,修复它们可以消除90%以上的堆增长。
平均而言,修复黯淡报告中的内存泄漏可以消除93%以上的堆增长根据我们的基准(中位数:98.2%)。这些结果表明,荒凉模型没有遗漏任何有显著影响的泄漏。
6.4.LeakShare有效性
我们将LeakShare与两个可选的排名指标进行比较:保留大小和传递闭包大小。保留大小对应于如果从堆图中删除泄漏根,垃圾收集器将回收的内存量,并且是标准堆快照查看器显示给开发人员的度量。泄漏根的传递闭包大小是Xu等人使用的从泄漏根可访问的所有对象的大小。16由于JavaScript堆是高度连接的,并且经常包含对全局作用域的引用,因此我们期望该指标对大多数泄漏报告类似的值。
在按顺序修复每个内存泄漏之后,我们通过计算未进行修复的应用程序的增长减少(如6.3节所述)来度量每个排序度量的有效性。然后计算该数据的四分位数,指出在修复按给定指标排名的前25%、50%和75%的内存泄漏后,消除了多少堆增长。我们试图为每次修复单个泄漏根源的每个评估应用程序编写补丁,但这在所有情况下都是不可行的;有些泄漏具有相同的根本原因。在这些情况下,我们在对报告的第一个相关泄漏根进行排序期间应用补丁。
除了Airbnb之外,我们对每个应用程序都运行了10次循环迭代,其中5次是针对每个指标的独特组合和需要修复的顶级泄漏根源的数量。当多个指标报告相同的排名时,我们避免运行重复配置。评估Airbnb具有挑战性,因为它有30个泄漏根,在运行之间随机执行A/B测试,并定期更新其精简的代码库,以破坏我们的内存泄漏修复。因此,我们只能为Airbnb的每个独特配置收集一次数据。图9显示结果。
图9。排名指标的性能:在修复四分之一的顶级泄漏后,增长指标下降。加粗表示最大降幅(±1%)。我们忽略了Firefox;它只有4个漏洞,必须全部修复(见第2节)。LeakShare通常优于或匹配其他指标。
在大多数情况下,LeakShare的表现优于或与其他指标持平。LeakShare最初的表现优于Airbnb和Loomio上的其他指标,因为它优先考虑与其他泄漏根共享重要状态的泄漏根。保留大小总是优先考虑那些唯一拥有最多状态的泄漏根,这些泄漏根在短期内提供了最大的增长减少。LeakShare最终超过了这两个应用程序上的其他指标,因为它修复了保留共享状态的最终泄漏根。
Web应用程序内存泄漏检测器:黯淡自动调试内存泄漏在web应用程序;过去在这方面的工作是无效的或不够普遍。LeakSpot定位分配和参考站点,这些站点会随着时间的推移产生和保留不断增加的对象数量,并使用陈旧性作为启发式来优化其输出。14在实际的应用程序中,LeakSpot通常报告50多个不同的分配和参考站点,开发人员必须手动检查这些站点以识别和诊断内存泄漏。JSWhiz静态分析使用谷歌闭包类型注释编写的代码,以检测特定的泄漏模式。13
Web应用程序内存调试:一些工具帮助web开发人员调试内存使用情况,并提供诊断信息,开发人员必须手动解释以定位泄漏(第2节描述谷歌Chrome的DevTools)。MemInsight总结并显示关于JavaScript堆的信息,包括每个对象类型的过期信息、对象的分配位置以及在堆中保留路径。7与凄凉不同的是,这些工具不直接识别内存泄漏或识别导致泄漏的代码。
基于增长的内存泄漏检测:LeakBot在Java应用程序的堆图中查找模式,以查找内存泄漏。9LeakBot假设泄漏根拥有所有泄漏对象,但web应用程序中的泄漏对象通常有多个所有者。凄凉不依赖于特定的模式,而是使用到相同可视状态的往返行程来识别泄漏对象。
基于过时的内存泄漏检测:SWAT (C/ c++)、Sleigh (JVM)和Hound (C/ c++)使用从对象最后一次被访问派生的过期度度量来查找泄漏对象,并确定负责分配这些对象的调用站点。6,2,12Leakpoint (C/ c++)还标识执行中引用泄漏内存位置的最后一个点。3.Xu等人使用一种混合方法来识别源自Java集合的泄漏,这种方法针对的是随着时间推移而增大的容器,其中包含过期的项目。正如我们在PLDI论文中所讨论的那样,陈旧对至少77%的内存泄漏无效。15
本文介绍了凄惨的,第一个有效的系统调试客户端内存泄漏在web应用程序。我们表明,渺茫具有较高的精度,并在web应用程序和库中发现了许多以前未知的内存泄漏。凄凉是开放源码的,可在以下网址下载http://bleak-detector.org/.
约翰·维尔克得到了Facebook博士奖学金的支持。本材料基于国家科学基金会1637536号拨款支持的工作。本材料中表达的任何意见、发现和结论或建议都是作者的观点,并不一定反映美国国家科学基金会的观点。
1.修复记忆问题,2017年。https://developers.google.com/web/tools/chrome-devtools/memory-problems/.
2.邦德,医学博士,麦金利,K.S.贝尔:位编码在线内存泄漏检测。在ASPLOS, ACM,圣何塞,加利福尼亚州,2006,61-72。
3.子句,j.a., Orso, A. LEAKPOINT:精确定位内存泄漏的原因。在ICSE, ACM,开普敦,南非,2010,515-524。
4.谷歌。加速谷歌chrome, 2017。https://support.google.com/chrome/answer/1385029.
5.原,K.油锅:GC为眨眼,2013。https://docs.google.com/presentation/d/1YtfurcyKFS0hxP0nC3U6JJroM8aRP49Yf0QWznZ9jrk.
6.Hauswirth, M, Chilimbi, t.m。使用自适应统计分析的低开销内存泄漏检测。在ASPLOS, ACM,波士顿,马萨诸塞州,2004,156-164。
7.Jensen, s.h., Sridharan, M., Sen, K., Chandra, S. MemInsight: JavaScript的平台无关内存调试。在工程师协会,意大利,贝加莫,2015,345-356。
8.Lee, L, Hundt, R. BloatBusters:在Gmail中消除内存泄漏,2012。https://docs.google.com/presentation/d/lwUVmf78gG-ra5aOxvTfYdiLkdGaR9OhXRnOlIcEmu2s.
9.LeakBot:用于诊断大型Java应用程序中的内存泄漏的自动化轻量级工具。在ECOOP, 2003, 351-377。
10.Mozilla。关于:记忆,2017。https://developer.mozilla.org/en-US/docs/Mozilla/Performance/about:memory.
11.有史以来最好的firefox, 2017年。https://blog.mozilla.org/blog/2017/06/13/faster-better-firefox/.
12.Novark, G., Berger, e.d., Zorn, B.G.有效和精确地定位内存泄漏和膨胀。在PLDI,都柏林,爱尔兰,2009,397-407。
13.Pienaar, J.A, Hundt, R. JSWhiz: JavaScript内存泄漏的静态分析。在CGO, IEEE计算机学会,深圳,2013,11:1-11:11。
14.泄漏点:检测和诊断javascript应用程序中的内存泄漏。Softw。Pract。Exp。1, 47(2017), 97-123。
15.维尔克,J.伯格,E.D.黯淡:自动调试web应用程序中的内存泄漏。在PLDI,费城,宾夕法尼亚州,2018,15-29。
16.许国华,朗特夫。基于容器分析的Java软件精确内存泄漏检测。TOSEM 3, 22(2013): 17:1-17:28。
1.https://issuetracker.google.com/issues/35821412.
本文的原始版本出现在三十九人会议记录thACM SIGPLAN编程语言设计与实现会议(2018年6月18-22日,美国宾夕法尼亚州费城),15-29日。
数字图书馆是由计算机协会出版的。版权所有©2020 ACM, Inc.
没有找到条目