为Hashtable.put()分配内存

5

我正在阅读Peter Norvig的IAQ(不经常问的问题-link),然后发现了这个:

您可能会惊讶地发现,在Sun JDK VM中,一个对象占用16个字节或4个字,这可以分解如下:有一个两个字的头,其中一个字是指向对象类的指针,另一个指向实例变量。即使Object没有实例变量,Java仍然为变量分配了一个字。最后,还有一个“句柄”,它是另一个指向两个字头的指针。Sun表示,这种额外的间接层使垃圾收集更简单。(至少15年来,已经有高性能的Lisp和Smalltalk垃圾收集器不使用额外的层次。我听说但尚未证实Microsoft JVM没有额外的间接层。)创建一个空的new String()需要40个字节或10个字:3个指针开销,3个用于实例变量(起始索引,结束索引和字符数组)的字,以及4个用于空字符数组的字。创建现有字符串的子字符串只需6个字,因为字符数组是共享的。将整数键和整数值放入Hashtable中需要64个字节(除了Hashtable数组中预先分配的4个字节之外):我会让您自己思考原因。
我显然尝试了,但是我想不出来。在下面的计数中,我只计算单词数:Hashtable put 创建一个 Hashtable$Entry:3(开销)+ 4 个变量(3 个引用,我假设每个引用占 1 个字 + 1 个 int)。我进一步假设他的意思是 Integer 是新分配的(因此不是由 Integer 类缓存或已存在),这样就会得到 2 *(3 [开销] + 1 [1 个 int 值])。所以最终我们得到..15 个单词或 60 字节。所以我最初想到的是 Entry 作为内部类需要引用其外部对象,但遗憾的是它是静态的,所以这没有太多意义(当然,我们必须存储指向父类的指针,但我认为该信息存储在 VM 的类头中)。只是好奇,我很清楚所有这些都取决于实际的 JVM 实现(在 64 位版本上,结果将不同),但我仍然不喜欢我无法回答的问题 :)

编辑:为了让这更加清晰:虽然我很清楚更紧凑的结构可以带来一些性能优势,但我同意总体上担心这里或那里的几个字节是浪费时间。我肯定不会仅仅因为有几个字节的开销就停止使用Hashtable,就像我不会使用纯char数组而不是String(或开始使用C)一样。这纯粹是学习Java / JVM内部的一点学术兴趣 :)


你引用的文本似乎非常古老(因为它提到了Microsoft JVM)。在那个时候,Hashtable的实现可能已经有所不同了。 - Michael Borgwardt
是的,那可能是真的,我试图找到1.3 JDK的源代码,但现在这似乎几乎是不可能的。 - Voo
你是不是在计算三个单词,而引用中使用了四个单词? - Paŭlo Ebermann
实际上找到了一个古老的1.3jdk,Entry类是相同的(除了显然缺少泛型)。@Paŭlo 抱歉不理解 - 我的意思是代码是公开的,我不知道我错过了哪个变量? - Voo
不,我的意思是你引用的文本中写着“[...]一个对象占用16个字节,或4个字”,而你计算时却加上了3个字的开销。如果我说错了,抱歉,因为我应该已经在床上了几个小时,现在思维不太清晰。 - Paŭlo Ebermann
是的,每个对象至少有4个字长,因为JVM总是为本地变量分配至少1个字(可能是为了避免几个对象的本地变量从同一地址开始),但如果对象没有本地变量,则这只是重要的,否则我们可以用有用的东西填充空间。 - Voo
1个回答

2
作者似乎假设Map.Entry中有2个32位的引用和2个32位的int值,并且每个对象都有16字节的开销,总共是64字节。然而,这是有缺陷的,因为Sun/Oracle的JVM只在8字节边界上分配内存,所以虽然技术上Integer占用20字节的内存,但使用了24字节(下一个8的倍数)。此外,许多JVM现在使用64位引用,因此Map.Entry将使用另外16字节。全部都非常低效,这就是为什么你可能会使用像TIntIntHashMap这样使用原始类型的类的原因。但通常来说,这并不重要,因为与你的时间成本相比,内存很便宜。如果你在服务器应用程序上工作,每小时花费公司约40美元,那么你需要每分钟节省约10 MB的内存才能节省相同数量的内存。最好每分钟节省更多。每分钟节省10MB的内存很难实现。内存可以重复使用,但你的时间不能。

但是,由于缓存行为的存在,紧凑的结构可以比笨重的结构快得多,因此,如果您在紧密循环内部使用Java对象,则仍会浪费宝贵的时间:您和用户等待程序完成的时间。 - Fred Foo
哦,我甚至不会考虑停止使用HashMap或String,只是因为我可以在这里或那里节省一些字节,尽管作为学生的我的空闲时间确实很便宜。那只是一种学术兴趣,可以这么说。是的,我们忽略了分配边界和64位,因为该条目肯定已经有几年了。但是我仍然不明白我们如何获得64字节-最多只分配2个新整数和1个Hashtable$Entry-HashTable$Entry中的Entry<K,V> next并不是新分配的,而只是一个引用,就我所看到的。 - Voo
@larsmans,我同意缓存内存在性能关键系统中是一种高级资源。我的印象是大多数系统并没有被调整得如此精细,以至于缓存效应不是主要因素。我可能错了。 ;) - Peter Lawrey
@Voo,即使你没有得到报酬,你的时间也是有价值的。也许它不值每小时40美元,也许只值每小时4美分,但这仍然是每分钟10 KB。你只考虑了对象中的字段,每个对象都有一个开销。 - Peter Lawrey

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