本文的方法是利用java.lang.Instrumentation的工具类来实现的,之前看过一篇红薯的文章介绍了这方面的内容就跟着做了下,并总结出一套比较靠谱的推算方式。红薯的文章因为是当时看的没有记录,暂时没有找到了,同时本文参考了一篇18摸的文章链接如下:Java SE 6 新特性: Instrumentation 新功能
前置条件:
1,本方法只在JDK6上验证过,Instrumentation接口是在JDK1.5引入的,所以1.5可能可行,1.5以前的版本就不可行了。
2,本位验证环境为32系统,64位系统不涉及。
3,本文需要通过打包为jar文件来实现,对jar的存在如下要求:
3.1 jar文件中需要有一个类实现了public static void premain(java.lang.String args, java.lang.instrument.Instrumentation instantce)方法,这个方法是用来传入Instrumentation接口的实现,因为这个接口的实现是native code的,在jdk中本身并没有固定实现,个人猜想是不同jvm各自实现的。
3.2 需要编辑jar文件的MANIFEAST.MF文件,添加属性Premain-Class,这个属性对应的值也就是3.1中实现对应方法的类全路径。
3.3 打包后在命令行里执行同时需要提供javaagent参数。运行命令如下java -javaagent:[jar路径] [包含main函数的类]。以我的实现为例:
java -javaagent:test.jar com.willard.instrumentation.object.size.ObjectMeasure
我用于获取Instrumetation的类实现(作为一个Util类用):
public class InstrumentationUtil
{
private static Instrumentation instance;
public static void premain(java.lang.String args, java.lang.instrument.Instrumentation instantce)
{
InstrumentationUtil.instance = instantce;
}
public static void permain(String args)
{
}
public static Instrumentation getInstance()
{
return instance;
}
}
jar中MANIFEAST.MF文件内容如下: `
Manifest-Version: 1.0`
`Premain-Class: com.willard.instrumentation.InstrumentationUtil`
一些用来计算的规则:
1,一个Object对象占用8个字节(Byte),这个是固定的——读到并验证了,至于出处确实不详。
2,一个对象的大小必须是8的倍数,不足8的倍数时会入到8的倍数上。这个同样是验证了可参考下面的论述。
3,一个引用reference占用的是4个字节(Byte)。这个是推算出来的,代码如下:
private static void mesureReference()
{
Object obj1 = new RefA();
Object obj2 = new RefB();
Object obj3 = new RefC();
simpleMeasure("A Object with one reference is : ", obj1);
simpleMeasure("A Object with two reference is : ", obj2);
simpleMeasure("A Object with three reference is : ", obj3); }
private static class RefA
{
private Object obj1;
}
private static class RefB
{
private Object obj1;
private Object obj2;
}
private static class RefC
{
private Object obj1;
private Object obj2;
private Object obj3;
}
}
这里打印出来的结果如下:
A Object with one reference is : 16
A Object with two reference is : 16
A Object with three reference is : 24
结合1、2条规则可以推出,首先除了基本对象任何对象都是Object(8 Byte), 一个类包含一个和两个引用时都是16 Byte说明一个reference小于8 Byte,同时验证了第二条。当一个类包含三个引用时变成了24,由此得出一个reference占用4Byte。
4,基本变量所占用的对空间统计如下:
统计方法比较笨,是声明一组class,这组class分别包括1~8个同类型的变量,类型就是下表一次列出的,然后查看在变量数递增是这个class的实例体积变化。
起初想直接尝试测量一个int等类型对象的大小,结果返回的都是16.原因应该是java的自动封装特性,因为Instrumentation.getObjectSize(Object obj)接受的是Object类型的参数,所以java自动转换类型为封装类型如Integer。通过查看下面类型的封装类可以发现都是一样的——继承自Object,拥有一个私有变量value,由此也就解释了为什么。
boolean | 1 |
short | 2 |
char | 2 |
char_zh | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
5,String对象的计算,String显示的值是24。通过String的类定义可以看出来(下图),一个char[]和3个int对象。在java中除了基本类型一切都是对象,所以char[]本身也是对象那么就是个引用因此String大小计算:Object(8) + char[](4) + int*3(12) = 24。因此在计算String大小的时候就应该是24+char[]实际的占用量。
6,char[]的计算使用了下面一段代码作为实验,首先Java中一切皆对象,所以推测数组=Object(8) + length(4) + charLength*2。运行和和猜想结果一致。
private static void basicType()
{
//object 8 + length 4 + 2
char[] buffer1 = new char[1];//16
char[] buffer2 = new char[2];//16
char[] buffer3 = new char[3];//24
char[] buffer4 = new char[4];//24
char[] buffer5 = new char[5];//24
char[] buffer6 = new char[6];//24
char[] buffer7 = new char[7];//32
simpleMeasure("A 1 size char buffer value is : ", buffer1);
simpleMeasure("A 2 size char buffer value is : ", buffer2);
simpleMeasure("A 3 size char buffer value is : ", buffer3);
simpleMeasure("A 4 size char buffer value is : ", buffer4);
simpleMeasure("A 5 size char buffer value is : ", buffer5);
simpleMeasure("A 6 size char buffer value is : ", buffer6);
simpleMeasure("A 7 size char buffer value is : ", buffer7);
}
最后总结一下