【问题标题】:Stack vs. Heap in Javascript? (Maximum call stack size exceeded)Javascript中的堆栈与堆? (超出最大调用堆栈大小)
【发布时间】:2012-06-26 22:47:35
【问题描述】:

我正在尝试构建一个网页,我需要在 JavaScript 中铲出大约 100MB 的数据。使用不同的浏览器时,我会在不同的数据量下遇到“超出最大调用堆栈大小”错误。

我是否可以通过检查我的代码并尝试将函数内的局部变量移动到更全局的范围以尝试将它们分配到堆而不是堆栈上来解决此问题?还是这些概念在 JavaScript 中不存在? (据我所知,我的数据中没有任何主要的递归循环,所以确实是几个巨大的字符串/数字数组似乎导致了错误)

如果这不可能,有没有办法让浏览器保留更多内存?

【问题讨论】:

  • 你还没有理解你所看到的。发生的事情是你有一个递归函数,即一个调用自身的函数(或调用另一个调用第一个函数的函数)可能是偶然的。
  • 您究竟是如何传输(铲除:)所说的 100MB 数据的?您使用的是什么服务器端技术?
  • 如果没有您的代码,它将永远是个谜!没有办法回答它。一切都将是猜测。如果没有有问题的代码,您就没有问题。
  • 好吧,现在我 150% 确定这不是递归问题(见下文)。但它是一个有点奇怪的机制导致了这个问题,所以在 99.99% 的情况下你可能是对的,这将是一个递归问题,只是这次不是。

标签: javascript memory-management heap-memory stack-memory


【解决方案1】:

好的,找到问题了。我的代码中确实没有递归。如果 JavaScript 函数是“可变参数”函数,例如 <array>.splice(...),那么确实可以调用具有数百个参数的 JavaScript 函数。

旁白:GWT 使用 JavaScript 拼接函数以或多或少聪明的方式实现了 Java 函数 System.arraycopy(...)

splice 接受任意数量的输入元素以插入到目标数组中。可以使用以下构造从另一个数组传递这些输入元素:

var arguments = [index, howmany].concat(elements);
Arrays.prototype.splice.apply(targetarray, arguments);

这相当于调用:

targetarray.splice(index, howmany, elements[0], elements[1], elements[2], ...);

如果 元素 变大(请参阅下文,了解“大”对于不同浏览器的含义),您可能会收到“超出最大调用堆栈大小”错误 没有递归,因为它的内容将被加载到函数调用的堆栈中。

这是一个演示此问题的简短脚本:

var elements = new Array();
for (i=0; i<126000; i++) elements[i] = 1;
try {
    var arguments = [0, 0].concat(elements);
    Array.prototype.splice.apply(elements, arguments);
    alert("OK");
} catch (err) {
    alert(err.message);
}

使用此脚本,“大”意味着以下内容:

  • Chrome 19:元素包含约 125,000 个数字
  • Safari 5.1(在 Windows 上):元素包含约 65,000 个数字
  • Firefox 12:元素包含约 500,000 个数字
  • Opera 11.61:元素包含约 1,000,000 个数字

赢家是:Internet Explorer 8 带来了改变!在此函数调用失败之前,它可以耗尽所有系统内存。

附注:Firefox 和 Opera 实际上会抛出不同的(更有用的)错误消息:Function.prototype.apply: argArray is too large

【讨论】:

  • 哦,我知道这是可能的,但希望没人会尝试。
  • 有趣的是:谷歌实际上在 GWT 中标准化了这种方法...... :) 这可能是在 JavaScript 中实现 Java 的 System.arraycopy 的最快方法,但对于大型数组它会崩溃......而且,男孩,这个虫子是不是很让人头疼……
  • @ThomasBroyer Strange... 我还没有在 System.java 的最新源代码中看到修复:code.google.com/p/google-web-toolkit/source/browse/trunk/user/… 还是我查看了错误的存储库?但这是个好消息! :)
  • 我猜有两个存储库:GIT 和 SVN。 GIT 似乎是当前的版本,我正在查看 SVN 版本...可以通过 Thomas 的链接访问 GIT 存储库(D'Uh!)...
【解决方案2】:

在 Javascript 中没有将内存分成堆栈/堆。您看到的可能是以下之一:

  1. 递归运行太深。在这种情况下,您需要检查您的算法,使其更具迭代性并使用更少的递归,这样您就不会遇到浏览器施加的调用堆栈限制。
  2. 如果您的算法没有深度递归,考虑到您的代码已生成,这可能仍然只是一个足够深的调用。
  3. 最后,一些引擎可能会在某种内部堆栈上分配函数参数和作用域命名变量,以便快速查找。如果您(或自动生成的代码)碰巧在函数中使用了数千个局部变量或参数,这也可能会超出引擎特定的限制。

【讨论】:

  • 我确定这不是递归问题(请参阅对原始问题的编辑)。
  • 无论数组多大,函数调用中的值都只是指向这个数组的指针。除非您的函数从字面上接受数百个参数,否则这仍然与内存无关,因为数组的内容不会仅仅因为您在调用中引用它而移动到其他引擎特定类型的内存。
  • 你是对的,至少部分是对的。我的函数实际上确实接受了数百个参数(实际上是数百万个)。有关详细信息,请参见下文。但这确实意味着对于传递给函数调用的参数,至少对于 Safari 和 Chrome 而言,将内存分为堆栈/堆。
  • @Markus,老实说,我不想是正确的。我希望没有任何代码会遇到这个问题。将此从评论添加到答案。
  • 我将您的答案标记为最有用的答案,因为我的答案太罗嗦,无法提供帮助。你是一个很好的总结要寻找的东西。谢谢。 :)
猜你喜欢
  • 2019-05-21
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 2015-12-29
  • 2017-12-27
  • 2020-12-06
  • 2013-08-23
  • 2022-10-08
相关资源
最近更新 更多