由于在项目中需要大致计算一下对象的内存占用率(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对象大小的计算对于优化内存使用、防止内存泄漏以及提高应用程序性能至关重要。开发人员需要了解不同方法的优缺点,并根据实际需求选择合适的方式。同时,应当注意,过于关注单个对象的大小可能...
本文将深入探讨如何计算Java对象的大小,以及这个知识点在实际开发中的应用。 首先,Java对象的大小不仅仅包括其字段的大小,还包括对象头(object header)的大小,对于HotSpot虚拟机,它包含了对齐填充、Mark ...
本示例“测量Java对象大小的demo”提供了一种方法,通过使用Java的`java.lang.instrument`包来注入`javaagent`,进而利用`Instrumentation`接口测量Java对象的精确内存占用。下面我们将详细探讨这一过程。 首先,`...
总的来说,理解Java对象在内存中的表示方式和占用空间,结合合适的工具进行监控,是开发高效、稳定的Java应用的基础。通过深入学习这些知识点,开发者可以更好地优化程序,提高系统资源利用率,避免因内存问题导致的...
总结来说,这个例子旨在帮助开发者理解Java对象在内存中的表示方式,以及不同类型和结构的对象如何影响内存使用。通过这种方式,开发者可以优化代码,减少不必要的内存开销,提高应用程序的效率。同时,这也提醒我们...
本文将深入探讨Java中的对象内存占用,以及如何使用"java-sizeof-0.0.4"工具来查看Java对象在内存中的大小。 在Java中,内存主要分为堆内存(Heap)和栈内存(Stack)。对象通常存储在堆内存中,而基本类型的变量和...
接下来,我们将详细介绍如何精确地计算Java字符串所占用的内存空间。这里主要关注字符串对象本身的内存占用情况,而非其引用部分。 ##### Java字符串结构分析 在Java中,字符串(String)是一个不可变的对象,它主要...
在Java编程中,当涉及到需要精确数值计算的场景时,我们通常会...综上,`BigDecimal`是Java中处理精确数值计算的重要工具,合理使用能确保商业计算的准确性。理解其基本操作和注意事项,有助于编写出高效且准确的代码。
这类软件通常会提供大量模拟试题,涵盖Java语法、面向对象编程、异常处理、IO流、集合框架、多线程、网络编程等多个方面的内容。用户可以通过做题、查看解析和答案来检验自己的学习效果,熟悉考试环境和题型,提升...
而GridBagLayout则更为灵活,可以精确控制组件的位置和大小。 2. 模块化设计:将计算器的各个功能如加法、减法等封装成独立的方法,便于代码的维护和扩展。同时,可以考虑设计一个计算器引擎类,负责所有的计算逻辑...
在Java Web开发中,实现文件下载的进度条和大小控制是一项常见的需求,特别是在使用Struts框架的情况下。Struts是基于MVC(Model-View-Controller)设计模式的Java Web应用程序框架,它允许开发者通过简单的配置和...
在Java中,可以通过遍历两个集合并计算交集和并集来实现。 3. **余弦相似度**:在多维空间中衡量两个向量之间的角度,适用于高维数据。可以先将字符串转化为词频向量,然后使用Java的`java.util.Math.cos()`计算两...
2. 小数的精确计算问题 在Java中,使用浮点数(如float和double)进行计算时可能会出现精度问题。由于二进制浮点数不能精确地表示一些十进制小数,例如1.1在二进制中无法精确表示,所以进行浮点数计算时需要注意其...
在Java编程语言中,处理图像任务,如头像剪切、上传、设置大小以及生成固定缩略图,是一项常见的需求。这些操作广泛应用于社交媒体、个人资料管理或任何需要用户自定义头像的系统中。本篇文章将深入探讨如何使用Java...
在标题为“时分秒比较大小”的主题下,我们主要探讨的是如何在编程环境中比较两个包含时、分、秒的时间值的大小。这种比较通常涉及到日期时间类库的使用,特别是在处理用户界面、事件调度或数据分析时。这里我们将...
这里我们将深入探讨计算Java对象占用空间的方法。 首先,每个Java对象都包含一个对象头(Object Header),它存储了一些元数据信息。对象头通常由两部分组成: 1. **Mark Word**: 这部分存储了对象的哈希码、锁...
这通常涉及到计算文本、图像或其他元素在页面上的准确位置,以及处理多页情况。 9. **JApplet**: 在示例代码中,`PrintTest`类继承自`JApplet`,这可能表示这个打印功能是在一个Java Applet中实现的。然而,由于...
在进行商业计算时,由于浮点数(double和float)存在精度问题,不能保证准确的结果,因此通常推荐使用BigDecimal来确保计算的精确性。本文将深入探讨BigDecimal的基本操作、应用场景及注意事项。 首先,创建...
5. 得到的 `KeyPoint` 对象表示关键点的位置、大小和方向,而描述符则存储在 `MatOfFloat` 对象中。 6. 可以将关键点和描述符用于后续的匹配、分类或其他图像处理任务。 在提供的文件列表中,`DummyIndex.java` ...
Java中的对象存储空间是在堆中分配的,而对象的引用则是在栈中分配。 - **栈**:用于管理线程的状态,保存局部变量、中间计算过程和其他数据。每当一个线程激活一个Java方法时,JVM会在该线程的Java堆栈中压入一个新...