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