import java.lang.reflect.Field
class NodeTest1 {
private int a = 13
private int b = 21
}
public class Test001 {
public static void main(String []args) {
NodeTest1 node = new NodeTest1()
Field []fields = NodeTest1.class.getDeclaredFields()
for(Field field : fields) {
field.setAccessible(true)
try {
int i = field.getInt(node)
field.setInt(node, i * 2)
System.out.println(field.getInt(node))
} catch (IllegalArgumentException e) {
e.printStackTrace()
} catch (IllegalAccessException e) {
e.printStackTrace()
}
}
}
}
代码最基本的意思就是:实例化一个NodeTest1这个类的实例,然后取出两个属性,分别乘以2,然后再输出,相信大家会认为这怎么可能,NodeTest1根本没有public方法,代码就在这里,将代码拷贝回去运行下就OK了,OK,现在不说这些了,运行结果为:
26
42
为什么可以取到,是每个属性都留了一道门,主要是为了自己或者外部接入的方便,相信看代码自己仔细的朋友,应该知道门就在:field.setAccessible(true)代表这个域的访问被打开,好比是一道后门打开了,呵呵,上面的方法如果不设置这个,就直接报错。
看似和对象大小没啥关系,不过这只是抛砖引玉,因为我们首先要拿到对象的属性,才能知道对象的大小,对象如果没有提供public方法我们也要知道它有哪些属性,所以我们后面多半会用到这段类似的代码哦!
对象测量大小的方法关键为java提供的(1.5过后才有):java.lang.instrument.Instrumentation,它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API(本文只阐述对象大小的测量方法),于是乎我心喜了,不过比较恶心的是它是实例化类:sun.instrument.IntrumentationImpl是sun开头的,这个鬼东西有点不好搞,翻开源码构造方法是private类型,没有任何getInstance的方法,写这个类干嘛?看来这个只能被JVM自己给初始化了,那么怎么将它自己初始化的东西取出来用呢,唯一能想到的就是agent代理,那么我们先抛开代理,首先来写一个简单的对象测量方法:
步骤1:(先创建一个用于测试对象大小的处理类)
import java.lang.instrument.Instrumentation
public class MySizeOf {
private static Instrumentation inst
/**
*这个方法必须写,在agent调用时会被启用
*/
public static void premain(String agentArgs, Instrumentation instP) {
inst = instP
}
/**
* 直接计算当前对象占用空间大小,包括:当前类及超类的基本类型实例字段大小
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小
* 用来测量java对象的大小(这里先理解这个大小是正确的,后面再深化)
*/
public static long sizeOf(Object o) {
if(inst == null) {
throw new IllegalStateException("Can not access instrumentation environment.\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument.")
}
return inst.getObjectSize(o)
}
}
步骤2:上面我们写好了agent的代码,此时我们要将上面这个类编译后打包为一个jar文件,并且在其包内部的META-INF/MANIFEST.MF文件中增加一行:Premain-Class: MySizeOf代表执行代理的全名,这里的类名称是没有package的,如果你有package,那么就写全名,我们这里假设打包完的jar包名称为agent.jar(打包过程这里简单阐述,就不细说了),OK,继续向下走:
步骤3:编写测试类,测试类中写:
public class TestSize {
public static void main(String []args) {
System.out.println(MySizeOf.sizeOf(new Integer(1)))
System.out.println(MySizeOf.sizeOf(new String("a")))
System.out.println(MySizeOf.sizeOf(new char[1]))
}
}
下一步准备运行,运行前我们准备初步估算下结果是什么,目前我是在32bit模式下运行jvm(注意,不同位数的JVM参数设置不一样,对象大小也不一样大)。
(1) 首先看Integer对象,在32bit模式下,class区域占用4byte,mark区域占用最少4byte,所以最少8byte头部,Integer内部有一个int类型的数据,占4个byte,所以此时为8+4=12,java默认要求按照8byte对象对其,所以对其到16byte,所以我们理论结果第一个应该是16;
(2) 再看String,长度为1,String对象内部本身有4个非静态属性(静态属性我们不计算空间,因为所有对象都是共享一块空间的),4个非静态属性中,有offset、count、hash为int类型,分别占用4个byte,char value[]为一个指针,指针的大小在bit模式下或64bit开启指针压缩下默认为4byte,所以属性占用了16byte,String本身有8byte头部,所以占用了24byte;其次,一个String包含了子对象char数组,数组对象和普通对象的区别是需要用一个字段来保存数组的长度,所以头部变成12byte,java中一个char采用UTF-16编码,占用2个byte,所以是14byte,对其到16byte,24+16=40byte;
(3) 第三个在第二个基础上已经分析,就是16byte大小;
也就是理论结果是:16、40、16;
步骤4:现在开始运行代码:运行代码前需要保证classpath把刚才的agent.jar包含进去:
D:>javac TestSize.java
D:>java -javaagent:agent.jar TestSize
16
24
16
第一个和第三个结果一致了,不过奇怪了,第二个怎么是24,不是40,怎么和理论结果偏差这么大,再回到理论结果中,有一个24曾经出现过,24是指String而不包含char数组的空间大小,那么这么算还真是对的,可见,java默认提供的方法只能测量对象当前的大小,如果要测量这个对象实际的大小(也就是包含了子对象,那么就需要自己写算法来计算了,最简单的方法就是递归,不过递归一项是我不喜欢用的,无意中在一个地方看到有人用栈写了一个代码写得还不错,自己稍微改了下,就是下面这种了)。
import java.lang.instrument.Instrumentation
import java.lang.reflect.Array
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.IdentityHashMap
import java.util.Map
import java.util.Stack
public class MySizeOf {
static Instrumentation inst
public static void premain(String agentArgs, Instrumentation instP) {
inst = instP
}
public static long sizeOf(Object o) {
if(inst == null) {
throw new IllegalStateException("Can not access instrumentation environment.\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument.")
}
return inst.getObjectSize(o)
}
/**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
*/
public static long fullSizeOf(Object obj) {//深入检索对象,并计算大小
Map<Object, Object>visited = new IdentityHashMap<Object, Object>()
Stack<Object>stack = new Stack<Object>()
long result = internalSizeOf(obj, stack, visited)
while (!stack.isEmpty()) {//通过栈进行遍历
result += internalSizeOf(stack.pop(), stack, visited)
}
visited.clear()
return result
}
//判定哪些是需要跳过的
private static boolean skipObject(Object obj, Map<Object, Object>visited) {
if (obj instanceof String) {
if (obj == ((String) obj).intern()) {
return true
}
}
return (obj == null) || visited.containsKey(obj)
}
private static long internalSizeOf(Object obj, Stack<Object>stack, Map<Object, Object>visited) {
if (skipObject(obj, visited)) {//跳过常量池对象、跳过已经访问过的对象
return 0
}
visited.put(obj, null)//将当前对象放入栈中
long result = 0
result += sizeOf(obj)
Class <?>clazz = obj.getClass()
if (clazz.isArray()) {//如果数组
if(clazz.getName().length() != 2) {// skip primitive type array
int length = Array.getLength(obj)
for (int i = 0i <lengthi++) {
stack.add(Array.get(obj, i))
}
}
return result
}
return getNodeSize(clazz , result , obj , stack)
}
//这个方法获取非数组对象自身的大小,并且可以向父类进行向上搜索
private static long getNodeSize(Class <?>clazz , long result , Object obj , Stack<Object>stack) {
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields()
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {//这里抛开静态属性
if (field.getType().isPrimitive()) {//这里抛开基本关键字(因为基本关键字在调用java默认提供的方法就已经计算过了)
continue
}else {
field.setAccessible(true)
try {
Object objectToAdd = field.get(obj)
if (objectToAdd != null) {
stack.add(objectToAdd)//将对象放入栈中,一遍弹出后继续检索
}
} catch (IllegalAccessException ex) {
assert false
}
}
}
}
clazz = clazz.getSuperclass()//找父类class,直到没有父类
}
return result
}
}