Java 这种情况下,为什么原始数据类型比引用数据类型消耗更多内存?

Why does primitive data type consume much memory than reference data type for this scenario in Java?

我试图检查程序的内存消耗。在检查的过程中,我发现了一些有趣的事情。

我创建了一个负载 class,其中包含一些字段。

class Load {
    String name;
    String title;
    long id;
}

我创建了 500000 个 Load 对象并将它们添加到 ArrayList。我发现,它占用了大约 18 MB 内存。

然后,我修改了加载 class 并使用 reference 类型 Long。

class Load {
    String name;
    String title;
    Long id;
}

再次创建了 500000 个 Load 对象并将它们添加到 ArrayList。有趣的是,这次占用的内存比上一次少。大约 14 MB。

运行 测试更改 os 和 JVM 版本。找到以下结果。

OS: Windows 10 Pro 64 bit
JDK: 11 64bit
 
Object Created  | Load Object         | Memory | Load Object         | Memory  
------------------------------------------------------------------------------
1. 500000       | With primitive long | 18 MB  | With reference Long | 14 MB
2. 900000       |                     | 32 MB  |                     | 26 MB
3. 1500000      |                     | 53 MB  |                     | 41 MB

OS: macOS Big Sur 64 bit
JDK: 8 64bit
 
Object Created  | Load Object         | Memory | Load Object         | Memory  
------------------------------------------------------------------------------
1. 500000       | With primitive long | 18 MB  | With reference Long | 14 MB
2. 900000       |                     | 32 MB  |                     | 26 MB
3. 1500000      |                     | 53 MB  |                     | 41 MB

令人惊讶的是,在所有这些测试运行中,Object contains primitive types long 比 Object contains reference Long 消耗更多的内存。

我的问题是,为什么原始类型在这种情况下占用更多内存?

内存测试器代码:

public class MemoryChecker {

    private static final long MEGABYTE = 1024L * 1024L;

    public static long bytesToMegabytes(long bytes) {
        return bytes / MEGABYTE;
    }

    public static void main(String[] args) {
        List<Load> list = new ArrayList<Load>();
        for (int i = 0; i <= 500000
                ; i++) {
            list.add(new Load("Jim", "Knopf", 11L));
        }
        // Get the Java runtime
        Runtime runtime = Runtime.getRuntime();
        // Run the garbage collector
        runtime.gc();
        // Calculate the used memory
        long memory = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
    }
}

完整代码git repo.

  • 最大的primitive可以容纳8 bytes

  • 每个 Object 至少有 12 bytes(默认 64 位 VM 和相对较小的堆)headers.自动使它比基元大。

据我所知,有一个很好的库是正确的,叫做 jolhere is a related 问题。

jolset-up和run the samples很容易理解您感兴趣的实际数字。

对于 32 位 JVM,以及具有 CompressedOOPs 功能的 64 位 JVM(HotSpot JVM 支持并默认启用),与 8 字节相比,引用仅占用 4 字节long.

即使您使用实际对象初始化引用,当对象 共享 时,它也可能消耗更少的内存。这适用于常量的 autoboxing

If the value p being boxed is the result of evaluating a constant expression (§15.29) of type boolean, byte, char, short, int, or long, and the result is true, false, a character in the range '\u0000' to '\u007f' inclusive, or an integer in the range -128 to 127 inclusive, then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

但也适用于一般情况下以 Long.valueOf(long) 结束的所有操作。

This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.

当然,如果你创建了很多非共享Long对象,它们会比原始long消耗更多的内存。如果您使用很多不同的值,即使可能共享它们也无济于事。

由于'Long id'没有初始化,所以它只占用4/8字节(取决于VM和OS),其中'long id'默认用0L初始化,占用8字节。