为什么newInstance会使用比预期更多的内存?

时间:2013-01-15 19:38:24

标签: java memory

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

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

无论如何,到目前为止,我发现您可以使用newConstructorForSerialization中的sun.reflect.ReflectionFactory创建一个没有其他分配的对象。

这样可行,但不知何故,对newInstance的调用会分配1块内存,而不是预期的内容。

例如,班级

public class a {
    Integer a;
}

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

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

但是,我的代码提供了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字节)。

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

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

1 个答案:

答案 0 :(得分: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”分配之前使用的内存值的差异。

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

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

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