如何确定Java对象的大小

转自:http://article.yeeyan.org/view/104091/62930

再议如何确定Java对象的大小
vangie于2009-10-10 22:42:28翻译 | 已有2346人浏览 | 有0人评论

在JDK 5.0之前,想要精确的计算Java对象在内存中的的大小并不容易,要么使用原始的统计方法,要么使用很影响性能的大型工具。而Instrumentation API可以借助于Java原生方法很好的解决这一问题。

Tags:Instrumentation | getObjectSize
 
再议如何确定Java对象的大小

有时计算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 方法。但是使用这个方法有两个问题:



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


这些问题很容易被解决。在任何类中,可以通过声明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:  MapObject, Object> visited = new IdentityHashMapObject, Object>();
043:  StackObject> stack = new StackObject>();
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, MapObject, 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, StackObject> stack, MapObject, 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类。好好享受它吧:)

你可能感兴趣的:(java)