准确计算Java中对象的大小
由于在项目中需要大致计算一下对象的内存占用率(Hadoop中的Reduce端内存占用居高不下却又无法解释),因此深入学习了一下如何准确计算对象的大小。
使用system.gc()和java.lang.Runtime类中的freeMemory(),totalMemory(),maxMemory()这几个方法测量Java对象的大小,这种方法的优点是数据类型大小无关的,不同的操作系统,都可以得到占用的内存,但经常我们手动调用的GC并未起到预期的效果,计算得不够精确。
又有人想将对象进行序列化之后的byte[]输出,根据这个大小得到对象在内存中的大小,这种方法是错误的,序列化的结果是种特定格式的数据,这种格式在多种JVM之间兼容,但是这种格式的数据在内存中占用的空间大小与序列化后的结果无关。
参考了几个大牛们的blog,例如:
http://www.cnblogs.com/yangjiandan/p/3534781.html
http://happyqing.iteye.com/blog/2013639
http://article.yeeyan.org/view/104091/62930
有些时候还是需要自己动手来观察一下所有的对象在内存中的状态,还是优先考虑使用Instrument的方式,但是Instrument方式仅返回某个对象的大小而不包括其成员变量所引用的对象。
Instrumentation.getObjectSize()会计算的对象中的基本类型,以及引用的长度,包括数组,但不会计算其中包含的对象类型里面的对象类型内容(会计算其中内部的基本类型)。
这种方式要求创建一个带有public static void premain(String[] args, Instrumentation inst)方法,不能直接在IDE中直接调用该方法,只能通过构建jar包的方式,MANIFEST.MF中加入这一行:
Premain-Class: com.clamaa.serialization.test.SizeOfObject
使用下面的方式进行调用:
JVM在调用时注入的执行类时,会调用到premain方法,传入Instrumentation对象,这时就可以使用Instrumentation.getObjectSize(Object)来计算对象占用的内存大小。
我这里直接使用了maven的方式进行调用,在maven构建时指定加入对应的MANIFEST.MF模版文件。
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<appendAssemblyId>true</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestFile>src/main/java/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
在本机运行时,由于是64位JVM,对比一下使用参数:XX+(-)UseCompressedOops。
在启用指针压缩后,测试的一些数据结果:
Bytes used by object: 16
Bytes used by int 2120121: 16
Bytes used by Integer 202323 : 16
Bytes used by new byte[3] : 24
Bytes used by new byte[30] : 48
Bytes used by string a : 24
Bytes used by string aaaabcsdsd : 24
Bytes used by new Object[100] : 416
Bytes used by new HashMap(100) : 48
Bytes used by new HashMap(1000) : 48
可以看到对应String,HashMap对象,无论其内容多大,用Instrument计算出来的只是对象的大小,但是数组不同,随着数组的大小增大,其内存占用率提高很大。
对象的大小如何计算?这篇blog讲的非常好:
http://www.cnblogs.com/magialmoon/p/3757767.html
原生类型(primitive type)的内存占用如下:
| Primitive Type | Memory Required(bytes) |
| boolean | 1 |
| byte | 1 |
| short | 2 |
| char | 2 |
| int | 4 |
| float | 4 |
| long | 8 |
| double | 8 |
reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。
在Hotspot中对象数据的计算方式:
(对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8
这里就不再赘述如何计算单个对象引用的方式,可以查看上面介绍的blog。
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set; /**
* 对象占用字节大小工具类
*
* @author tianmai.fh
* @date 2014-03-18 11:29
*/
public class SizeOfObject {
static Instrumentation inst; public static void premain(String args, Instrumentation instP) {
inst = instP;
} /**
* 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br>
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br>
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br>
*
* @param obj
* @return
*/
public static long sizeOf(Object obj) {
return inst.getObjectSize(obj);
} /**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
*
* @param objP
* @return
* @throws IllegalAccessException
*/
public static long fullSizeOf(Object objP) throws IllegalAccessException {
Set<Object> visited = new HashSet<Object>();
Deque<Object> toBeQueue = new ArrayDeque<Object>();
toBeQueue.add(objP);
long size = 0L;
while (toBeQueue.size() > 0) {
Object obj = toBeQueue.poll();
//sizeOf的时候已经计基本类型和引用的长度,包括数组
size += skipObject(visited, obj) ? 0L : sizeOf(obj);
Class<?> tmpObjClass = obj.getClass();
if (tmpObjClass.isArray()) {
//[I , [F 基本类型名字长度是2
if (tmpObjClass.getName().length() > 2) {
for (int i = 0, len = Array.getLength(obj); i < len; i++) {
Object tmp = Array.get(obj, i);
if (tmp != null) {
//非基本类型需要深度遍历其对象
toBeQueue.add(Array.get(obj, i));
}
}
}
} else {
while (tmpObjClass != null) {
Field[] fields = tmpObjClass.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()) //静态不计
|| field.getType().isPrimitive()) { //基本类型不重复计
continue;
} field.setAccessible(true);
Object fieldValue = field.get(obj);
if (fieldValue == null) {
continue;
}
toBeQueue.add(fieldValue);
}
tmpObjClass = tmpObjClass.getSuperclass();
}
}
}
return size;
} /**
* String.intern的对象不计;计算过的不计,也避免死循环
*
* @param visited
* @param obj
* @return
*/
static boolean skipObject(Set<Object> visited, Object obj) {
if (obj instanceof String && obj == ((String) obj).intern()) {
return true;
}
return visited.contains(obj);
}
}
这个工具就是用于解决刚才说明的Instrumentation.getObjectSize()只能够计算对象的大小的问题。SizeOfObject中提供了两个方法,sizeOf仍然是直接计算对象大小,而fullSizeOf提供了一个用于递归计算当前对象占用大小。 递归计算当前对象占用大小时,大致根据下面的算法计算:
- 递归列出当前对象的所有字段,跳过被执行过intern的String;
- 如果是引用类型的数组,遍历该引用类型;
- 如果是引用对象,列出所有的非基本类型/非static字段,并列出父类的字段;
- 每个字段进行再次遍历;
最终得到计算后的对象大小结果。
public class MemorySizes {
private final Map primitiveSizes = new IdentityHashMap() {
{
put(boolean.class, new Integer(1));
put(byte.class, new Integer(1));
put(char.class, new Integer(2));
put(short.class, new Integer(2));
put(int.class, new Integer(4));
put(float.class, new Integer(4));
put(double.class, new Integer(8));
put(long.class, new Integer(8));
}
};
public int getPrimitiveFieldSize(Class clazz) {
return ((Integer) primitiveSizes.get(clazz)).intValue();
}
public int getPrimitiveArrayElementSize(Class clazz) {
return getPrimitiveFieldSize(clazz);
}
public int getPointerSize() {
return 4;
}
public int getClassSize() {
return 8;
}
}
/**
* This class can estimate how much memory an Object uses. It is
* fairly accurate for JDK 1.4.2. It is based on the newsletter #29.
*/
public final class MemoryCounter {
private static final MemorySizes sizes = new MemorySizes();
private final Map visited = new IdentityHashMap();
private final Stack stack = new Stack(); public synchronized long estimate(Object obj) {
assert visited.isEmpty();
assert stack.isEmpty();
long result = _estimate(obj);
while (!stack.isEmpty()) {
result += _estimate(stack.pop());
}
visited.clear();
return result;
} private boolean skipObject(Object obj) {
if (obj instanceof String) {
// this will not cause a memory leak since
// unused interned Strings will be thrown away
if (obj == ((String) obj).intern()) {
return true;
}
}
return (obj == null)
|| visited.containsKey(obj);
} private long _estimate(Object obj) {
if (skipObject(obj)) {
return 0;
}
visited.put(obj, null);
long result = 0;
Class clazz = obj.getClass();
if (clazz.isArray()) {
return _estimateArray(obj);
}
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!Modifier.isStatic(fields[i].getModifiers())) {
if (fields[i].getType().isPrimitive()) {
result += sizes.getPrimitiveFieldSize(
fields[i].getType());
} else {
result += sizes.getPointerSize();
fields[i].setAccessible(true);
try {
Object toBeDone = fields[i].get(obj);
if (toBeDone != null) {
stack.add(toBeDone);
}
} catch (IllegalAccessException ex) {
assert false;
}
}
}
}
clazz = clazz.getSuperclass();
}
result += sizes.getClassSize();
return roundUpToNearestEightBytes(result);
} private long roundUpToNearestEightBytes(long result) {
if ((result % 8) != 0) {
result += 8 - (result % 8);
}
return result;
} protected long _estimateArray(Object obj) {
long result = 16;
int length = Array.getLength(obj);
if (length != 0) {
Class arrayElementClazz = obj.getClass().getComponentType();
if (arrayElementClazz.isPrimitive()) {
result += length *
sizes.getPrimitiveArrayElementSize(arrayElementClazz);
} else {
for (int i = 0; i < length; i++) {
result += sizes.getPointerSize() +
_estimate(Array.get(obj, i));
}
}
}
return result;
}
}
System.gc();
SysOutLogger.info("Total memory: " + Runtime.getRuntime().totalMemory());
SysOutLogger.info("Free memory: " + Runtime.getRuntime().freeMemory());
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
SysOutLogger.info("Used memory current: " + usedMemory);
SysOutLogger.info(String.format("Current Stat: %s, Used Memory: %s", currentStat.toString(), new MemoryCounter().estimate(currentStat)));
[INFO] 2014-10-22 19:52:53 : Total memory: 2327707648
[INFO] 2014-10-22 19:52:53 : Free memory: 774631832
[INFO] 2014-10-22 19:52:53 : Used memory current: 1553075816
[INFO] 2014-10-22 19:53:19 : Current Stat, Used Memory: 1479780762
准确计算Java中对象的大小的更多相关文章
- java 中对象比较大小
java 中对象比较大小 java 中对象比较大小有两种方法 1:实现Comparable 接口 的 public int compareTo(T o) 方法: 2:实现Comparator 接口 的 ...
- 如何准确计算Java对象的大小
如何准确计算Java对象的大小 原创文章,转载请注明:博客园aprogramer 原文链接:如何准确计算Java对象的大小 有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两 ...
- Java中对象和引用的理解
偶然想起Java中对象和引用的基本概念,为了加深下对此的理解和认识,特地整理一下相关的知识点,通过具体实例从两者的概念和区别两方面去更形象的认识理解,再去记忆. 一.对象和引用的概念: 在Java中万 ...
- Java中对象的深复制和浅复制详解
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵ ...
- Java中对象、对象引用、堆、栈、值传递以及引用传递的详解
Java中对象.对象引用.堆.栈.值传递以及引用传递的详解 1.对象和对象引用的差别: (1).对象: 万物皆对象.对象是类的实例. 在Java中new是用来在堆上创建对象用的. 一个对象能够被多个引 ...
- Java中对象流使用的一个注意事项
再写jsp的实验作业的时候,需要用到java中对象流,但是碰到了之前没有遇到过的情况,改bug改到崩溃!!记录下来供大家分享 如果要用对象流去读取一个文件,一定要先判断这个文件的内容是否为空,如果为空 ...
- Java中对象的生与灭- 核心篇
前言 大家好啊,我是汤圆,今天给大家带来的是<Java中对象的生与灭- 核心篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论区回复或后台私信,谢啦 简介 ...
- 你真的了解JAVA中对象和类、this、super和static关键字吗
作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 目录 Java对象究竟是什么? 创建对象的过程 ...
- Java中对象方法的调用过程&动态绑定(Dynamic Binding)
Java面向对象的最重要的一个特点就是多态, 而多态当中涉及到了一个重要的机制是动态绑定(Dynamic binding). 之前只有一个大概的概念, 没有深入去了解动态绑定的机理, 直到很多公司都问 ...
随机推荐
- maven手动添加jar(转)
Maven 手动添加 JAR 包到本地仓库 原文链接:http://www.blogjava.net/fancydeepin/archive/2012/06/12/380605.html Maven ...
- ROW_NUMBER() OVER(PARTITION BY)
select * from (select *,ROW_NUMBER() OVER(PARTITION BY GoodsID ORDER BY IsMain desc,OrderNum) as Mai ...
- Quartz创建多个不同名字的scheduler实例
_http://my.oschina.net/laiweiwei/blog/122280 需求创建多个不同的Scheduler实例,每个实例自主启动.关闭 问题 如果直接用 SchedulerFact ...
- iOS开发之如何在Xcode中显示断点堆栈
昨天有人问我在Xcode中打断点后怎么查看堆栈,今天就简单的聊聊. 首先解释一下,什么是堆栈? 堆:顺序随意:栈区(stack)- 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式 ...
- Mac终端建立替身 并置于桌面或Finder中
前情 Xcode存放log的文件夹路径忒长了,且需要用终端才能查看.所以就想制作个替身,放在Finder中便于查看. going on command+space打开terminal 一直cd...进 ...
- oracle 未明确定义错误
select sysuser1.* from (select sysuser2.*, rownum rownum_temp from (select yycgdmx.id yycgdmxid, -- ...
- baos bais 意义
import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOut ...
- Jmeter简单的接口测试
1.新建线程组 2.编辑线程组信息 3.在线程组中添加HTTP信息头管理器 4.配置HTTP信息头管理器 参数格式配置 5.在线程组中添加HTTP请求 6.编辑HTTP请求信息 7.添加响应断言 8. ...
- html5大纲算法(目录树)
看了<CSS那些事儿>我一直遵循着给每个板块写一个h标签,并保持层次,比如导航条.焦点图我都写了一个缩进隐藏的h标签.这种规范一般人根本看不出来,即使是行内的大多数人也觉得没有必要.可是我 ...
- WPF 绘制对齐像素的清晰显示的线条
此前有小伙伴询问我为何他 1 像素的线条显示发虚,然后我告诉他是“像素对齐”的问题,然而他设置了各种对齐像素的属性依旧没有作用.于是我对此进行了一系列试验,对 WPF 像素对齐的各种方法进行了一次总结 ...