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