Java对象的大小

 基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。

    在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。看下面语句:

Object ob = new Object();

    这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是上面部分所说的Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。

   有了Object对象的大小,我们就可以计算其他对象的大小了。

Class NewObject {

    int count;

    boolean flag;

    Object ob;

}

    其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。

    这里需要注意一下基本类型的包装类型的大小。因为这种包装类型已经成为对象了,因此需要把他们作为对象来看待。包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化。

引用类型

    对象引用类型分为强引用、软引用、弱引用和虚引用

 

强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收

 

软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

 

弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

 

    强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。


有时计算java对象在内存中的大小是必要的。这篇文章将介绍一种借助于Java Instrumentation API的方法。

适用场景

计算java对象大小适用于如下场景

  • 缓存 缓存通常用来提高频繁访问数据的性能。由于受到Java进程可分配内存大小的限制,它们通常不能从数据库(或者其他存储)中加载所有数据。也就是说,为了确保其内存大小不超过预设,缓存必须计算已加载数据的大小并且丢弃旧数据。
  • 检测内存泄漏 在某些情况下你能在存在泄漏的指令的前后量测堆内存的大小并发现内存泄漏。如果你怀疑某些对象存在泄漏,你需要精确量测他们的大小并和泄漏的内存进行比较。有许多专门的大工具用可供使用,但是他们通常都太重量级了,并影响性能。在某些情况下如果有一种简单的对象大小计算方法,你能够更快的解决问题。
  • 其他内存估算 例如,你能估算JVM最大堆内存的设置,如果你知道有多少对象将在你的应用程序中创建。
  • 纯属娱乐:)

 

常见方法概述

有几种不同的方法可用于确定java对象的大小。他们大多是在JDK 5.0之前已经出现。

  • http://jroller.com/page/mipsJava?entry=sizeof_java_objects -使用System.gc(),Runtime.freeMemory(), Runtime.totalMemory()方法来计算java对象的大小。这个方法通常需要许多资源才能精确计算出对象的大小。它必须创建许多的需要估算对象的实例(最好是几千个),在创建的前后量测堆内存的大小。这个方法对于使用缓存机制的生产系统并不奏效。这个方法的优点是可以得到较为精确的结果,而不受Java实现版本和操作系统的影响。
  • 另一个更好的方法:http://www.javaspecialists.co.za/archive/Issue078.html - 它更加的巧妙。他使用真实的原始类型大小的对照表来确定整个对象的大小。使用反射API遍历对象继承链上的成员变量并且计算所有原始类型变量的大小。这个方法不像上一方法那样需要很多的资源并能够用于缓存机制。弊端是原始类型大小的对照表会随着JVM实现版本的不同而不同,对于不同的实现版本需要重新计算。

 

下面是一些关于类似方法的文章:

http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
http://www.javaworld.com/javaworld/javaqa/2003-12/02-qa-1226-sizeof.html
http://www.javapractices.com/Topic83.cjp
http://forum.java.sun.com/thread.jspa?threadID=565721&messageID=2790847

使用Instrumentation API确定java对象大小

从JDK 5.0开始,新引入的 Instrumentation API 终于提供了 getObjectSize 方法。但是使用这个方法有两个问题:

 

  1. 这个方法不能直接使用,必须实现一个instrumentation代理类并且打包进JAR文件。
  2. 它仅返回某个对象的大小而不包括其成员变量所引用的对象。

 

这些问题很容易被解决。在任何类中,可以通过声明premain方法实现Java代理类:

 

 
1: public class SizeOfAgent {
2: 
3:         static Instrumentation inst;
4:         
5:         /** initializes agent */
6:         public static void premain(String agentArgs, Instrumentation instP) {
7:                 inst = instP;           
8:         }
9: }
permain方法会在启动的时候被JVM调用, Instrumentation的实例被传入。SizeOfAgent类使用静态变量保存 Instrumentation对象的引用。为了让JVM知道instrumentation代理类的存在,必须将其打包进JAR文件并且设定manifest.mf文件中的属性。在我们的例子中,需要设定如下属性:
    Premain-Class: sizeof.agent.SizeOfAgent
    Boot-Class-Path:
    Can-Redefine-Classes:
 false
另外,Java程序必须使用 -javaagent 参数指向该jar文件来启动。我们的例子中形如:
    java -javaagent:sizeofag.jar <Your main class>
在获得了 Instrumentation对象的引用之后,实现一个 sizeOf方法变得很简单。
 
 
 
 
1: public class SizeOfAgent {
2: 
3:         static Instrumentation inst;
4:         
5:         // ...
6:                 
7:         public static long sizeOf(Object o) {
8:                 return inst.getObjectSize(o);
9:         }
10: }

     SizeOgAgent.sizeOf()方法能方便的被你的程序所调用。如前面所提到的,这个方法仅返回某个对象的大小而不包括其成员变量。完整的对象大小能通过反射得到。我们能通过简单的递归遍历所有的成员变量来总计他们的大小。并不是所有人都知道能够通过反射访问private和protected变量。你仅仅需要在获得private成员之前调用  Field.setAccessible(true) 方法即可。下面是 SizeOfAgent类中 fullSizeOf方法完整实现的源码:
 
 
001: package sizeof.agent;
002: 
003: import java.lang.instrument.Instrumentation;
004: import java.lang.reflect.Array;
005: import java.lang.reflect.Field;
006: import java.lang.reflect.Modifier;
007: import java.util.IdentityHashMap;
008: import java.util.Map;
009: import java.util.Stack;
010: 
011: /** Instrumentation agent used */
012: public class SizeOfAgent {
013: 
014:         static Instrumentation inst;
015:         
016:         /** initializes agent */
017:         public static void premain(String agentArgs, Instrumentation instP) {
018:                 inst = instP;           
019:         }
020:         
021:         /**
022:          * Returns object size without member sub-objects.
023:          * @param o object to get size of
024:          * @return object size
025:          */
026:         public static long sizeOf(Object o) {
027:                 if(inst == null) {
028:                         throw new IllegalStateException("Can not access instrumentation environment.n" +
029:                                         "Please check if jar file containing SizeOfAgent class is n" +
030:                                         "specified in the java's "-javaagent" command line argument.");
031:                 }
032:                 return inst.getObjectSize(o);
033:         }
034:         
035:         /**
036:          * Calculates full size of object iterating over
037:          * its hierarchy graph.
038:          * @param obj object to calculate size of
039:          * @return object size
040:          */
041:         public static long fullSizeOf(Object obj) {
042:                 Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
043:                 Stack<Object> stack = new Stack<Object>();
044: 
045:             long result = internalSizeOf(obj, stack, visited);
046:             while (!stack.isEmpty()) {
047:               result += internalSizeOf(stack.pop(), stack, visited);
048:             }
049:             visited.clear();
050:             return result;
051:         }               
052:           
053:     private static boolean skipObject(Object obj, Map<Object, Object> visited) {
054:             if (obj instanceof String) {
055:               // skip interned string
056:               if (obj == ((String) obj).intern()) {
057:                 return true;
058:               }
059:             }
060:             return (obj == null) // skip visited object
061:                 || visited.containsKey(obj);
062:          }
063: 
064:     private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
065:             if (skipObject(obj, visited)){
066:                 return 0;
067:             }
068:             visited.put(obj, null);
069:             
070:             long result = 0;
071:             // get size of object + primitive variables + member pointers 
072:             result += SizeOfAgent.sizeOf(obj);
073:             
074:             // process all array elements
075:             Class clazz = obj.getClass();
076:             if (clazz.isArray()) {
077:               if(clazz.getName().length() != 2) {// skip primitive type array
078:                   int length =  Array.getLength(obj);
079:                           for (int i = 0; i < length; i++) {
080:                                   stack.add(Array.get(obj, i));
081:                       } 
082:               }       
083:               return result;
084:             }
085:             
086:             // process all fields of the object
087:             while (clazz != null) {
088:               Field[] fields = clazz.getDeclaredFields();
089:               for (int i = 0; i < fields.length; i++) {
090:                 if (!Modifier.isStatic(fields[i].getModifiers())) {
091:                   if (fields[i].getType().isPrimitive()) {
092:                           continue; // skip primitive fields
093:                   } else {
094:                     fields[i].setAccessible(true);
095:                     try {
096:                       // objects to be estimated are put to stack
097:                       Object objectToAdd = fields[i].get(obj);
098:                       if (objectToAdd != null) {                        
099:                         stack.add(objectToAdd);
100:                       }
101:                     } catch (IllegalAccessException ex) { 
102:                         assert false; 
103:                     }
104:                   }
105:                 }
106:               }
107:               clazz = clazz.getSuperclass();
108:             }
109:             return result;
110:          }
111: }
基本思想类似于Dr. Heinz M. Kabutz的方法: http://www.javaspecialists.co.za/archive/Issue078.html.我甚至重用了他的skipObject方法。该算法用来保证每个对象仅被统计一次,防止循环引用。另外它忽略了intern类型的String(详细参加  String.intern())。
缺点
这个方法的主要缺点是不能用于沙箱环境类似于applet或者Web Start程序。这个限制是因为通过反射访问私有成员的方法和instrumentation代理在沙箱环境中无效。
文件
文件  sizeofag.jar 包含编译好的class文件和java源码。你可以仅仅将sizeofag.jar通过-javaagent参数添加进JVM中,在你的程序中像使用普通类一样使用SizeOfAgent类。好好享受它吧:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值