Java内存使用简单的数据结构

时间:2011-10-14 12:42:25

标签: java string memory jvm hashmap

我希望对我在java中实现的缓存进行非常精确的测量。请告诉我这种方法是否可行。

我有一个将字符串映射到字符串数组的hashmap。有没有办法让这个数据结构得到很好的近似?

  1. 如何获取字符串的大小?调用String.toByte()并为保持对象的开销添加一些加号?

  2. 字符串数组是所有字符串的总和吗?还是有一些开销?

  3. hashmap是否也有一些overead,可能将对象包装到某个条目对象中?

  4. 对于地图中所有未使用的空间,hashmap仍会分配一些空间,我可以为地图中所有未使用的空格总结2 * null pointer吗?

  5. 我对个人的答案感到满意,并为我指明正确的方向。

5 个答案:

答案 0 :(得分:3)

我认为一个好的实用方法是使用内存分析器,例如YourKit

答案 1 :(得分:3)

你试过Instrumentation.getObjectSize()吗?这可能会告诉您想要的,尽管JavaDoc声称它只是一个估计值。

答案 2 :(得分:2)

对象实例隐含的实际内存开销取决于JVM实现的一些内部细节,并且可能很难定义,因为它可能在对象的整个生命周期内(垃圾收集器内)发生变化,一个对象可以在使用不同内存管理结构的代之间“移动”。)

非常粗略的近似是任何对象的每个实例都包含两个“字”(32位机器上的两个32位值,64位机器上的两个64位值);其中一个单词或多或少是指向该对象的Class实例的指针,另一个包含一些对象状态,例如该对象的监视器(使用synchronized锁定的对象)。然后是对象字段。对于数组,数组长度必须写在对象的某处,以及值。

此时,请查看Java类的源代码(在JDK发行版中查找名为src.zip的文件)。在String.java文件中,我们可以看到,String实例内部有四个字段:对char值数组的引用,以及三个int(一个是String数组中第一个字符串字符的索引,第二个是字符串长度,第三个字符串缓存字符串hashcode)。因此,对于32位计算机,您可以估计 n 字符的String实例的最小内存使用量是以下总和:

  • String实例对象标题
  • 的两个32位字
  • char实例字段的四个32位字
  • 数组实例标头和长度
  • 的三个32位字
  • n 字符本身的16位字(String为16位)

这只是最小值,因为String实例仅引用内部字符数组的,因此数组内存大小可能更大。另一方面,可以在几个String.substring()实例之间共享字符数组。这种结构允许String非常快:新的String实例内部使用相同的数组,因此不涉及数据复制;但它也意味着如果你有一个大字符串,取一个小子串,并存储那个小子串,你实际上也将大数组保留在RAM中(对于{{1}实例str,您可以使new String(str)获取一个新实例,该实例将在内部使用新分配和修剪的数组实例。好的一面是,如果你有两个字符串,一个是另一个字符串的子字符串,并且你将它们存储在缓存中,那么你只需要为公共内部数组付一次。

因此,即使不考虑GC隐含的所有隐藏成本,也很难知道“字符串的内存大小”是什么意思:如果两个String个实例共享相同的内部数组,那么你如何计算每个字符串的“大小”?

查看HashMap的来源将向您显示还有已分配的内部实例;每个存储值都有一个HashMap.Entry实例数组和一个HashMap.Entry实例。根据条目数和配置的加载因子动态调整数组大小。

由于对内存大小的考虑很难,一个完全不同的解决方案是让GC自己决定何时应删除旧的缓存条目。这在内部使用“软引用”:它们是某种指针,当内存变紧时GC可能设置为null(破坏引用可能允许GC释放更多对象)。这使得原始的“内存感知”缓存会根据可用的RAM自动修剪。一个有用的库是Google的Guava及其MapMaker类。

答案 3 :(得分:1)

1)让我们假设,虽然不能保证(不同的JVM可以采取不同的行为)

2)字符串总和加上持有对象(数组)的开销

3)当然,很多。对象被包装到条目中,然后将这些条目存储到内部HashSet等中......至少在Oracle JVM中是这样。

4)地图上没有“未使用”的空间......你的意思是什么?

总而言之,遗憾的是,没有办法得到任何这些问题的准确答案。这取决于VM,GC,操作系统等......分析器可以为您提供与一种配置相关的一些有用信息,但这是您可能希望获得的最多信息。

这是设计的:Java及其垃圾收集器希望您永远不必担心内存分配和管理细节。它在大多数情况下都很棒,在你的情况下它是一种负担。无论如何,为什么你有这样的需要?

答案 4 :(得分:0)

量化内存使用量的一种简单方法是使用以下内容: jmap -histo:live <pid>(java进程的进程ID)

这将为您提供堆的直方图。对于每个Java类,都会打印对象数,内存大小(以字节为单位)和完全限定的类名 你也可以这样做: jmap -dump:live pid
以hprof二进制格式转储Java堆。
我会更多地关注jmap。当你的瓶颈是java的内存时,这是非常有用的。
例如,您可以创建一个每30秒执行一次jmap -histo的脚本。然后,您可以绘制输出图形,并查看在java类中创建的每个对象的内存演变。

以下是jmap -histo的一个例子:

$ jmap -histo `pgrep java` |more
num   #instances    #bytes  class name
--------------------------------------
  1:    224437    27673848  [C
  2:     38611    23115312  [B
  3:     47801    12187536  [I
  4:    208624     8344960  java.lang.String
  5:     45332     6192904  <constMethodKlass>
  6:     45332     5450864  <methodKlass>
  7:      3889     4615536  <constantPoolKlass>
  8:     45671     4193136  [Ljava.lang.Object;
  9:     66203     3222312  <symbolKlass>
 10:      3889     3192264  <instanceKlassKlass>
 11:      3455     2999296  <constantPoolCacheKlass>
 12:     19754     1106224  java.nio.HeapCharBuffer

更多示例here


此外,描述您的过程也是一个不错的选择。
我建议使用visualvm (free)jprofiler7 (not free, but awesome!)