如何获取JavaScript对象的大小?

437

我想知道JavaScript对象所占用的大小。

请看以下函数:

function Marks(){
  this.maxMarks = 100;
}

function Student(){
  this.firstName = "firstName";
  this.lastName = "lastName";
  this.marks = new Marks();
}

现在我实例化student

var stud = new Student();

这样我就可以做类似以下的事情

stud.firstName = "new Firstname";

alert(stud.firstName);

stud.marks.maxMarks = 200;

现在,stud对象将占用一些内存空间。它有一些数据和更多的对象。

我该如何找出stud对象占用了多少内存?类似于JavaScript中的sizeof()吗?如果能够像sizeof(stud)这样进行单个函数调用来查找,那就太棒了。

我已经搜索了几个月的互联网 - 找不到答案(在一些论坛上发过帖子 - 没有回复)。


关于这个问题提出的答案怎么样:https://dev59.com/qm855IYBdhLWcg3wXC5- - csturtz
1
另一个内存近似函数在Github上(https://gist.github.com/zensh/4975495)。 - Rafael Emshoff
22个回答

232
我已经对我的原始答案中的代码进行了重构。我去掉了递归,并消除了假设存在的开销。这是一个新的编辑,使其在风格上更加现代化。
function roughSizeOfObject(object) {
  const objectList = [];
  const stack = [object];
  let bytes = 0;

  while (stack.length) {
    const value = stack.pop();

    switch (typeof value) {
      case 'boolean':
        bytes += 4;
        break;
      case 'string':
        bytes += value.length * 2;
        break;
      case 'number':
        bytes += 8;
        break;
      case 'object':
        if (!objectList.includes(value)) {
          objectList.push(value);
          for (const prop in value) {
            if (value.hasOwnProperty(prop)) {
              stack.push(value[prop]);
            }
          }
        }
        break;
    }
  }

  return bytes;
}

51
你可以考虑一下对象键的问题。 - zupa
10
任何想要查找最小类型以进行真/假目的的人,似乎都是未定义/空值。 - zupa
3
在JavaScript中,"よんもじ".length的长度为4,但你确定它是8个字节吗?因为你的代码返回了这个值。 - syockit
10
没问题。JavaScript 中的字符是根据 ECMA-262 第三版规范存储的 - http://bclary.com/2004/11/07/#a-4.3.16 - thomas-peter
7
该函数不会计算闭包中隐藏的引用。例如 var a={n:1}; var b={a:function(){return a}}; roughSizeOfObject(b)——在这里,b 持有对 a 的引用,但 roughSizeOfObject() 返回 0 - Roman Pominov
显示剩余26条评论

165

Google Chrome堆分析器可以让你检查对象的内存使用情况。

你需要能够在追踪记录中找到该对象,这可能有些棘手。如果你将对象固定到窗口全局对象(Window global),则可以从“包含”列表模式中很容易地找到它。

在附加的屏幕截图中,我在窗口上创建了一个名为“testObj”的对象。然后,在记录一段时间后,在分析器中找到了它,并且显示了对象的完整大小和其中所有内容的“保留大小”。

更多关于内存分解的细节请参考此链接.

Chrome profiler

在上面的屏幕截图中,该对象的保留大小为60。我相信这里的单位是字节(bytes)。


18
这个答案以及这里提供的链接:https://developers.google.com/chrome-developer-tools/docs/heap-profiling 帮我解决了问题。快速提示:先拍一个快照,运行你怀疑可能存在泄漏的任务,再拍一个新的快照,并在底部选择“比较”视图。这会清楚地显示出两个快照之间创建的对象。 - Johnride
1
比较,由@Johnride提到,现在是顶部的下拉菜单。 - frandroid
“Shallow size” 对于 { a:"55c2067aee27593c03b7acbe", b:"55c2067aee27593c03b7acbe", c:null, d:undefined }{ c:null, d:undefined } 对象都是40。这样可以吗? - efkan
3
您可以在Node中使用Google Chrome Heap Profiler。如果您的Node版本是v8或更高版本,请使用node --inspect启动它,在Chrome中输入about:inspect并查找打开Node Inspector。在node CLI中创建您的对象,然后进行堆快照。 - Gregor

95

有时我会使用这个标记非常大的对象,这些对象可能会从服务器发送到客户端。它不代表内存占用情况。 它只能让您大致了解发送或存储此对象所需的成本。

同时请注意,它很慢,仅限于开发使用。但对于用一行代码获取近似答案而言,它对我很有用。

roughObjSize = JSON.stringify(bigObject).length;

2
从我的测试来看,这种方法比对象-sizeof情况要快得多,因为它没有来自lodash的缓慢_.isObject()调用。此外,返回的大小对于粗略估计来说相当可比。代码片段https://gist.github.com/owenallenaz/ff77fc98081708146495。 - Owen Allen
4
很遗憾,当对象太大时,它会崩溃。:( - cregox
7
无法使用带有循环结构的 VM1409:1 Uncaught TypeError: Converting circular structure to JSON :( 但仍然有用。 - givanse
当对象具有某种二进制信息,例如图像或位数组时,它可能会失败。 - Andrés
如果您有很多布尔值和数字,那么它不会是一个好的表示方式。当您进行 stringify 操作时,将数字和布尔值转换为字符串。在 C++ 中,布尔值占据 1 个字节(是的,是字节,不是位)。UTF-8 对于主题中字符谱中的每个字符都需要 1 个字节,因此字面意义上的 true 占用 4 个字节,而字面意义上的 false 则占用 5 个字节 - 如果您有数千个项,每个项都有一个布尔值,这是一个巨大的差异。 - Maciej Krawczyk
显示剩余3条评论

82

我刚刚写了这个来解决一个类似的问题。它并不完全符合你可能正在寻找的,也就是它没有考虑解释器如何存储对象。

但是,如果你使用的是V8,它应该会给你一个相当不错的近似值,因为强大的原型和隐藏类可以消耗大部分开销。

function roughSizeOfObject( object ) {

    var objectList = [];

    var recurse = function( value )
    {
        var bytes = 0;

        if ( typeof value === 'boolean' ) {
            bytes = 4;
        }
        else if ( typeof value === 'string' ) {
            bytes = value.length * 2;
        }
        else if ( typeof value === 'number' ) {
            bytes = 8;
        }
        else if
        (
            typeof value === 'object'
            && objectList.indexOf( value ) === -1
        )
        {
            objectList[ objectList.length ] = value;

            for( i in value ) {
                bytes+= 8; // an assumed existence overhead
                bytes+= recurse( value[i] )
            }
        }

        return bytes;
    }

    return recurse( object );
}

76

以下是稍微更紧凑的解决方案:

const typeSizes = {
  "undefined": () => 0,
  "boolean": () => 4,
  "number": () => 8,
  "string": item => 2 * item.length,
  "object": item => !item ? 0 : Object
    .keys(item)
    .reduce((total, key) => sizeOf(key) + sizeOf(item[key]) + total, 0)
};

const sizeOf = value => typeSizes[typeof value](value);

4
这是以KB为单位的尺寸吗?还是以比特为单位? - vincent thorpe
2
@vincent-thorpe 这是以字节为单位的。 - Dan
4
不错的脚本,但需要修改以处理循环引用。 - Jim Pedid
2
我刚刚在一个节点进程中对您的算法进行了大量数据的测试,它报告了13GB,但是节点正在消耗22GB,您有什么想法这个差异来自哪里?内存中没有其他内容。 - Josu Goñi
7
@JosuGoñi,他们不计算物体本身占据的空间,只计算它们的价值。所有物体都占据比它们的价值更多的空间,否则 typeof ... 将无法工作。 - Alexis Wilke
显示剩余2条评论

61

有一个NPM模块可获取对象大小,您可以使用npm install object-sizeof安装它。

  var sizeof = require('object-sizeof');

  // 2B per character, 6 chars total => 12B
  console.log(sizeof({abc: 'def'}));

  // 8B for Number => 8B
  console.log(sizeof(12345));

  var param = { 
    'a': 1, 
    'b': 2, 
    'c': {
      'd': 4
    }
  };
  // 4 one two-bytes char strings and 3 eighth-bytes numbers => 32B
  console.log(sizeof(param));

1
sizeof(new Date()) === 0 and sizeof({}) === 0. 这是有意为之吗? - Philipp Claßen
1
@PhilippClaßen 显然是的。这两个对象都没有属性。 - Robert
8
这似乎不太有用。它提供了一些类似于 C 语言 sizeof 语义的理论数字,但并非实际使用的内存量。对象本身会占用空间(因此 sizeof(new Date()) 应大于 0),而 JS 引擎通过去重字符串并在可能时将其存储为单字节编码来节省内存。 - ZachB
但是一个空对象仍然占用内存空间。它至少有一个指向其原型的引用(__proto__ 隐式属性),可能还有其他我不知道的引用。而且,在 Date 对象的情况下,它至少有一个指向它所指代的时间点的引用。sizeof 无法读取它的属性,因为 Date 是用本地代码编写的。此外,sizeof 使用 for ... in 循环来读取对象属性,因此它也不计算符号属性和私有字段。 - Leonardo Raele
这个库下载了30秒,结果甚至都没能正常工作。 - ICW

18

这是一个不太正规的方法,但我试过两次,使用不同的数字,似乎结果很一致。

你可以尝试分配一个巨大数量的对象,比如一百万或两百万个所需类型的对象。将对象放入数组中以防止垃圾回收器释放它们(请注意,这将因为数组而增加一些内存开销,但我希望这不会产生影响,另外如果你担心对象在内存中的存储,那么你需要把它们存储在别处)。在分配之前和之后添加一个警告,并在每个警告中检查Firefox进程占用了多少内存。在打开测试页面之前,请确保您有一个新的Firefox实例。打开页面,在“before”警报显示后注意内存使用情况。关闭警报,等待分配内存。从旧内存减去新内存并除以分配数量即可得到每个对象的内存大小。例如:

function Marks()
{
  this.maxMarks = 100;
}

function Student()
{
  this.firstName = "firstName";
  this.lastName = "lastName";
  this.marks = new Marks();
}

var manyObjects = new Array();
alert('before');
for (var i=0; i<2000000; i++)
    manyObjects[i] = new Student();
alert('after');

我在我的电脑上尝试了这个过程,并且在"before"警报出现时,进程占用了48352K的内存。分配后,Firefox占用了440236K的内存。对于200万个分配,每个对象约为200字节。

我尝试了1百万次分配,结果也类似:每个对象196字节(我想2百万中额外的数据用于数组)。

因此,这是一个可能有用的hacky方法。 JavaScript没有提供“sizeof”方法的原因是:每个JavaScript实现都不同。例如,在Google Chrome中,同一页面每个对象大约使用66字节(至少从任务管理器判断)。


嘿,谢谢你的技巧。如果没有直接的方法来测量内存使用情况,我会把它作为备选方案。 - anonymous
4
每个 C 和 C++ 实现也是不同的。;) 在 C 或 C++ 中,数据类型的大小是与实现相关的。我认为 JavaScript 没有理由不支持这样的运算符,尽管它的作用和意义与 C 或 C++ 中不同(它们是低级语言,在编译时测量固定大小数据类型的实际大小,而不是在运行时测量动态 JavaScript 对象的变量大小)。 - bambams

14

抱歉我无法评论,所以我只能继续tomwrong的工作。 这个增强版将不会重复计算对象,因此避免了无限循环的问题。 另外,我认为对象的键也应该被计算在内,大概是这样。

function roughSizeOfObject( value, level ) {
    if(level == undefined) level = 0;
    var bytes = 0;

    if ( typeof value === 'boolean' ) {
        bytes = 4;
    }
    else if ( typeof value === 'string' ) {
        bytes = value.length * 2;
    }
    else if ( typeof value === 'number' ) {
        bytes = 8;
    }
    else if ( typeof value === 'object' ) {
        if(value['__visited__']) return 0;
        value['__visited__'] = 1;
        for( i in value ) {
            bytes += i.length * 2;
            bytes+= 8; // an assumed existence overhead
            bytes+= roughSizeOfObject( value[i], 1 )
        }
    }

    if(level == 0){
        clear__visited__(value);
    }
    return bytes;
}

function clear__visited__(value){
    if(typeof value == 'object'){
        delete value['__visited__'];
        for(var i in value){
            clear__visited__(value[i]);
        }
    }
}

roughSizeOfObject(a);

我认为这更准确,因为它在计算键时,尽管它也会计算“_visited_”键。 - Sam Hasler
仅仅检查 typeof value === 'object' 是不够的,如果值为 null,你会得到异常。 - floribon
这对于我的对象来说非常快(我相信它远远超过5mb),与@tomwrong的任何重复答案相比。它也更准确(因为它显示了大约3mb),但仍然远离现实。有关于它可能没有计算的任何线索吗? - cregox
对我不起作用。对象level包含数据,但是roughSizeOfObject(level)返回零。(当然,我的变量level不应与您的参数混淆。我认为变量遮蔽在这里不应该引起问题,而且当我在您的脚本中重命名level时,我得到了相同的结果。)屏幕截图:https://snipboard.io/G7E5yj.jpg - Luc
这太棒了,但我不得不注意空值来让它正常工作。谢谢你。 - Daniel Lefebvre
这真是太棒了,但是为了让它正常工作,我确实不得不处理空值的情况。谢谢你。 - undefined

13

遇到同样的问题。我在 Google 上搜索并想与 stackoverflow 社区分享此解决方案。

重要提示:

我使用了 Yan Qing 在 Github 上共享的函数 https://gist.github.com/zensh/4975495

function memorySizeOf(obj) {
    var bytes = 0;

    function sizeOf(obj) {
        if(obj !== null && obj !== undefined) {
            switch(typeof obj) {
            case 'number':
                bytes += 8;
                break;
            case 'string':
                bytes += obj.length * 2;
                break;
            case 'boolean':
                bytes += 4;
                break;
            case 'object':
                var objClass = Object.prototype.toString.call(obj).slice(8, -1);
                if(objClass === 'Object' || objClass === 'Array') {
                    for(var key in obj) {
                        if(!obj.hasOwnProperty(key)) continue;
                        sizeOf(obj[key]);
                    }
                } else bytes += obj.toString().length * 2;
                break;
            }
        }
        return bytes;
    };

    function formatByteSize(bytes) {
        if(bytes < 1024) return bytes + " bytes";
        else if(bytes < 1048576) return(bytes / 1024).toFixed(3) + " KiB";
        else if(bytes < 1073741824) return(bytes / 1048576).toFixed(3) + " MiB";
        else return(bytes / 1073741824).toFixed(3) + " GiB";
    };

    return formatByteSize(sizeOf(obj));
};


var sizeOfStudentObject = memorySizeOf({Student: {firstName: 'firstName', lastName: 'lastName', marks: 10}});
console.log(sizeOfStudentObject);

你对此有何看法?


3
这个缺少功能。如果我添加一个功能,对象不会显示得更大。 - Don Rhummy
不计算键值 - ebg11

8
我想知道我的内存减少工作是否真的有助于减少内存。在这个评论的基础上,以下是你应该做的: 尝试制造一个内存问题-编写创建所有这些对象的代码,并逐渐增加上限,直到遇到问题(浏览器崩溃、浏览器冻结或内存不足错误)。理想情况下,您应该使用不同的浏览器和操作系统重复此实验。 现在有两个选择: 选项1-您没有成功制造内存问题。因此,您在担心什么。您没有内存问题,您的程序很好。 选项2-您确实遇到了内存问题。现在问问自己,问题发生的限制是否合理(换句话说:在正常使用代码时,可能会创建这么多的对象)。如果答案是“否”,那么您就没问题了。否则,您现在知道您的代码可以创建多少对象。重新设计算法,使其不违反此限制。

从内存角度来看,我的扩展程序为Firefox中打开的每个页面/选项卡添加了许多对象。这个“数量”与页面的大小成比例。假设“高级”用户在15-20个选项卡之间切换,并且如果网页有很多内容,则浏览器会在一段时间后变得缓慢和令人沮丧的无响应。即使我没有明确尝试压力测试应用程序,也会发生这种情况。我计划重写代码,我认为这将减少很多对象的创建。我只是想确保减少的对象数量达到一定程度,以便值得这样做。 - anonymous
@Senthil:但是对象大小没有意义,除非您知道可用内存的数量。由于内存量很可能仍然是个谜,因此以#对象的术语说话与以#字节的术语说话一样有用。 - Itay Maman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接