【问题标题】:Accurate measurement of object sizes精确测量物体尺寸
【发布时间】:2013-02-05 12:42:36
【问题描述】:

这个问题底部的代码有点长,但基本上是创建了一些对象并确定了它们在内存中的大小。我使用以下 JVM 参数执行代码(TLAB 是为了避免块内存分配并应该得到准确的内存使用数据):

-server -Xms2000m -Xmx2000m -verbose:gc -XX:-UseTLAB

我在 64 位 Hotspot JVM 上运行代码并得到以下输出:

Java HotSpot(TM) 64 位服务器虚拟机
对象:16 字节

具有 1 个 int 的对象:16 个字节
具有 2 个整数的对象:24 个字节
具有 3 个整数的对象:24 个字节

1 长的对象:24 字节
具有 2 个 long 的对象:32 个字节
具有 3 个 long 的对象:40 个字节

具有 1 个引用的对象:16 个字节
具有 2 个引用的对象:24 个字节
具有 3 个引用的对象:24 个字节

我的结论是:

  • 一个对象占用 12 个字节,对齐到 16 个字节。
  • 一个 int 占用 4 个字节(1 个带有一个 int 的对象是 12 + 4 = 仍然是 16 个字节,有 2 个 int:12 + 8 = 20 对齐到 24 个字节)
  • 一个 long 需要 8 个字节(1 个 long 对象是 12 + 8 = 20 个字节,对齐到 24 个字节)

但我很难理解为什么引用不像 longs 那样占用那么多空间。

由于在 64 位 JVM 上引用是 8 字节,显而易见的结论是测量方法存在缺陷*。 你能解释一下发生了什么以及可以做些什么来解决它吗?

*注意:
- 测量期间不运行 GC。
- 使用 Netbeans 分析器会产生类似的结果。

public class TestMemoryReference {

    private static final int SIZE = 100_000;
    private static Runnable r;
    private static Object o = new Object();
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    private static Object o3 = new Object();

    public static class ObjectWith1Int  { int i; }
    public static class ObjectWith2Ints { int i, j; }
    public static class ObjectWith3Ints { int i, j, k; }
    public static class ObjectWith1Long  { long i; }
    public static class ObjectWith2Longs { long i, j; }
    public static class ObjectWith3Longs { long i, j, k; }
    public static class ObjectWith1Object  { Object o = o1; }
    public static class ObjectWith2Objects { Object o = o1; Object p = o2; }
    public static class ObjectWith3Objects { Object o = o1; Object p = o2; Object q = o3; }

    private static void test(Runnable r, String name, int numberOfObjects) {
        long mem = Runtime.getRuntime().freeMemory();
        r.run();
        System.out.println(name + ":" + (mem - Runtime.getRuntime().freeMemory()) / numberOfObjects + " bytes  ");
    }

    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("java.vm.name") + "  ");

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new Object(); } };
        test(r, "Object", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Int(); } };
        test(r, "Object with 1 int", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Ints(); } };
        test(r, "Object with 2 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Ints(); } };
        test(r, "Object with 3 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Long(); } };
        test(r, "Object with 1 long", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Longs(); } };
        test(r, "Object with 2 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Longs(); } };
        test(r, "Object with 3 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Object(); } };
        test(r, "Object with 1 reference", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Objects(); } };
        test(r, "Object with 2 references", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Objects(); } };
        test(r, "Object with 3 references", SIZE);
    }
}

【问题讨论】:

    标签: java memory jvm


    【解决方案1】:

    因为在 64 位 JVM 上引用是 8 个字节

    这是您可能存在缺陷的假设。

    HotSpot 能够使用 "compressed oops" 在 JVM 的一些位置使用 32 位值进行引用(强调我的):

    哪些oops被压缩了?

    在 ILP32 模式的 JVM 中,或者如果在 LP64 模式下关闭 UseCompressedOops 标志,则所有 oops 都是本机机器字长。

    如果 UseCompressedOops 为 true,则堆中的以下 oops 将被压缩:

    • 每个对象的类字段
    • 每个 oop 实例字段
    • oop 数组 (objArray) 的每个元素

    怀疑这就是你的情况。

    使用测试

    -XX:-UseCompressedOops
    

    -XX:+UseCompressedOops
    

    在我的机器上,默认情况下我得到的结果和你一样,但我看到-XX:-UseCompressedOops

    Object:16 bytes
    Object with 1 int:24 bytes
    Object with 2 ints:24 bytes
    Object with 3 ints:32 bytes
    Object with 1 long:24 bytes
    Object with 2 longs:32 bytes
    Object with 3 longs:40 bytes
    Object with 1 reference:24 bytes
    Object with 2 references:32 bytes
    Object with 3 references:40 bytes
    

    ...这可能更接近您的预期:)

    【讨论】:

    • Aargh - 我认为 UseCompressedOops 默认为 false - 结果证明它是默认启用的。很好发现。
    【解决方案2】:

    Java 对象的大小在定义类时是已知的。

    Java对象的内存使用:通用http://www.javamex.com/tutorials/memory/object_memory_usage.shtml

    【讨论】:

    • 仪器有时不如直接测量精确。并且该链接未使用压缩指针选项解决 64 位 jvm。
    猜你喜欢
    • 1970-01-01
    • 2012-12-01
    • 2017-06-07
    • 2018-05-06
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多