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

类别:Java 点击:0 评论:0 推荐:
 

结果:

让我们来对一些类使用这个工具,察看是否结果和我们预想的一样。

注意:以下的结果都是基于Windows平台的jdk1.3.1版本,并不能保证所有的平台或者jdk版本都得到相同的信息。

l         java.lang.Object

这个所有对象的基类作为我们的第一个例子。对于java.lang.Object我们将得到:

'before' heap: 510696, 'after' heap: 1310696

heap delta: 800000, {class java.lang.Object} size = 8 bytes

所以,一个简单的Object对象要占用8个字节的内存空间,当然我们不能希望它所占的空间是0,因为每个实例都至少必须包含一些最基本的操作,比如equals(), hashCode(), wait()/notify()等。

 

l         java.lang.Integer

我和我的同事都经常封装本地的int到Integer的实例中去,以便于我们能在集合的对象中使用它们,那样做到底要耗费多少内存呢?

'before' heap: 510696, 'after' heap: 2110696

heap delta: 1600000, {class java.lang.Integer} size = 16 bytes

这个16字节的结果比我们预想的要糟糕,因为一个int值恰好是4个字节,但是使用了Integer以后,多使用了3倍的空间。

 

l         java.lang.Long

Long看起来应该比Integer使用更多的空间,可是事实并非如此:

'before' heap: 510696, 'after' heap: 2110696

heap delta: 1600000, {class java.lang.Long} size = 16 bytes

很明显,因为一种特别的JVM实现必须符合特定的CPU类型,所以事实上的对象大小在堆中所占的空间必须和低级的内存边界对齐。看起来一个Long是一个8字节的大小的Object对象加上8字节用来保存long值。相比之下,Integer就有4个字节没有使用的空间。所以,应该是JVM强制对象使用8字节作为字的边界。

 

l         Arrays

接下来比较一些基本类型的数组,比较有指导意义,能够部分的发现一些隐藏的信息和证明另一些流行的诡计:使用一个size-1的数组封装基本类型来当作对象。通过修改Sizeof.main()来使用一个循环增加数组的长度。然后能够得到int数组:

length: 0, {class [I} size = 16 bytes

length: 1, {class [I} size = 16 bytes

length: 2, {class [I} size = 24 bytes

length: 3, {class [I} size = 24 bytes

length: 4, {class [I} size = 32 bytes

length: 5, {class [I} size = 32 bytes

length: 6, {class [I} size = 40 bytes

length: 7, {class [I} size = 40 bytes

length: 8, {class [I} size = 48 bytes

length: 9, {class [I} size = 48 bytes

length: 10, {class [I} size = 56 bytes

还有一些char数组:

length: 0, {class [C} size = 16 bytes

length: 1, {class [C} size = 16 bytes

length: 2, {class [C} size = 16 bytes

length: 3, {class [C} size = 24 bytes

length: 4, {class [C} size = 24 bytes

length: 5, {class [C} size = 24 bytes

length: 6, {class [C} size = 24 bytes

length: 7, {class [C} size = 32 bytes

length: 8, {class [C} size = 32 bytes

length: 9, {class [C} size = 32 bytes

length: 10, {class [C} size = 32 bytes

从以上可以看出,8个字节的边界很明显的表现出来了。同时,肯定包含不可避免的8个字节的Object头部,然后基本数据类型的数组占用其它的8个字节。使用int[1]和Integer相比,看起来不能提供任何的内存使用,除了可以作为一个同样数据的可变版本。

 

l         多维数组

多维数组有另外的一个惊人之处。开发者普遍的使用一个构造函数例如int[dim1][dim2]用于数字或者科学计算。在一个int[dim1][dim2]的数组实例中,每一个嵌套的int[dim2]都是一个对象,并且每一个对象都加上一个16字节的数组对象头。当我不需要一个三角的或者粗糙的数组,那个代表着纯粹的头部。当维数增加时,影响增加很大。举例来说,一个int[128][2]的实例占用3600字节,使用着246%的头部。在特别的例子byte[256][1]中,这个头部因素已经是19!和C/C++的解决方案相比,同样的语法不会增加这么多的内存消耗。

 

l         java.lang.String

让我们来测试一个空串,现构造一个new String():

'before' heap: 510696, 'after' heap: 4510696

heap delta: 4000000, {class java.lang.String} size = 40 bytes

结果提供了一种相当不好的现象,就是一个空的String就要占用40字节的大小,足够用来保存20个字符了。

在我们使用包含内容的字符串以前,我们使用一个帮组方法来创建一个字符串。不过使用以下文字来创建:

object = "string with 20 chars";

将不会工作,因为所有的这样的对象操作将结束于同一个字符串实例。语言规范中明确表明如此的行为(java.lang.String.intern()),因此使用如下:

    public static String createString (final int length)

    {

        char [] result = new char [length];

        for (int i = 0; i < length; ++ i) result [i] = (char) i;

       

        return new String (result);

    }

 

在这样的创建函数以后,得到如此的结果:

length: 0, {class java.lang.String} size = 40 bytes

length: 1, {class java.lang.String} size = 40 bytes

length: 2, {class java.lang.String} size = 40 bytes

length: 3, {class java.lang.String} size = 48 bytes

length: 4, {class java.lang.String} size = 48 bytes

length: 5, {class java.lang.String} size = 48 bytes

length: 6, {class java.lang.String} size = 48 bytes

length: 7, {class java.lang.String} size = 56 bytes

length: 8, {class java.lang.String} size = 56 bytes

length: 9, {class java.lang.String} size = 56 bytes

length: 10, {class java.lang.String} size = 56 bytes

结果很明显的表明了字符串的内存增加轨迹。但是字符串要增加一个24字节的头部。对于非空的字符串,如果字符个数少于10个或者更少,这个增加的头部将消耗相对于有效的负载(2个字节对于每个字符,加上4个作为长度)在100%到400%之间变化。

本文地址:http://com.8s8s.com/it/it17805.htm