你知道数据大小吗?--不要花太多的功夫来隐藏类的成员(一)

来源:互联网 发布:java netty好处 编辑:程序博客网 时间:2024/05/17 22:31
 

你知道数据大小吗?

--不要花太多的功夫来隐藏类的成员

 

摘要:

过去很多年里面,许多的Java开发人员都一直在问一个问题:“一个Java对象到底耗费多少内存呢?”在本文中,Vladimir Roubtsov用以前的解决方案来解释了这个问题,在此之外,基于他的经验演示了内存的使用,并且还提供了一些技巧来让你的Java程序更加高效。

 

作者:Vladimir Roubtsov

近来,我们帮助开发了一个Java服务器,这是一个类似内存数据库的应用。那就是说,我们特别强调设计,因为设计要特别考虑在内存中缓存大量的数据来提高查询的性能。

一旦我们得到运行的原型,那么在数据从硬盘上宰入经过分析以后,我们自然的决定了数据存储的轮廓。不太满意的初始效果,促使我们寻找更好的解决方案。

 

工具:

既然Java有目的的隐藏了很多内存管理的细节信息,要发现你的对象要消耗多少内存的确要花一些功夫。你可以使用Runtime.freeMemory()方法来测量在一个或者多个对象被分配前前后的堆的变化值。有一些文章,例如(Ramchander Varadarajan's "Question of the Week No. 107" (Sun Microsystems, September 2000) and Tony Sintes's "Memory Matters" (JavaWorld, December 2001))都详细地介绍了这些方法。但是不幸的是,这些先前的文章的解决方案的失败在于实现中使用了一个错误的Runtime方法。即使后来的文章也有它的不完整性。

l         调用Runtime.freeMemory() 方法提供的功能是不够的,因为JVM可以在任何时候(只要需要,特别是在运行垃圾收集的时候)决定增加它的当前的堆大小。除非在运行的时候已经使用了参数-Xmx指定了堆的最大值,否则我们应该使用Runtime.totalMemory()-Runtime.freeMemory()作为在使用的堆大小。

l         执行单个Runtime.gc()方法并不能保证有效的请求垃圾收集。举例来说,我们可以请求对象的finalizer运行正常。既然Runtime.gc()不能保证阻塞到垃圾处理,那么一直等待到当堆大小稳定以后是一个很好的办法。

l         如果轮廓类创建了一个静态的数据作为先前的类初始化的一部分,那么堆内存对于第一个类实例的分配的空间应该包括这个数据。我们应该忽略被第一个类实例消耗的堆空间。

考虑这些问题:我们给出了一个Sizeof,作为一个工具来查看各种Java核心和应用类。

 

public class Sizeof

{

    public static void main (String [] args) throws Exception

    {

        // Warm up all classes/methods we will use

        runGC ();

        usedMemory ();

 

        // Array to keep strong references to allocated objects

        final int count = 100000;

        Object [] objects = new Object [count];

       

        long heap1 = 0;

 

        // Allocate count+1 objects, discard the first one

        for (int i = -1; i < count; ++ i)

        {

            Object object = null;

           

            // Instantiate your data here and assign it to object

           

            object = new Object ();

            //object = new Integer (i);

            //object = new Long (i);

            //object = new String ();

            //object = new byte [128][1]

           

            if (i >= 0)

                objects [i] = object;

            else

            {

                object = null; // Discard the warm up object

                runGC ();

                heap1 = usedMemory (); // Take a before heap snapshot

            }

        }

 

        runGC ();

        long heap2 = usedMemory (); // Take an after heap snapshot:

       

        final int size = Math.round (((float)(heap2 - heap1))/count);

        System.out.println ("'before' heap: " + heap1 +

                            ", 'after' heap: " + heap2);

        System.out.println ("heap delta: " + (heap2 - heap1) +

            ", {" + objects [0].getClass () + "} size = " + size + " bytes");

 

        for (int i = 0; i < count; ++ i) objects [i] = null;

        objects = null;

    }

 

    private static void runGC () throws Exception

    {

        // It helps to call Runtime.gc()

        // using several method calls:

        for (int r = 0; r < 4; ++ r) _runGC ();

    }

 

    private static void _runGC () throws Exception

    {

        long usedMem1 = usedMemory (), usedMem2 = Long.MAX_VALUE;

        for (int i = 0; (usedMem1 < usedMem2) && (i < 500); ++ i)

        {

            s_runtime.runFinalization ();

            s_runtime.gc ();

            Thread.currentThread ().yield ();

           

            usedMem2 = usedMem1;

            usedMem1 = usedMemory ();

        }

    }

 

    private static long usedMemory ()

    {

        return s_runtime.totalMemory () - s_runtime.freeMemory ();

    }

   

    private static final Runtime s_runtime = Runtime.getRuntime ();

 

} // End of class  

Sizeof的关键方法是runGC() usedMemory()方法,我使用了runGC()这样的封装方法来调用_runGC()几次,是为了让这个方法有更加明显的效果。

注意我调用runGC()方法的地方,你可以在heap1heap2编辑你的代码,加入你任何你感兴趣的例子。

也请注意Sizeof怎么样输出对象的大小,数据的传递闭包要求被用所得count类实例所用到,然后被count整除。对于大多数类来说,这个结果将是单个类实例对象所耗费的内存大小,包括所有它自己的成员域。内存的界限值不同于由一些商业的工具报告的影子内存界限(比如说,如果一个对象有一个int[],那么它的内存消耗将显得很特别)。