Java:数据结构内存估算

5
有没有人有各种数据结构的大致估算器列表?例如:

  • 数组
  • 列表
  • 哈希表
  • 链表

我记得在不同的地方看到过一些这样的估算,但现在好像找不到了。

我知道这实际上非常复杂,特别是对于像HashMaps这样的东西,但我正在寻找一些粗略的东西,比如:

Memory(HashMap) = fixedOverhead + variableOverhead * tableSize + A*numKeys + B*numValues + Memory(allKeys) + Memory(allValues)

当然,这将根据各种因素而有很大的差异,但即使是粗略估计,也会非常有用。


我知道我已经回答过那些问题好几次了,但我甚至找不到自己的帖子..这里是热点问题的非常简短、不完整的版本: 每个对象都有2个字节的开销,并且按8字节对齐。数组还需要额外的4字节来存储大小。引用大小取决于JVM位数,但在64位系统上,对于小于32GB的堆,存在压缩的oops。 - Voo
我想知道visualvm是否可以为您完成此操作...有一个内存分析器,但我从未使用过。 - Jonathan S. Fisher
我也没有找到你的答案 =D 如果你能找到一个旧的回答来解决这个问题,那就太棒了。 - Li Haoyi
编写一个shell脚本,根据参数1(数组、列表等)和第二个参数(元素数量1M、2M、4M),初始化其中一个,并在3个循环中调用此程序,迭代您的程序并减少-Xmx-Param,以找出下限。我希望集合的大小几乎相等,与集合类型无关。 - user unknown
5个回答

3

不错的发现!那正是我正在寻找的。 - Li Haoyi
1
这个可以帮助到你:http://www.slideshare.net/aszegedi/everything-i-ever-learned-about-jvm-performance-tuning-twitter - George
那就是我认为我看到的那个,但当我真正回去找它时,我从未成功地找到它。谢谢! - Li Haoyi
由于该链接没有提到他们正在谈论哪个JVM(这是一个严重的缺陷,应该非常令人不安),它可能是正确的。但是,如果我们谈论Hotspot,则给定的对象头已经过时了十年。他甚至没有在任何地方提到内存是8字节对齐的。该文章最多是可疑的...或者因为他是IBM的人,他正在谈论他们的JVM-我对那个JVM一无所知。 - Voo

2

0

这可能有点粗糙,但这些估计值应该是正确的。这些是裸骨数据结构,不包括长度变量或其他通常包含在Java中的额外内容。

其中dataType是存储的数据类型

Array: (length n)
    n*sizeOf(dataType)

LinkedList:
    n*(sizeOf(dataType)+sizeOf(pointer))+sizeOf(pointer[head pointer])

List: 
    Array-backed=SpaceEfficiency(Array)
    LinkedList-backed=SpaceEfficiency(LinkedList)

HashMap: with v values, k keys
    v*sizeOf(valueDataType)

Tree: k-way tree with n nodes
    n*(sizeOf(nodeDataType)+(k*sizeOf(pointer)))+sizeOf(pointer[head pointer])

Graph: e edges, v vertices
    AdjacencyList:
        at most: v*((v*sizeOf(vertexDataType))+(e*sizeOf(pointer))) fully connected graph
        at least: v*sizeOf(vertexDataType) disconnected graph
    AdjacencyMatrix:
        v^2*sizeOf(int)

这很有用,但我也知道如何在理论上分析这些东西 =)。我感兴趣的是常数:数组中每个项的开销是多少字节?链表中呢?HashMap中呢?固定开销是多少?我想要比这更详细一点的东西,这实际上是没有任何常数的渐近符号表示法。 - Li Haoyi
我对除了我们理论确定的内容以外的任何问题感到困惑。我的意思是,数组中不应该有任何每个项的开销,对吧?链表中每个项的开销只是指向下一个节点的指针,HashMap中的开销不存在(除了存储哈希算法代码的内存空间),因为它只是一个数组。你是在寻找比这更具体的东西吗?任何更具体的内容都是Java实现选择的结果。或者这就是你要找的东西吗?我发现这非常有趣:p - Drew
OP正在询问Java实现选择,@Mako。 - Louis Wasserman

0

这里有一个简单的程序,它只是消耗内存:

import java.util.*;
/**
    RamInit (c) GPLv3

    @author Stefan Wagner
    @date Do 22. Mär 08:40:40 CET 2012

*/
public class RamInit
{
    private java.lang.Object consumer; 

    public RamInit (char type, int size)
    {
        switch (type) 
        {
            case 'a': Integer [] ai = new Integer [size]; 
                for (int i = 0; i < size; ++i) 
                    ai[i] = i; 
                consumer = ai; 
                break;
            case 'l': List<Integer> li = new ArrayList<Integer> (); 
                for (int i = 0; i < size; ++i) 
                    li.add (i); 
                consumer = li;
                break;
            case 'h': HashMap <Integer, Integer> hm = new HashMap <Integer, Integer> (); 
                for (int i = 0; i < size; ++i) 
                    hm.put (i, size - i); 
                consumer = hm;
                break;
            case 'L': LinkedList <Integer> ll = new LinkedList <Integer> (); 
                for (int i = 0; i < size; ++i) 
                    ll.add (i);     
                consumer = ll;          
                break;
            default: System.err.println ("invalid: " + type);
        }
    }

    public static void main (String args[])
    {
        char type = 'a';
        int size = 1000000; // 1M
        if (args.length == 2)
        {
            type = args[0].charAt (0);
            size = Integer.parseInt (args[1]);
        }
        try {
            new RamInit (type, size);
        }
        catch (OutOfMemoryError oome)
        {
            System.exit (1);
        }
    }
}

这里有一个非常简单的脚本来测试它:

#!/bin/bash

iterProg () {
ram=$1
maxram=$2 
typ=$3
size=$4
# echo java -Xmx${ram}M RamInit $typ $((size*1000*1000)) 
echo -n "." 
java -Xmx${ram}M RamInit $typ $((size*1000*1000)) && echo -en "\n"$typ $size ${ram}M || { 
    if (($ram==$maxram))
    then
        # echo "fail" 
        return 
    else 
        iterProg $((ram+1)) $maxram $typ $size 
    fi
    }
}

# try from 16 MB to 256
for typ in {a,l,h,L}; do 
  for size in {1,2,4}; do 
    iterProg $((size*17+1)) 256 $typ $size 
  done
done

这是一个原始的迭代器,应该被更复杂的东西所取代 - 例如,如果你需要37MB来调用Collection a和1M元素的RamInit,那么你应该从超过2M个元素开始。

而且你应该选择二分搜索中的步骤,例如如果20M太少了,就检查128,然后(20+128)/2,然后根据下限或上限的成功或失败来计算平均值。

由于HashMap每个元素存储2个Ints,因此它的初始大小应该大约是List/Array/Vector的两倍。然而 - 时间飞逝,当写作时,结果已经完成:

bash iterRamFind.sh 
..
a 1 19M.....
a 2 39M...............
a 4 83M..
l 1 19M.......
l 2 41M.......................
l 4 91M..............................................
h 1 63M.............................................................................................
h 2 127M...........................................................................................................................................................................................
h 4 255M......................
L 1 39M.................................................
L 2 83M...............................................................................................
L 4 163

值为17的意义可以从最初的实验中解释清楚。正如我们所看到的,大小几乎成线性增长。

如果您使用Longs来检查影响,修改代码取决于您 - 我猜您最终将得出2的因素。


0

Infoq上,有一位Twitter员工的演讲:Pdf幻灯片、音频:mp3和视频。

它主要涉及JVM中集合和对象大小等细节。


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