我希望对我在java中实现的缓存进行非常精确的测量。请告诉我这种方法是否可行。
我有一个将字符串映射到字符串数组的hashmap。有没有办法让这个数据结构得到很好的近似?
如何获取字符串的大小?调用String.toByte()并为保持对象的开销添加一些加号?
字符串数组是所有字符串的总和吗?还是有一些开销?
hashmap是否也有一些overead,可能将对象包装到某个条目对象中?
对于地图中所有未使用的空间,hashmap仍会分配一些空间,我可以为地图中所有未使用的空格总结2 * null pointer
吗?
我对个人的答案感到满意,并为我指明正确的方向。
答案 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
实例对象标题char
实例字段的四个32位字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!)