一种Java中对象实例占用堆空间衡量方法

本文的方法是利用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[]实际的占用量。

String

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);
    }

最后总结一下

  • 一个Java对象首先是一个Object,因此8个字节已占据。
  • 基本类型的变量(field)占用空间可以从表中查出,这些类型是传值的没有引用的开销。
  • 非基本类型的变量除了变量本身占用还要计算至少一个引用的开销,如果Java对象中多个引用指向一个对象就要计算多次引用。
  • 数组也是对象,包括Object的8个字节+length变量的四个字节,以及根据数组类型计算的内存占用,如果是非基本类型的数组还要计算引用的开销length * 4(已验证,略过)。
  • 一个复杂的Java对象的空间占用是递归计算的,即对象类型本身->父类->祖先类->Object[end]。

你可能感兴趣的:(一种Java中对象实例占用堆空间衡量方法)