简短的回答
为了找出一个对象有多大,我会使用探查器。例如,在 YourKit 中,您可以搜索对象,然后获取它来计算其深度大小。这将使您清楚地了解如果对象是独立的并且是对象的保守大小,则将使用多少内存。
狡辩
如果对象的一部分在其他结构中重复使用,例如字符串文字,您不会通过丢弃它来释放这么多内存。事实上,丢弃对 HashMap 的一个引用可能根本不会释放任何内存。
序列化怎么样?
序列化对象是获得估计值的一种方法,但由于序列化开销和编码在内存和字节流中不同,因此可能会出现很大的偏差。使用多少内存取决于 JVM(以及是否使用 32/64 位引用),但序列化格式始终相同。
e.g.
在Sun/Oracle的JVM中,一个Integer可以占用16个字节的头部,4个字节的数字和4个字节的填充(对象在内存中是8字节对齐的),总共24个字节。但是,如果序列化一个整数,则需要 81 个字节,序列化两个整数则需要 91 个字节。即第一个整数的大小被夸大,第二个整数小于内存中使用的大小。
字符串是一个更复杂的例子。在Sun/Oracle JVM中,它包含3个int
价值观和char[]
参考。所以你可能会假设它使用 16 字节标头加上 3 * 4 字节作为int
s,4个字节char[]
,16字节的开销char[]
然后每个字符两个字节,与 8 字节边界对齐......
哪些标志可以改变大小?
如果您有 64 位参考,char[]
引用的长度为 8 个字节,因此需要 4 个字节的填充。如果您有 64 位 JVM,则可以使用+XX:+UseCompressedOops
使用 32 位引用。 (因此,仅查看 JVM 位大小并不能告诉您其引用的大小)
如果你有-XX:+UseCompressedStrings
,JVM 将尽可能使用 byte[] 而不是 char 数组。这可能会稍微减慢您的应用程序的速度,但可能会显着改善您的内存消耗。当使用 byte[] 时,每个字符消耗 1 个字节的内存。 ;) 注意:对于 4 字符字符串,如示例中所示,由于 8 字节边界,使用的大小是相同的。
你所说的“尺寸”是什么意思?
正如已经指出的,HashMap 和 List 更复杂,因为许多(如果不是全部)字符串可以重用,可能是字符串文字。 “大小”的含义取决于它的使用方式。即该结构单独使用多少内存?如果丢弃该结构,将释放多少资源?如果复制该结构会使用多少内存?这些问题可以有不同的答案。
没有分析器你能做什么?
如果您可以确定可能的保守尺寸足够小,则确切的尺寸并不重要。保守的情况可能是从头开始构建每个字符串和条目。 (我只是说可能,因为 HashMap 即使是空的,也可以容纳 10 亿个条目。具有单个字符的字符串可以是具有 20 亿个字符的字符串的子字符串)
您可以执行 System.gc(),获取可用内存,创建对象,执行另一个 System.gc() 并查看可用内存减少了多少。您可能需要多次创建该对象并取平均值。多次重复这个练习,但它可以给你一个公平的想法。
(顺便说一句,虽然 System.gc() 只是一个提示,但 Sun/Oracle JVM 默认情况下每次都会执行一次 Full GC)