【实践派】Java实例的内存占用测试(一)


一、背景

据我目前所知,大多数开发者在编写代码的时候,没有特别注意到一个对象占用了多少内存,因为大多数情况下占用的内存也是很小,小到可以忽略不计,这样会容易埋下隐患(例如在Android手机上面的话就会占用了很大内存,多了的话很容易OOM);

把数据放在内存里面的场境如下:

1、单例模式;

2、为了加快读取速度,将一些常用的数据放在内存里;

3、Android里面的Application;

本文章的目的并不是什么非常官方的专业文,完全是出于自己的想法写出来,主要是从序列化角度去观察一个对象占用内存的多少(使用byte为一个单位),这是一个比较贴近实际场境的方法;当然查看内存占用还有很多方法;如果有什么不对的地方,欢迎指点;


二、开始实践各种不同的类对象占用内存;

在测试之前先将用于获取对象的Byte大小的工具类代码写出来:

public class ObjectSizeUtil {
    private static byte[] objectToByte(Object obj) throws IOException {

        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(bs);
        os.writeObject(obj);
        os.flush();
        byte bytes[] = bs.toByteArray();
        return bytes;
    }

    /**
     * 获取对象的Byte长度
     * @param obj 
     * @return
     */
    public static long getObjectSize(Object obj){

        try {
            return objectToByte(obj).length;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }
}
可以看出是一个简单的把Object转成byte数组;


1)无包名的无属性的类对象:

public class A implements Serializable{
    @Override
    public String toString() {       
        return "类 "+packName+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);</span>
    }
}
	
	    再执行代码:System.out.println(new A().toString());
输出结果:类 A 的Byte大小:22

从结果可以这个A的对象占用了22个byte


2)包名长度对于内存占用的影响:

现在我就新建一个空属性,类名为B的类放到包名为a的目录下,代码如下:

package a;

import java.io.Serializable;

public class B implements Serializable{
    @Override
    public String toString() {
        return "类 "+ getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}
执行代码: System. out .println( new B().toString());

输出结果:类 a.B 对象的Byte大小:24

看了输出结果,这个在包名为a下的类B产生的对象比上面的那个空属性的类A对象多了2个Byte,明明包名只是一个字母为什么会多了2个Byte呢,原因是这个类的原本路径是 a.B ,中间还有一个点的,这个也是路径的一部分,所以这个时候就多了两个byte(包名长度+包的层数); 


3)类名的长度对内存的影响:

同样,这个也是拿那个空包名空属性的类来做比较的,现在就创建一个无包名的Temp类如下:

public class Temp implements Serializable {
    @Override
    public String toString() {
        return "类 "+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}

执行代码:System.out.println(new Temp().toString());

输出结果:类 Temp 对象的Byte大小:25

对比结果,比A类的对象多出了3个byte,原因是类名比A多了3个字母

4) 属性名长度对内存占用的影响:

<pre name="code" class="java">public class D implements Serializable {
    private int v;
    @Override
    public String toString() {
        return "类 "+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}
 
 
 

执行代码:System.out.println(new D().toString());

输出结果:类 D 对象的Byte大小:30

--------------------------------------------------------------------

public class E  implements Serializable {
    private int value;

    @Override
    public String toString() {
        return "类 "+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}

执行代码:System.out.println(new E().toString());

输出结果:类 E 对象的Byte大小:34

对比D和E类的输出,E类比D类的内存占用多了4个Byte,原因是E类的属性value比D类的属性v 的属性名长了4;


5)继承类对内存占用的影响:

写一个C类继承A类,代码如下:

<pre>
public class C extends A {
    @Override
    public String toString() {
        return "这是继承类 "+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}
 
 

执行代码:System.out.println(new C().toString());

输出结果:这是继承类 C 对象的Byte大小:38

对比A类22个Byte多出了16个Byte,由此可见,如果类的继承层次越多,所占用的内存就越大;

3)各种数据类型各占内存(byte short int long  boolean float double  char String ):

public class U implements Serializable{
    public 	byte	 v;
//    public 	short	 v;
//    public 	int	 v;
//    public 	long	 v;
//    public 	boolean	 v;
//    public 	float	 v;
//    public 	double	 v;
//    public 	char	 v;
//    public 	String	 v;//这个不是基本数据类型,会有另外解释
    
    @Override
    public String toString() {
        return "类 "+getClass().getName()+" 对象的Byte大小:"+ tools.ObjectSizeUtil.getObjectSize(this);
    }
}
这里的代码是每次执行的时候,只保留一个属性,按顺序执行结果如下:

类型整个对象byte总长度数据类型实际byte长度(byte总长度-参考A类所占内存)
byte275
short286
int308
long3412
boolean275
float308
double3412
char286
String4826


根据上面的表格可以看出,一个数据类型在定义后的最低消费;


除了String默认值为null之外,其他的数据类型都会有相应的默认值,而且所占的内存不会因为值的变化而变化;

而String是使用内存引用,跟基本数据类型有所不同,的大小是根据值的长度和字符的类型而定的:如果默认值为"abcd"的话,byte总长度达到54,其中(54-22=32)是String实际所占内存,这里比赋值的情况下比null值多出了6个byte,这6个byte的组成由(4个byte字符+2个byte的内存引用),由于String类型是内存引用的一种,所以这个相对基本数据类型来说有点特殊;



三、结论

1、一个类实例化之后所占用的内存大小跟包名长度、类名长度、属性的类型、属性的名称长度是成正比关系的;

2、混淆类确定可以节省一定量的内存;

3、基本数据类型都会有默认值,所占用的内存不会因为值变化而变化;

如觉得文章所讲的内容不对,欢迎来指点;


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值