由于在项目中需要大致计算一下对象的内存占用率(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

使用下面的方式进行调用:

java -javaagent:*.jar <main class>

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。

上述的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字段,并列出父类的字段;
  • 每个字段进行再次遍历;

最终得到计算后的对象大小结果。

但是有时候使用这种调用java -javaagent的方式也是不太方便的,尤其是不容易控制java调用的时候,比如在hadoop中启动的map/reduce任务,虽然可以通过参数来控制其选项,但不容易控制调用的java命令,也无法加入java -javaagent。
另外一种方式虽然没有Instrument那样精确,但是也是可以接受的,也不用特殊的方式启动。
这种方法使用一个MemorySizes:
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;
}
}
然后定义一个MemoryCount来对对象的大小进行计算:
/**
* 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中对象的大小的更多相关文章

  1. java 中对象比较大小

    java 中对象比较大小 java 中对象比较大小有两种方法 1:实现Comparable 接口 的 public int compareTo(T o) 方法: 2:实现Comparator 接口 的 ...

  2. 如何准确计算Java对象的大小

    如何准确计算Java对象的大小 原创文章,转载请注明:博客园aprogramer 原文链接:如何准确计算Java对象的大小      有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两 ...

  3. Java中对象和引用的理解

    偶然想起Java中对象和引用的基本概念,为了加深下对此的理解和认识,特地整理一下相关的知识点,通过具体实例从两者的概念和区别两方面去更形象的认识理解,再去记忆. 一.对象和引用的概念: 在Java中万 ...

  4. Java中对象的深复制和浅复制详解

    1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵ ...

  5. Java中对象、对象引用、堆、栈、值传递以及引用传递的详解

    Java中对象.对象引用.堆.栈.值传递以及引用传递的详解 1.对象和对象引用的差别: (1).对象: 万物皆对象.对象是类的实例. 在Java中new是用来在堆上创建对象用的. 一个对象能够被多个引 ...

  6. Java中对象流使用的一个注意事项

    再写jsp的实验作业的时候,需要用到java中对象流,但是碰到了之前没有遇到过的情况,改bug改到崩溃!!记录下来供大家分享 如果要用对象流去读取一个文件,一定要先判断这个文件的内容是否为空,如果为空 ...

  7. Java中对象的生与灭- 核心篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中对象的生与灭- 核心篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论区回复或后台私信,谢啦 简介 ...

  8. 你真的了解JAVA中对象和类、this、super和static关键字吗

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 目录 Java对象究竟是什么? 创建对象的过程 ...

  9. Java中对象方法的调用过程&动态绑定(Dynamic Binding)

    Java面向对象的最重要的一个特点就是多态, 而多态当中涉及到了一个重要的机制是动态绑定(Dynamic binding). 之前只有一个大概的概念, 没有深入去了解动态绑定的机理, 直到很多公司都问 ...

随机推荐

  1. 201621123005《Java程序设计》第四周学习总结

    201621123005<Java程序设计>第四周学习总结 标签(空格分隔): 1.本章学习总结 1. 面向对象设计 1.1 写出你认为本周学习中比较重要的知识点关键词 继承.多态.覆盖. ...

  2. ffmpeg新老接口对比

    http://blog.csdn.net/leixiaohua1020/article/details/41013567

  3. iOS GCD之dispatch_semaphore(信号量)

    前言 最近在看AFNetworking3.0源码时,注意到在 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法 (L681),dispatch_semapho ...

  4. SQL基础五(作业代码)

    create database stuinfo create table student ( mid ) not null primary key, mname ) not null ) create ...

  5. vc 实现一个 http Server

    实现 http 的协议解析 以及封装 , 对应不同的访问地址, 调用绑定的处理程序 , #include "stdafx.h" #include "lyocomm\web ...

  6. canvas globalCompositeOperation

      source-over 默认,相交部分由后绘制图形的填充(颜色,渐变,纹理)覆盖source-in 只绘制相交部分,由后绘制图形的填充覆盖,其余部分透明 source-out 只绘制后绘制图形不相 ...

  7. JSP、Java和Servlet获取当前工程的路径

    JSP.Java和Servlet获取当前工程的路径: 1.JSP中取得路径: 以工程名为TEST为例: (1)得到包含工程名的当前页面全路径:request.getRequestURI()结果:/TE ...

  8. BZOJ1066 SCOI2007 蜥蜴 【网络流-最大流】

    BZOJ1066 SCOI2007 蜥蜴 Description 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外. 每行每列中相邻石柱的距离 ...

  9. Python学习-lambda表达式

    lambda只是一个表达式,函数体比def简单很多.lambda的主体是一个表达式,而不是一个代码块.仅仅能在lambda表达式中封装有限的逻辑进去.lambda表达式是起到一个函数速写的作用.允许在 ...

  10. 《DSP using MATLAB》示例Example 8.23

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...