1

在 Java 中,我想弄清楚一个对象在分配时使用的确切内存量。

仅仅调用构造函数和度量是行不通的,因为它可能会在构造函数期间分配其他对象。此外,我更喜欢使用实时计算给定 VM 中已用内存的方法。这可能不是标准的 VM,因此计算字段并做出明智的猜测是不够的。

无论如何,到目前为止,我发现您可以使用newConstructorForSerializationsun.reflect.ReflectionFactory.

这可行,但不知何故,对 newInstance 的调用分配的内存块比预期的多。

例如,类

public class a {
    Integer a;
}

public class b {
    Integer b = new Integer(12345);
}

两者都应该给出相同的结果。在这种情况下,在默认 VM 中使用 Java 7 时为 16 个字节。

但是,我的代码给出了 32 个字节(总是比预期多 16 个字节)。我可以通过从结果中删除 16 来弥补这一点,但我需要 100% 确定它总是分配那个额外的块。对我来说,知道内存使用的上限比确切的数量更重要。因此,如果我 100% 确定始终添加此块,则从结果中减去 16 才是安全的。

我的代码:(使用 -XX:-UseTLAB VM 参数运行)

import java.lang.reflect.Constructor;

import sun.reflect.ReflectionFactory;

public class test {

    public static void main(String[] args) throws Exception {
        prepare(a.class, Object.class);
        System.out.println(memUse());
        System.out.println(memUseSimple());
    }

    private static long memUseSimple() {
        long start = Runtime.getRuntime().freeMemory();
        a a = new a();
        return start - Runtime.getRuntime().freeMemory();
    }

    private static long memUse() throws Exception {
        Object o0 = intConstr.newInstance();
        long start = Runtime.getRuntime().freeMemory();
        Object o1 = intConstr.newInstance();
        return start - Runtime.getRuntime().freeMemory() - 16;
    }

    private static Constructor<?> intConstr;

    private static void prepare(Class<?> clazz, Class<?> parent) throws Exception {
            intConstr = ReflectionFactory.getReflectionFactory()
                    .newConstructorForSerialization(clazz,
                            parent.getDeclaredConstructor());
            return;
    }
}

编辑:澄清:我想知道为什么我需要减去intConstr.newInstance()调用的 16 字节开销,如果我可以 100% 确定这个开销总是相同的(或者至少不少于 16 字节)。

即使您在上面的代码中将 a 替换为 b,它仍然给出 16 作为结果memUse(),但不是memUseSimple()。我只关心memUse()但添加了简单的方法作为比较。

我知道intConstr.newInstance()在另一个 VM 上可能会有不同的开销。这并不重要,我需要知道的是,如果它在当前 VM 上产生 16 字节的开销,它是否总是会产生 16 字节的开销(在此运行时)?此外,与 just 相比,这种开销来自哪里new a()

4

1 回答 1

1

事情或多或少很简单。我使用以下代码检查和之间是否存在差异newnewInstance()我找不到任何(Java 7、64 位、-XX:-UseTLAB -verbose:gc):

public final static long usedMemory()
{
    return Runtime.getRuntime().totalMemory() 
               - Runtime.getRuntime().freeMemory();
}

public static void main(String[] args) throws Exception
{
    // we get the default ctor
    Constructor<?> ctor = ReflectionFactory
            .getReflectionFactory()
            .newConstructorForSerialization(
                    Main.class, Object.class.getDeclaredConstructor());
    // warm up newInstance
    Object d = ctor.newInstance();
    // warm up Runtime
    System.out.println(usedMemory());
    // warm up Main
    Object b = new Main();
    // force GC
    System.gc();
    // get currently used memory
    final long mem = usedMemory();
    Object a = new Main();
    System.out.println(usedMemory() - mem);
    Object c = ctor.newInstance();
    System.gc();
    System.out.println(usedMemory() - mem);
    System.out.println(a + ", " + b + ", " + c + ", " + d);
}

代码看起来像这样,因为我们必须欺骗编译器不要优化它的任何部分并让系统初始化一些背景内容。我得到的输出是:

384024
[GC 381K->400K(249664K), 0.0006325 secs]
[Full GC 400K->264K(249664K), 0.0040675 secs]
16
[GC 264K->296K(249664K), 0.0002040 secs]
[Full GC 296K->264K(249664K), 0.0023534 secs]
32

...这正是我所期望的。'Main' 类型的一个对象消耗 16 个字节,如果我分配另一个对象,我们将消耗 32 个字节。第二个值是与第一次“主要”分配之前已用内存值的差值。

Main类是否包含成员变量也没有任何区别。我试过没有,有Integer integer;and Integer integer = Integer.of(42);,结果总是一样的。

newInstance您遇到的开销来自通话背景中正在发生的事情。在我的示例中,我通过强制 GC 运行来弥补这一点。

这可能有点作弊,但这个例子只是为了表明现在和之间有区别newnewInstance只是后者在后台分配了更多的东西。由于这些对象在伊甸园空间中分配,并且不会在任何 GC 循环中存活,恕我直言,它们可以被忽略。

于 2013-01-15T20:41:48.523 回答