一、背景
据我目前所知,大多数开发者在编写代码的时候,没有特别注意到一个对象占用了多少内存,因为大多数情况下占用的内存也是很小,小到可以忽略不计,这样会容易埋下隐患(例如在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>
}
}
输出结果:类 A 的Byte大小:22再执行代码:System.out.println(new A().toString());
从结果可以这个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类所占内存) |
byte | 27 | 5 |
short | 28 | 6 |
int | 30 | 8 |
long | 34 | 12 |
boolean | 27 | 5 |
float | 30 | 8 |
double | 34 | 12 |
char | 28 | 6 |
String | 48 | 26 |
根据上面的表格可以看出,一个数据类型在定义后的最低消费;
除了String默认值为null之外,其他的数据类型都会有相应的默认值,而且所占的内存不会因为值的变化而变化;
而String是使用内存引用,跟基本数据类型有所不同,的大小是根据值的长度和字符的类型而定的:如果默认值为"abcd"的话,byte总长度达到54,其中(54-22=32)是String实际所占内存,这里比赋值的情况下比null值多出了6个byte,这6个byte的组成由(4个byte字符+2个byte的内存引用),由于String类型是内存引用的一种,所以这个相对基本数据类型来说有点特殊;
三、结论
1、一个类实例化之后所占用的内存大小跟包名长度、类名长度、属性的类型、属性的名称长度是成正比关系的;
2、混淆类确定可以节省一定量的内存;
3、基本数据类型都会有默认值,所占用的内存不会因为值变化而变化;
如觉得文章所讲的内容不对,欢迎来指点;