When---什么时候需要知道对象的内存大小

在内存足够用的情况下我们是不需要考虑java中一个对象所占内存大小的。但当一个系统的内存有限,或者某块程序代码允许使用的内存大小有限制,又或者设计一个缓存机制,当存储对象内存超过固定值之后写入磁盘做持久化等等,总之我们希望像写C一样,java也能有方法实现获取对象占用内存的大小。

How---java怎样获取对象所占内存大小

在回答这个问题之前,我们需要先了解java的基础数据类型所占内存大小。

数据类型 所占空间(byte)
byte     1
short 2
int 4
long 8
float 4
double 8
char   2
boolean 1

当然,java作为一种面向对象的语言,更多的情况需要考虑对象的内存布局,java对于对象所占内存大小需要分两种情况考虑:

对象类型 内存布局构成
一般非数组对象 8个字节对象头(mark) + 4/8字节对象指针 + 数据区 + padding内存对齐(按照8的倍数对齐)
数组对象                                  8个字节对象头(mark) + 4/8字节对象指针 + 4字节数组长度 + 数据区 + padding内存对齐(按照8的倍数对齐)

可以看到数组类型对象和普通对象的区别仅在于4字节数组长度的存储区间。而对象指针究竟是4字节还是8字节要看是否开启指针压缩。Oracle JDK从6 update 23开始在64位系统上会默认开启压缩指针
http://rednaxelafx.iteye.com/blog/1010079。如果要强行关闭指针压缩使用-XX:-UseCompressedOops,强行启用指针压缩使用: -XX:+UseCompressedOops。

接下来我们来举例来看实现java获取对象所占内存大小的方法:

假设我们有一个类的定义如下:

 1     private static class ObjectA {
2 String str; // 4
3 int i1; // 4
4 byte b1; // 1
5 byte b2; // 1
6 int i2; // 4
7 ObjectB obj; //4
8 byte b3; // 1
9 }
10
11 private static class ObjectB {
12
13 }

如果我们直接按照上面掌握的java对象内存布局进行计算,则有:

Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(数据区)
Size(ObjectA) = 8 + 4 + 4(String) + 4(int) + 1(byte) + 1(byte) + 2(padding) + 4(int) + 4(ObjectB指针) + 1(byte) + 7(padding)
Size(ObjectA) = 40

我们直接通过两种获取java对象内存占用大小的方式来验证我们的计算是否正确。

方式1---通过Instrumentation来获取

这种方法得到的是Shallow Size,即遇到引用时,只计算引用的长度,不计算所引用的对象的实际大小。如果要计算所引用对象的实际大小,必须通过递归的方式去计算。
查看jdk的代码发现,Instrumentation是一个接口,本来我想的是可以直接定义一个类实现该接口。但是看了下该接口里面的方法瞬间傻眼。根本没法去重写。
calm down,原来Instrumentation接口的实例需要使用代理的方式来获得。具体步骤如下:

1. 编写 premain 函数

编写一个 Java 类,包含如下两个方法当中的任何一个
public static void premain(String agentArgs, Instrumentation inst); [1]
public static void premain(String agentArgs); [2]
其中,[1] 的优先级比 [2] 高,将会被优先执行([1] 和 [2] 同时存在时,[2] 被忽略)。
在这个 premain 函数中,开发者可以进行对类的各种操作。
agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等。

 1 package instrumentation.test;
2
3 import java.lang.instrument.Instrumentation;
4
5 public class ObjectShallowSize {
6 private static Instrumentation inst;
7
8 public static void premain(String agentArgs, Instrumentation instP){
9 inst = instP;
10 }
11
12 public static long sizeOf(Object obj){
13 return inst.getObjectSize(obj);
14 }
15 }

2. 在META-INF下面新建MANIFEST.MF文件,并且指定

Manifest-Version: 1.0 
Premain-Class: instrumentation.test.ObjectShallowSize

3. 通过eclipse->export->jar->next->next,然后选中定制的 MANIFEST.MF 文件,进行jar打包。

4. 给需要使用ObjectShallowSize的工程引入该jar包,并通过代码测试对象所占内存大小:

1 System.out.println(ObjectShallowSize.sizeOf(new ObjectA())); // 32 
5. 在运行调用ObjectShallowSize.sizeof的类的工程中加上刚打的jar包依赖,同时eclipse里面run configuration,在VM arguments中添加(标红部分为jar包的绝对路径):

-javaagent:E:/software/instrumentation-sizeof.jar

方式2---使用Unsafe来获取

关于Unsafe的使用,后面我会专门开一个专题来详细讲述,这里暂时让我们来见识下Unsafe的神奇之处。

 1     private final static Unsafe UNSAFE;
2 // 只能通过反射获取Unsafe对象的实例
3 static {
4 try {
5 UNSAFE = (Unsafe) Unsafe.class.getDeclaredField("theUnsafe").get(null);
6 } catch (Exception e) {
7 throw new Error();
8 }
9 }
10
11 Field[] fields = ObjectA.class.getDeclaredFields();
12 for (Field field : fields) {
13   System.out.println(field.getName() + "---offSet:" + UNSAFE.objectFieldOffset(field));
14 }

输出结果为:

str---offSet:24
i1---offSet:12
b1---offSet:20
b2---offSet:21
i2---offSet:16
obj---offSet:28
b3---offSet:22

我们同样可以算得对象实际占用的内存大小:

Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(排序后数据区)  =  8 + 4 + (28+4-12)  =  32.

我们再回过头来,看我们在通过代码获取对象所占内存大小之前的预估值40。比我们实际算出来的值多了8个字节。通过Unsafe打印的详细信息,我们不难想到这其实是由hotspot创建对象时的排序决定的:

HotSpot创建的对象的字段会先按照给定顺序排列,默认的顺序为:从长到短排列,引用排最后: long/double –> int/float –> short/char –> byte/boolean –> Reference。

所以我们重新计算对象所占内存大小得:

Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(排序后数据区)
Size(ObjectA) = 8 + 4 + 4(int) + 4(int) + byte(1) + byte(1) + 2(padding) + 4(String) + 4(ObjectB指针)
Size(ObjectA) = 32

与上面计算结果一致。

Deeper---深入分析的一个例子:

以下代码摘抄自原链接:

  1 package test;
2
3 import java.lang.reflect.Array;
4 import java.lang.reflect.Field;
5 import java.lang.reflect.Modifier;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.IdentityHashMap;
11 import java.util.List;
12 import java.util.Map;
13
14 import sun.misc.Unsafe;
15
16 public class ClassIntrospector {
17
18 private static final Unsafe unsafe;
19 /** Size of any Object reference */
20 private static final int objectRefSize;
21 static {
22 try {
23 Field field = Unsafe.class.getDeclaredField("theUnsafe");
24 field.setAccessible(true);
25 unsafe = (Unsafe) field.get(null);
26
27 // 可以通过Object[]数组得到oop指针究竟是压缩后的4个字节还是未压缩的8个字节
28 objectRefSize = unsafe.arrayIndexScale(Object[].class);
29 } catch (Exception e) {
30 throw new RuntimeException(e);
31 }
32 }
33
34 /** Sizes of all primitive values */
35 private static final Map<Class<?>, Integer> primitiveSizes;
36
37 static {
38 primitiveSizes = new HashMap<Class<?>, Integer>(10);
39 primitiveSizes.put(byte.class, 1);
40 primitiveSizes.put(char.class, 2);
41 primitiveSizes.put(int.class, 4);
42 primitiveSizes.put(long.class, 8);
43 primitiveSizes.put(float.class, 4);
44 primitiveSizes.put(double.class, 8);
45 primitiveSizes.put(boolean.class, 1);
46 }
47
48 /**
49 * Get object information for any Java object. Do not pass primitives to
50 * this method because they will boxed and the information you will get will
51 * be related to a boxed version of your value.
52 *
53 * @param obj
54 * Object to introspect
55 * @return Object info
56 * @throws IllegalAccessException
57 */
58 public ObjectInfo introspect(final Object obj)
59 throws IllegalAccessException {
60 try {
61 return introspect(obj, null);
62 } finally { // clean visited cache before returning in order to make
63 // this object reusable
64 m_visited.clear();
65 }
66 }
67
68 // we need to keep track of already visited objects in order to support
69 // cycles in the object graphs
70 private IdentityHashMap<Object, Boolean> m_visited = new IdentityHashMap<Object, Boolean>(
71 100);
72
73 private ObjectInfo introspect(final Object obj, final Field fld)
74 throws IllegalAccessException {
75 // use Field type only if the field contains null. In this case we will
76 // at least know what's expected to be
77 // stored in this field. Otherwise, if a field has interface type, we
78 // won't see what's really stored in it.
79 // Besides, we should be careful about primitives, because they are
80 // passed as boxed values in this method
81 // (first arg is object) - for them we should still rely on the field
82 // type.
83 boolean isPrimitive = fld != null && fld.getType().isPrimitive();
84 boolean isRecursive = false; // will be set to true if we have already
85 // seen this object
86 if (!isPrimitive) {
87 if (m_visited.containsKey(obj))
88 isRecursive = true;
89 m_visited.put(obj, true);
90 }
91
92 final Class<?> type = (fld == null || (obj != null && !isPrimitive)) ? obj
93 .getClass() : fld.getType();
94 int arraySize = 0;
95 int baseOffset = 0;
96 int indexScale = 0;
97 if (type.isArray() && obj != null) {
98 baseOffset = unsafe.arrayBaseOffset(type);
99 indexScale = unsafe.arrayIndexScale(type);
100 arraySize = baseOffset + indexScale * Array.getLength(obj);
101 }
102
103 final ObjectInfo root;
104 if (fld == null) {
105 root = new ObjectInfo("", type.getCanonicalName(), getContents(obj,
106 type), 0, getShallowSize(type), arraySize, baseOffset,
107 indexScale);
108 } else {
109 final int offset = (int) unsafe.objectFieldOffset(fld);
110 root = new ObjectInfo(fld.getName(), type.getCanonicalName(),
111 getContents(obj, type), offset, getShallowSize(type),
112 arraySize, baseOffset, indexScale);
113 }
114
115 if (!isRecursive && obj != null) {
116 if (isObjectArray(type)) {
117 // introspect object arrays
118 final Object[] ar = (Object[]) obj;
119 for (final Object item : ar)
120 if (item != null)
121 root.addChild(introspect(item, null));
122 } else {
123 for (final Field field : getAllFields(type)) {
124 if ((field.getModifiers() & Modifier.STATIC) != 0) {
125 continue;
126 }
127 field.setAccessible(true);
128 root.addChild(introspect(field.get(obj), field));
129 }
130 }
131 }
132
133 root.sort(); // sort by offset
134 return root;
135 }
136
137 // get all fields for this class, including all superclasses fields
138 private static List<Field> getAllFields(final Class<?> type) {
139 if (type.isPrimitive())
140 return Collections.emptyList();
141 Class<?> cur = type;
142 final List<Field> res = new ArrayList<Field>(10);
143 while (true) {
144 Collections.addAll(res, cur.getDeclaredFields());
145 if (cur == Object.class)
146 break;
147 cur = cur.getSuperclass();
148 }
149 return res;
150 }
151
152 // check if it is an array of objects. I suspect there must be a more
153 // API-friendly way to make this check.
154 private static boolean isObjectArray(final Class<?> type) {
155 if (!type.isArray())
156 return false;
157 if (type == byte[].class || type == boolean[].class
158 || type == char[].class || type == short[].class
159 || type == int[].class || type == long[].class
160 || type == float[].class || type == double[].class)
161 return false;
162 return true;
163 }
164
165 // advanced toString logic
166 private static String getContents(final Object val, final Class<?> type) {
167 if (val == null)
168 return "null";
169 if (type.isArray()) {
170 if (type == byte[].class)
171 return Arrays.toString((byte[]) val);
172 else if (type == boolean[].class)
173 return Arrays.toString((boolean[]) val);
174 else if (type == char[].class)
175 return Arrays.toString((char[]) val);
176 else if (type == short[].class)
177 return Arrays.toString((short[]) val);
178 else if (type == int[].class)
179 return Arrays.toString((int[]) val);
180 else if (type == long[].class)
181 return Arrays.toString((long[]) val);
182 else if (type == float[].class)
183 return Arrays.toString((float[]) val);
184 else if (type == double[].class)
185 return Arrays.toString((double[]) val);
186 else
187 return Arrays.toString((Object[]) val);
188 }
189 return val.toString();
190 }
191
192 // obtain a shallow size of a field of given class (primitive or object
193 // reference size)
194 private static int getShallowSize(final Class<?> type) {
195 if (type.isPrimitive()) {
196 final Integer res = primitiveSizes.get(type);
197 return res != null ? res : 0;
198 } else
199 return objectRefSize;
200 }
201 }

下面来分析ObjectC所占内存大小:

 1 package test;
2
3 public class IntrospectorTest {
4 private static class ObjectC {
5 ObjectD[] array = new ObjectD[2];
6 }
7
8 private static class ObjectD {
9 int value;
10 }
11
12 public static void main(String[] args) throws IllegalAccessException {
13 final ClassIntrospector ci = new ClassIntrospector();
14 ObjectInfo res = ci.introspect(new ObjectC());
15 System.out.println( res.getDeepSize() );
16 }
17 }

代码输出为:40。

下面我们来分析下ObjectC的内存布局:

ShallowSize(ObjectC) = Size(对象头) + Size(oop指针) + Size(内容) + Size(对齐)

ShallowSize(ObjectC) = 8 + 4 + 4(ObjectD[]数组引用) =16

Size(ObjectD[] arr) = 8(数组对象头) + 4(oop指针) + 4(数组长度) + 4(ObjectD[0]对象引用) + 4(ObjectD[1]对象引用) = 24

因为arr没有具体赋值,所以此时具体引用的为null,不占用内存。否则需要再次计算ObjectD的内存最后想加。

所以总共得到:Size(ObjectC) = ShallowSize(ObjectC) + Size(ObjectD[] arr)  = 40。

参考链接:

http://blog.csdn.net/antony9118/article/details/54317637
https://www.cnblogs.com/licheng/p/6576644.html

java如何获取一个对象的大小【转】的更多相关文章

  1. java如何获取一个对象的大小

    When---什么时候需要知道对象的内存大小 在内存足够用的情况下我们是不需要考虑java中一个对象所占内存大小的.但当一个系统的内存有限,或者某块程序代码允许使用的内存大小有限制,又或者设计一个缓存 ...

  2. JAVA中获取文件的大小和文件的扩展名

    一.获取文件扩展名(该段代码来自博客园网站装男人的博客https://www.cnblogs.com/nanrenzhuang/archive/2013/05/19/6315546.html) pub ...

  3. 获取JAVA对象占用的内存大小

    介绍两种获取JAVA对象内存大小的方法. 第一种:Instrumentation 简介: 使用java.lang.instrument 的Instrumentation来获取一个对象的内存大小.利用I ...

  4. 如何获取一个Java对象所占内存大小

    新建一个maven工程 我们先在IDEA中新建一个名为ObjectSizeFetcherAgent的maven工程,如下图: 在maven项目中的pom.xml中新增一个打jar包的插件,如下: &l ...

  5. Java获取视频的大小、时长

    前端上传视频之后,根据上传的视频文件获取视频的大小和时长 1.获取视频时长 private String ReadVideoTime(File source) { Encoder encoder = ...

  6. jvm大局观之内存管理篇(二):当java中new一个对象,背后发生了什么

    https://zhuanlan.zhihu.com/p/257863129?utm_source=ZHShareTargetIDMore 番茄番茄我是西瓜 那是我日夜思念深深爱着的人啊~ 已关注   ...

  7. 无废话Android之android下junit测试框架配置、保存文件到手机内存、android下文件访问的权限、保存文件到SD卡、获取SD卡大小、使用SharedPreferences进行数据存储、使用Pull解析器操作XML文件、android下操作sqlite数据库和事务(2)

    1.android下junit测试框架配置 单元测试需要在手机中进行安装测试 (1).在清单文件中manifest节点下配置如下节点 <instrumentation android:name= ...

  8. Java如何获取系统cpu、内存、硬盘信息

    1 概述 前段时间摸索在Java中怎么获取系统信息包括cpu.内存.硬盘信息等,刚开始使用Java自带的包进行获取,但这样获取的内存信息不够准确并且容易出现找不到相应包等错误,所以后面使用sigar插 ...

  9. Java API获取topic所占磁盘空间(Kafka 1.0.0)

    很多用户都有这样的需求:实时监控某个topic各分区在broker上所占的磁盘空间大小总和.Kafka并没有提供直接的脚本工具用于统计这些数据. 如果依然要实现这个需求,一种方法是通过监控JMX指标得 ...

随机推荐

  1. Asp.Net SignalR GlobalHost外部通知

    GlobalHost 外部通知 之前都是在集线器类中进行服务器对客户端的通知操作,但是在开发中往往会有需求监控某个系统 ,比如OA系统  上级领导在上面宣布下午两点要开会 那么就要通知到其他的人.这里 ...

  2. 【ASP.NET Core快速入门】(七)WebHost的配置、 IHostEnvironment和 IApplicationLifetime介绍、dotnet watch run 和attach到进程调试

    WebHost的配置 我们用vs2017新建一个空网站HelloCore 这里的CreateDefaultBuilde实际上已经在内部替我们做好了默认配置. UseKestrel 使用kestrel ...

  3. docker删除镜像和删除容器

    删除容器:docker rm ID 删除镜像:docker rmi ID

  4. 深度学习框架Keras介绍及实战

    Keras 是一个用 Python 编写的高级神经网络 API,它能够以 TensorFlow, CNTK, 或者 Theano 作为后端运行.Keras 的开发重点是支持快速的实验.能够以最小的时延 ...

  5. F#周报2019年第8期

    新闻 Fable 2.2发布,支持匿名记录 Paket提升还原时间 Microsoft.Jupyter.Core预览 .NET Framework 4.8早期可访问编译版本3745 博客 使用SAFE ...

  6. xamarin.forms之使用CarouselView插件模仿网易新闻导航

    在APP中基本都能见到类似网易.今日头条等上边横向导航条,下边是左右滑动的页面,之前做iOS的时候模仿实现过,https://github.com/ywcui/ViewPagerndicator,在做 ...

  7. (摘)Entity Framework Core 2.1带来更好的SQL语句生成方案

    微软发布了Entity Framework Core2.1,为EF开发者带来了很多期待已久的特性.EF Core 2.1增加了对SQL GROUP BY的支持,支持延迟加载和数据种子等. EF Cor ...

  8. 使用Jenkins自动发布Windows服务项目

    不同于发布Web项目,自动发布Windows服务项目需要解决以下几个问题: 如何远程停止和开启服务?需要在发布前停止服务,在发布完成后开启服务. 如何上传编译文件到目标服务器? 问题1:如何远程停止和 ...

  9. ios手机录屏软件哪个好

    苹果手机中的airplay镜像,是苹果手机系统的一大特色,可以轻松把手机屏幕投射电脑,这个功能使苹果手机相较安卓手机投屏会更加轻松,那么如何实现苹果手机投射电脑屏幕?下面小编便来分享ios手机录屏软件 ...

  10. jquery获取内容和属性的方法

    通过jquery如何捕获文本内容和属性? text(),html(),val()及attr(). attr()更具有普遍性,元素text属性和表单value属性,可以通过attr()操作. <! ...