基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的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实现版本的不同而不同,对于不同的实现版本需要重新计算。
下面是一些关于类似方法的文章:
使用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: }
Boot-Class-Path:
Can-Redefine-Classes: false
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: }
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: }