Java计算一个对象占用内存的大小


在C/C++中计算某一个基本类型或者对象占用内存大小的方法很简单,只要调用库里面的sizeof()操作符即可,但是在Java的API里面并没有给我们提供类似的方法。那么我们可不可以自己实现一个Java中的sizeof()方法呢?答案是肯定的。为了计算一个Java对象占用内存的大小,首先你得对Java对象的内存结构有所了解。如果你还不了解,请先阅读Java内存结构

首先介绍一下sun.misc.Unsafe类,该类是Java中很神奇的一个类,这个类是用于执行低级别、不安全操作的方法集合。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己Java程序中直接使用该类,因为它的构造函数是私有的(private)


Unsafe类的更多介绍和用法可以参照http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

package size;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import sun.misc.Unsafe;

public class UnsafeTest {
	/** 对象头部的大小 */
	private static final int OBJECT_HEADER_SIZE = 8;
	/** 对象占用内存的最小值 */
	private static final int MINIMUM_OBJECT_SIZE = 8;
	/** 对象按多少字节的粒度进行对齐 */
	private static final int OBJECT_ALIGNMENT = 8;

	public static long sizeOf(Object obj) {
		// 获得Unsafe实例
		Unsafe unsafe;
		try {
			Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
			unsafeField.setAccessible(true);
			unsafe = (Unsafe) unsafeField.get(null);
		} catch (Throwable t) {
			unsafe = null;
		}

		// 判断对象是否为数组
		if (obj.getClass().isArray()) {
			Class<?> klazz = obj.getClass();
			int base = unsafe.arrayBaseOffset(klazz);
			int scale = unsafe.arrayIndexScale(klazz);
			long size = base + (scale * Array.getLength(obj));
			if ((size % OBJECT_ALIGNMENT) != 0) {
				size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
			}
			return Math.max(MINIMUM_OBJECT_SIZE, size);
		} else {
			// 如果数组对象则迭代遍历该对象的父类,找到最后一个非静态字段的偏移量
			for (Class<?> klazz = obj.getClass(); klazz != null; klazz = klazz
					.getSuperclass()) {
				long lastFieldOffset = -1;
				for (Field f : klazz.getDeclaredFields()) {
					if (!Modifier.isStatic(f.getModifiers())) {
						lastFieldOffset = Math.max(lastFieldOffset,
								unsafe.objectFieldOffset(f));
					}
				}
				if (lastFieldOffset > 0) {
					lastFieldOffset += 1;
					if ((lastFieldOffset % OBJECT_ALIGNMENT) != 0) {
						lastFieldOffset += OBJECT_ALIGNMENT
								- (lastFieldOffset % OBJECT_ALIGNMENT);
					}
					return Math.max(MINIMUM_OBJECT_SIZE, lastFieldOffset);
				}
			}
			// 该对象没有任何属性
			long size = OBJECT_HEADER_SIZE;
			if ((size % OBJECT_ALIGNMENT) != 0) {
				size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
			}
			return Math.max(MINIMUM_OBJECT_SIZE, size);
		}
	}

	public static void main(String[] args) throws InterruptedException {

		OpusVoiceInfo voice = new OpusVoiceInfo();
		
		OpusVoiceInfo voicex = new OpusVoiceInfo();
		voicex.setOpusId(8888888888888888888L);
		voicex.setOpusId2(8888888888888888888L);
		voicex.setOpusId3(8888888888888888888L);
		voicex.setOpusId4(8888888888888888888L);

		OpusVoiceInfo2 voice2 = new OpusVoiceInfo2();

		System.out.println(UnsafeTest.sizeOf(voice));// 输出
		System.out.println(UnsafeTest.sizeOf(voicex));
		System.out.println("voice2:" + UnsafeTest.sizeOf(voice2));// 输出

		Thread.sleep(100000);// 阻塞线程,为了使用jmap工具
	}
}

/**
output:
72
72
voice2:56
*/


jmap 查看 一致的:OK 

技术分享

打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)。

可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本。使用方法 jmap -histo pid。如果连用SHELL jmap -histo pid>a.log可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。jmap -dump:format=b,file=outfile 3024可以将3024进程的内存heap输出出来到outfile文件里,再配合MAT(内存分析工具(Memory Analysis Tool),使用参见:http://blog.csdn.net/fenglibing/archive/2011/04/02/6298326.aspx)或与jhat (Java Heap Analysis Tool)一起使用,能够以图像的形式直观的展示当前内存是否有问题。

64位机上使用需要使用如下方式:

jmap -J-d64 -heap pid

2、命令格式


SYNOPSIS

jmap [ option ] pid

jmap [ option ] executable core

jmap [ option ] [server-id@]remote-hostname-or-IP

3、参数说明


1)options: 

executable Java executable from which the core dump was produced.

(可能是产生core dump的java可执行程序)

core 将被打印信息的core dump文件

remote-hostname-or-IP 远程debug服务的主机名或ip

server-id 唯一id,假如一台主机上多个远程debug服务 

2)基本参数:

-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件. 

-finalizerinfo 打印正等候回收的对象的信息.

-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.

-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. 

-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来. 

-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效. 

-h | -help 打印辅助信息 

-J 传递参数给jmap启动的jvm. 

pid 需要被打印配相信息的java进程id,创业与打工的区别 - 博文预览,可以用jps查问.

4、使用示例


1)[fenglb@ccbu-156-5 ~]$ jmap -histo 4939

[输出较多这里不贴了]

2)[fenglb@ccbu-156-5 ~]$ jmap -dump:format=b,file=test.bin 4939

Dumping heap to /home/fenglb/test.bin ...

Heap dump file created






郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。