如何精确地测量java对象的大小-底层instrument API
转载: 如何精确地测量java对象的大小-底层instrument API
关于java对象的大小测量,网上有很多例子,大多数是申请一个对象后开始做GC,后对比前后的大小,不过这样,虽然说这样测量对象的大小是可行的,不过未必是完全准确的,因为过程中包含对象本身的开销,也许你运气好,正好能碰上,差不多,不过这种测试往往显得十分的笨重,因为要写一堆代码才能测试一点点东西,而且只能在本地测试玩玩,要真正测试实际的系统的对象大小这样可就不行了,本文说说java一些比较偏底层的知识,如何测量对象大小,java其实也是有提供方法的。注意:本文的内容仅仅针对于Hotspot VM,如果你以前不知道jvm的对象大小怎么测量,而又很想知道,跟我一步一步做一遍你就明白了。
首先,我们先写一段大家可能不怎么写或者认为不可能的代码:一个类中,几个类型都是private类型,没有public方法,如何对这些属性进行读写操作,看似不可能哦,为什么,这违背了面向对象的封装,其实在必要的时候,留一道后面可以使得语言的生产力更加强大,对象的序列化不会因为没有public方法就无法保存成功吧,OK,我们简单写段代码开个头,逐步引入到怎么样去测试对象的大小,一下代码非常简单,相信不用我解释什么:
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本身有8直接头部,所以占用了24byte;其次,一个String包含了子对象char数组,数组对象和普通对象的区别是需要用一个字段来保存数组的长度,所以头部变成12字节,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 = 0; i < length; i++) {
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;
}
}
OK,通过上面已经可以看出,保持了原有方法,因为深度递归毕竟比较慢,我们有些时候可以选择到底用那一种:
回到步骤重新做一次:
1、编译agent
2、打包class,并修改META-INF/MANIFEST.MF文件中增加一行:Premain-Class: MySizeOf
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.fullSizeOf(new String("a")));
System.out.println(MySizeOf.sizeOf(new char[1]));
}
}
4、设置环境变量开始运行(如果已经设置好了就无需重复设置):
D:\>javac TestSize.java
D:\>java -javaagent:agent.jar TestSize
16
24
40
16
这个结果是我们想要的了,看来这个测试是靠谱的,面对理论和测试结果,以及上面所谓的对齐方法,大家可以自己编写一些类的对象来测试大小看时候和实际的保持一致;
最后,文章补充一些:
1、对象采用8字节对齐的方式是不论32bit还是64bit都是一样的
2、java在64bit模式下开启指针压缩,比32bit模式下,头部会大4byte(_mark区域变成8byte,_class区域被压缩),如果没有开启指针压缩,头部会大8byte(_mark和_class都会变成8byte),jdk1.6推出参数-XX:+UseCompressedOops,在32G内存一下默认会自动打开这个参数,如下:
[xieyu@oracle001 ~]$ java -Xmx31g -XX:+PrintFlagsFinal |grep Compress
bool SpecialStringCompress = true {product}
bool UseCompressedOops := true {lp64_product}
bool UseCompressedStrings = false {product}
[xieyu@oracle001 ~]$ java -Xmx32g -XX:+PrintFlagsFinal |grep Compress
bool SpecialStringCompress = true {product}
bool UseCompressedOops = false {lp64_product}
bool UseCompressedStrings = false {product}
简单计算一个,在指针压缩的情况下,一个new String("a");这个对象的空间大小为:12字节头部+4*4 = 28字节对齐到32字节,然后c所指向的char数组头部比普通对象多4个byte来存放长度,12+4+2byte的字符=16,也就是48个byte,其实即使你new String()也会占这么大的空间,因为有对齐,如果字符的长度是8个,那么就是12+4+16=32,也就是有64byte;
如果不开启指针压缩再算算:头部变成16byte + 4*3个int数据 + 8(1个指针) = 36对齐到40byte,对应的char数组的头部变成16+4 + 2 = 22对齐到24byte,40+24=64,也就是只有一个字符或者0个字符都会对齐到64byte,所以,你懂的,参数该怎么调,代码该怎么写,如果长度为8个字符的那么后面部分就会变成16+4+16=36对齐到40byte,40+40=80byte,也就是说,抛开其他的引用空间(比如通过数组或集合类引用),如果你有10来个String,每个大小就装8个字符,就会有1K的大小,你的代码里头有多少?呵呵!
这些不是我说的,这些是一种计算方法,而且这个计算结果只会少不会多,因为代码运行过程中,一些对象的头部会伸展,_mark区域装不下会用外部的空间来存放,所以官方给出的说明也是,最少会占用多少字节,绝对不会说只占用多少字节。
OK,说得挺吓人的,不过写代码还是不要怕,不过就这些而言,只是说明java是如何浪费空间的,不要一味使用一些高级的东西,在必要的时候,考虑性能还是有很大的空间,类似集合类以及多维数组,前面的引用其实和数据一点关系都没有,但是占用的空间比数据本身都要大很多。
本文只是通过一种方式让大家知道如何去测量对象大小,同时知道一个java对象如何开销内存,开销而且很大,所以回过头来说,即使java并不看重性能和空间,不过如果你的代码写得好同样会跑得更加快。
如何精确地测量java对象的大小-底层instrument API的更多相关文章
- JVM —— Java 对象占用空间大小计算
零. 为什么要知道 Java 对象占用空间大小 缓存的实现: 在设计 JVM 内缓存时(不是借助 Memcached. Redis 等), 须要知道缓存的对象是否会超过 JVM 最大堆限制, 假设会超 ...
- 如何准确计算Java对象的大小
如何准确计算Java对象的大小 原创文章,转载请注明:博客园aprogramer 原文链接:如何准确计算Java对象的大小 有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两 ...
- 计算Java对象内存大小
摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍,涉及内存模型.锁原理 ...
- Ehcache计算Java对象内存大小
在EHCache中,可以设置maxBytesLocalHeap.maxBytesLocalOffHeap.maxBytesLocalDisk值,以控制Cache占用的内存.磁盘的大小(注:这里Off ...
- 如何查看java对象的大小
有时需要查看java对象占用了多少内存(对象大小),lucene为我们提供了一个很好的工具类,操作简单,如下: int[] s = new int[1024]; System.out.println( ...
- Java对象的大小及应用类型
基础类型数据的大小是固定的,对于非基本类型的java对象,其大小就值得商榷了. 在java中一个空Object对象的大小是8byte,这个大小只是保存堆中没有任何属性的对象的大小,看下面的语 ...
- java对象内存大小评估
Java对象的内存布局:对象头(Header).实例数据(Instance Data)和对齐填充(Padding).无论是32位还是64位的HotSpot,使用的都是8字节对齐.也就是说每个java对 ...
- Java虚拟机14:Java对象大小、对象内存布局及锁状态变化
一个对象占多少字节? 关于对象的大小,对于C/C++来说,都是有sizeof函数可以直接获取的,但是Java似乎没有这样的方法.不过还好,在JDK1.5之后引入了Instrumentation类,这个 ...
- Java虚拟机18:Java对象大小、对象内存布局及锁状态变化
一个对象占多少字节? 关于对象的大小,对于C/C++来说,都是有sizeof函数可以直接获取的,但是Java似乎没有这样的方法.不过还好,在JDK1.5之后引入了Instrumentation类,这个 ...
随机推荐
- response.sendRedirect()重新定向的乱码问题
这里response.sendRedirect("YPbianhaoModify.jsp?jinhuoshang="+jinhuoshang+"&jinhuori ...
- jquery之insertBefore(),insertAfter(),prependTo(),appendTo()用法详解
导航: 1,insertBefore(),insertAfter(),prependTo(),appendTo()这四个函数用法几乎一样 2, 与之相对的有四个函数:Before(),After(), ...
- POSTMAN中各种请求方式的区别
1.form-data: 就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ...
- HTMl中Meta标签详解以及meta property=og标签含义
meta是用来在HTML文档中模拟HTTP协议的响应头报文.META标签是HTML语言HEAD区的一个辅助性标签,它位于HTML文档头部的<HEAD>标记和<TITLE>标记之 ...
- VBA中方法的函数式调用和过程式调用的差别
因见到有人求助批量设置工作簿中的超链接,尝试写了一段代码: Sub AddHyperlinks() Dim strName As String, source As String, target As ...
- CUBRID学习笔记 32 对net的datatable的支持 cubrid教程
在net的驱动中实现理一下的支持 DataTable data populate Built-in commands construct: INSERT , UPDATE, DELETE Column ...
- CUBRID学习笔记 10 数据库文件的类型和含义
demodb contains the database data; demodb_lgar000, 001 and so forth are log archives used for point ...
- hdu 1874(Dijkstra + Floyd)
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1874 畅通工程续 Time Limit: 3000/1000 MS (Java/Others) Me ...
- STL--map
map--概述: 映射(Map)和多重映射(Multimap)是基于某一类型Key的键集的存在,提供对TYPE类型的数据进行快速和高效的检索. l对Map而言,键只是指存储在容器中的某一成员. lMu ...
- iOS - Sign up/in 注册/登录
1.Sign up/in 1.1 用户登录安全原则 不能在网络上传输用户隐私数据的明文. 不能在本地和服务器上存储用户隐私数据的明文. 1.2 用户登录流程 登录成功之后,应该跳转视图控制器到主页. ...