计算 java_两种计算Java对象大小的方法objectFieldOffset

原文:http://blog.csdn.net/iter_zc/article/details/41822719

另一篇类似文章:http://www.cnblogs.com/magialmoon/p/3757767.html​

这篇说说如何计算Java

对象大小的方法。之前在聊聊高并发(四)Java对象的表示模型和运行时内存表示 这篇中已经说了Java对象的内存表示模型是Oop-Klass模型。

普通对象的结构如下,按64位机器的长度计算

1. 对象头(_mark), 8个字节

2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节

3. 数据区

4.Padding(内存对齐),按照8的倍数对齐

数组对象结构是

1. 对象头(_mark), 8个字节

2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节

3. 数组长度,4个字节

4. 数据区

5. Padding(内存对齐),按照8的倍数对齐

清楚了对象在内存的基本布局后,咱们说两种计算Java对象大小的方法

1. 通过直接获取对象的大小

2.

通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小

的方式

先讲讲的方式,这种方法得到的是Shallow

Size,即遇到引用时,只计算引用的长度,不计算所引用的对象的实际大小。如果要计算所引用对象的实际大小,可以通过递归的方式去计算。

java.lang.instrument.Instrumentation的实例必须通过指定javaagent的方式才能获得,具体的步骤如下:

1. 定义一个类,提供一个premain方法: public static void premain(String

agentArgs, Instrumentation instP)

2. 创建META-INF/MANIFEST.MF文件,内容是指定PreMain的类是哪个: Premain-Class:

sizeof.ObjectShallowSize

3. 把这个类打成jar,然后用java -javaagent XXXX.jar XXX.main的方式执行

下面先定义一个类来获得java.lang.instrument.Instrumentation的实例,并提供了一个static的sizeOf方法对外提供Instrumentation的能力

[java] view plain

copy

packagesizeof;

import

java.lang.instrument.Instrumentation;

public

class

ObjectShallowSize {
private

static

Instrumentation inst;

public

static

void

premain(String agentArgs, Instrumentation instP){
inst = instP;

}

public

static

long

sizeOf(Object obj){
return

inst.getObjectSize(obj);

}

}

定义META-INF/MANIFEST.MF文件

[java]

view

plaincopy

Premain-Class: sizeof.ObjectShallowSize

打成jar包

[html]

view

plaincopy

cd 编译后的类和META-INF文件夹所在目录

jar cvfm java-agent-sizeof.jar META-INF/MANIFEST.MF  .

准备好了这个jar之后,我们可以写测试类来测试Instrumentation的getObjectSize方法了。在这之前我们先来看对象在内存中是按照什么顺序排列的

有如下这个类,字段的定义按如下顺序

[java]

view

plaincopy

private

static

class

ObjectA {
String str;  // 4

int

i1;

// 4

byte

b1;

// 1

byte

b2;

// 1

int

i2;

// 4

ObjectB obj; //4

byte

b3;

// 1

}

按照我们之前说的方法来计算一下这个对象所占大小,注意按8对齐

8(_mark) + 4(oop指针) + 4(str) + 4(i1) + 1(b1) + 1(b2) +

2(padding) + 4(i2) + 4(obj) + 1(b3) + 7(padding) = 40 ?

但事实上是这样的吗? 我们来用Instrumentation的getObjectSize来计算一下先:

[java]

view

plaincopy

package

test;

import

sizeof.ObjectShallowSize;

public

class

SizeofWithInstrumetation {
private

static

class

ObjectA {
String str;  // 4

int

i1;

// 4

byte

b1;

// 1

byte

b2;

// 1

int

i2;

// 4

ObjectB obj; //4

byte

b3;

// 1

}

private

static

class

ObjectB {
}

public

static

void

main(String[] args){
System.out.println(ObjectShallowSize.sizeOf(new

ObjectA()));

}

}

得到的结果是32!不是会按8对齐吗,b3之前的数据加起来已经是32了,多了1个b3,为33,应该对齐到40才对啊。事实上,HotSpot创建的对象的字段会先按照给定顺序排列一下,默认的顺序如下,从长到短排列,引用排最后

:  long/double --> int/float

-->  short/char -->

byte/boolean --> Reference

这个顺序可以使用JVM参数:

-XX:FieldsAllocationSylte=0(默认是1)来改变。

我们使用sun.misc.Unsafe对象的objectFieldOffset方法来验证一下:

[java]

view

plaincopy

Field[] fields = ObjectA.

class

.getDeclaredFields();

for

(Field f: fields){
System.out.println(f.getName() + " offset: "

+unsafe.objectFieldOffset(f));

}

可以看到确实是按照从长到短,引用排最后

的方式在内存中排列的。按照这种方法我们来重新计算下ObjectA创建的对象的长度:

8(_mark) + 4(oop指针) + 4(i1) + + 4(i2) + 1(b1) + 1(b2) + 1(b3) +

1(padding) +  4(str) + 4(obj) = 32

得到的结果和的结果是一样的,证明我们的计算方式是正确的。

sun.misc.Unsafe的方式

下面说一下通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小。基本的思路如下:

1. 通过反射获得一个类的Field

2. 通过Unsafe的objectFieldOffset()获得每个Field的offSet

3. 对Field按照offset排序,取得最大的offset,然后加上这个field的长度,再加上Padding对齐

上面三步就可以获得一个对象的Shallow

size。可以进一步通过递归去计算所引用对象的大小,从而可以计算出一个对象所占用的实际大小。

如何获得Unsafe对象已经在这篇中聊聊序列化(二)使用sun.misc.Unsafe绕过new机制来创建Java对象

说过了,可以通过反射的机制来获得.

Oop指针是4还是未压缩的8也可以通过unsafe.arrayIndexScale(Object[].class)来获得,这个方法返回一个引用所占用的长度

[java]

view

plaincopy

static

{
try

{
Field field = Unsafe.class

.getDeclaredField(

"theUnsafe"

);

field.setAccessible(true

);

unsafe = (Unsafe) field.get(null

);

objectRefSize = unsafe.arrayIndexScale(Object[].class

);

} catch

(Exception e) {
throw

new

RuntimeException(e);

}

}

下面的源码摘自

http://java-performance.info/memory-introspection-using-sun-misc-unsafe-and-reflection/,

原文中的代码在计算对象大小的时候有问题,我做了微调,并加上了内存对齐的方法,这样计算出的结果和Instrumentation的getObjectSize方法是一样的。

[java]

view

plaincopy

package

test;

import

java.util.ArrayList;

import

java.util.Collections;

import

java.util.Comparator;

import

java.util.List;

public

class

ObjectInfo {
public

final

String name;

public

final

String type;

public

final

String contents;

public

final

int

offset;

public

final

int

length;

public

final

int

arrayBase;

public

final

int

arrayElementSize;

public

final

int

arraySize;

public

final

List children;

public

ObjectInfo(String name, String type, String contents,

int

offset,

int

length,

int

arraySize,

int

arrayBase,

int

arrayElementSize)

{
this

.name = name;

this

.type = type;

this

.contents = contents;

this

.offset = offset;

this

.length = length;

this

.arraySize = arraySize;

this

.arrayBase = arrayBase;

this

.arrayElementSize = arrayElementSize;

children = new

ArrayList(

1

);

}

public

void

addChild(

final

ObjectInfo info )

{
if

( info !=

null

)

children.add( info );

}

public

long

getDeepSize()

{
//return length + arraySize + getUnderlyingSize( arraySize != 0 );

return

addPaddingSize(arraySize + getUnderlyingSize( arraySize !=

0

));

}

long

size =

0

;

private

long

getUnderlyingSize(

final

boolean

isArray )

{
//long size = 0;

for

(

final

ObjectInfo child : children )

size += child.arraySize + child.getUnderlyingSize( child.arraySize != 0

);

if

( !isArray && !children.isEmpty() ){
int

tempSize = children.get( children.size() -

1

).offset + children.get( children.size() -

1

).length;

size += addPaddingSize(tempSize);

}

return

size;

}

private

static

final

class

OffsetComparator

implements

Comparator

{
@Override

public

int

compare(

final

ObjectInfo o1,

final

ObjectInfo o2 )

{
return

o1.offset - o2.offset;

//safe because offsets are small non-negative numbers

}

}

//sort all children by their offset

public

void

sort()

{
Collections.sort( children, new

OffsetComparator() );

}

@Override

public

String toString() {
final

StringBuilder sb =

new

StringBuilder();

toStringHelper( sb, 0

);

return

sb.toString();

}

private

void

toStringHelper(

final

StringBuilder sb,

final

int

depth )

{
depth( sb, depth ).append("name="

).append( name ).append(

", type="

).append( type )

.append( ", contents="

).append( contents ).append(

", offset="

).append( offset )

.append(", length="

).append( length );

if

( arraySize >

0

)

{
sb.append(", arrayBase="

).append( arrayBase );

sb.append(", arrayElemSize="

).append( arrayElementSize );

sb.append( ", arraySize="

).append( arraySize );

}

for

(

final

ObjectInfo child : children )

{
sb.append( '\n'

);

child.toStringHelper(sb, depth + 1

);

}

}

private

StringBuilder depth(

final

StringBuilder sb,

final

int

depth )

{
for

(

int

i =

0

; i 

sb.append( "\t"

);

return

sb;

}

private

long

addPaddingSize(

long

size){
if

(size %

8

!=

0

){
return

(size /

8

+

1

) *

8

;

}

return

size;

}

}

package

test;

import

java.lang.reflect.Array;

import

java.lang.reflect.Field;

import

java.lang.reflect.Modifier;

import

java.util.ArrayList;

import

java.util.Arrays;

import

java.util.Collections;

import

java.util.HashMap;

import

java.util.IdentityHashMap;

import

java.util.List;

import

java.util.Map;

import

sun.misc.Unsafe;

public

class

ClassIntrospector {
private

static

final

Unsafe unsafe;

private

static

final

int

objectRefSize;

static

{
try

{
Field field = Unsafe.class

.getDeclaredField(

"theUnsafe"

);

field.setAccessible(true

);

unsafe = (Unsafe) field.get(null

);

objectRefSize = unsafe.arrayIndexScale(Object[].class

);

} catch

(Exception e) {
throw

new

RuntimeException(e);

}

}

private

static

final

Map primitiveSizes;

static

{
primitiveSizes = new

HashMap(

10

);

primitiveSizes.put(byte

.

class

,

1

);

primitiveSizes.put(char

.

class

,

2

);

primitiveSizes.put(int

.

class

,

4

);

primitiveSizes.put(long

.

class

,

8

);

primitiveSizes.put(float

.

class

,

4

);

primitiveSizes.put(double

.

class

,

8

);

primitiveSizes.put(boolean

.

class

,

1

);

}

public

ObjectInfo introspect(

final

Object obj)

throws

IllegalAccessException {
try

{
return

introspect(obj,

null

);

} finally

{
// clean visited cache before returning in order to make

// this object reusable

m_visited.clear();

}

}

// we need to keep track of already visited objects in order to support

// cycles in the object graphs

private

IdentityHashMapm_visited =

new

IdentityHashMap(

100

);

private

ObjectInfo introspect(

final

Object obj,

final

Field fld)

throws

IllegalAccessException {
// use Field type only if the field contains null. In this case we will

// at least know what's expected to be

// stored in this field. Otherwise, if a field has interface type, we

// won't see what's really stored in it.

// Besides, we should be careful about primitives, because they are

// passed as boxed values in this method

// (first arg is object) - for them we should still rely on the field

// type.

boolean

isPrimitive = fld !=

null

&& fld.getType().isPrimitive();

boolean

isRecursive =

false

;

// will be set to true if we have already

// seen this object

if

(!isPrimitive) {
if

(m_visited.containsKey(obj))

isRecursive = true

;

m_visited.put(obj, true

);

}

final

Class type = (fld ==

null

|| (obj !=

null

&& !isPrimitive)) ? obj

.getClass() : fld.getType();

int

arraySize =

0

;

int

baseOffset =

0

;

int

indexScale =

0

;

if

(type.isArray() && obj !=

null

) {
baseOffset = unsafe.arrayBaseOffset(type);

indexScale = unsafe.arrayIndexScale(type);

arraySize = baseOffset + indexScale * Array.getLength(obj);

}

final

ObjectInfo root;

if

(fld ==

null

) {
root = new

ObjectInfo(

""

, type.getCanonicalName(), getContents(obj,

type), 0

, getShallowSize(type), arraySize, baseOffset,

indexScale);

} else

{
final

int

offset = (

int

) unsafe.objectFieldOffset(fld);

root = new

ObjectInfo(fld.getName(), type.getCanonicalName(),

getContents(obj, type), offset, getShallowSize(type),

arraySize, baseOffset, indexScale);

}

if

(!isRecursive && obj !=

null

) {
if

(isObjectArray(type)) {
// introspect object arrays

final

Object[] ar = (Object[]) obj;

for

(

final

Object item : ar)

if

(item !=

null

)

root.addChild(introspect(item, null

));

} else

{
for

(

final

Field field : getAllFields(type)) {
if

((field.getModifiers() & Modifier.STATIC) !=

0

) {
continue

;

}

field.setAccessible(true

);

root.addChild(introspect(field.get(obj), field));

}

}

}

root.sort(); // sort by offset

return

root;

}

// get all fields for this class, including all superclasses fields

private

static

List getAllFields(

final

Class type) {
if

(type.isPrimitive())

return

Collections.emptyList();

Class cur = type;

final

List res =

new

ArrayList(

10

);

while

(

true

) {
Collections.addAll(res, cur.getDeclaredFields());

if

(cur == Object.

class

)

break

;

cur = cur.getSuperclass();

}

return

res;

}

// check if it is an array of objects. I suspect there must be a more

// API-friendly way to make this check.

private

static

boolean

isObjectArray(

final

Class type) {
if

(!type.isArray())

return

false

;

if

(type ==

byte

[].

class

|| type ==

boolean

[].

class

|| type == char

[].

class

|| type ==

short

[].

class

|| type == int

[].

class

|| type ==

long

[].

class

|| type == float

[].

class

|| type ==

double

[].

class

)

return

false

;

return

true

;

}

// advanced toString logic

private

static

String getContents(

final

Object val,

final

Class type) {
if

(val ==

null

)

return

"null"

;

if

(type.isArray()) {
if

(type ==

byte

[].

class

)

return

Arrays.toString((

byte

[]) val);

else

if

(type ==

boolean

[].

class

)

return

Arrays.toString((

boolean

[]) val);

else

if

(type ==

char

[].

class

)

return

Arrays.toString((

char

[]) val);

else

if

(type ==

short

[].

class

)

return

Arrays.toString((

short

[]) val);

else

if

(type ==

int

[].

class

)

return

Arrays.toString((

int

[]) val);

else

if

(type ==

long

[].

class

)

return

Arrays.toString((

long

[]) val);

else

if

(type ==

float

[].

class

)

return

Arrays.toString((

float

[]) val);

else

if

(type ==

double

[].

class

)

return

Arrays.toString((

double

[]) val);

else

return

Arrays.toString((Object[]) val);

}

return

val.toString();

}

// obtain a shallow size of a field of given class (primitive or object

// reference size)

private

static

int

getShallowSize(

final

Class type) {
if

(type.isPrimitive()) {
final

Integer res = primitiveSizes.get(type);

return

res !=

null

? res :

0

;

} else

return

objectRefSize;

}

}

先一个测试

类来验证一下Unsafe的方式计算出的结果

[html]

view

plaincopy

public class ClassIntrospectorTest

{
public static void main(String[] args) throws IllegalAccessException {
final ClassIntrospector ci

=

new

ClassIntrospector();

ObjectInfo res;

res

=

ci

.introspect( new ObjectA() );

System.out.println( res.getDeepSize() );

}

private static class ObjectA {
String str;  // 4

int i1; // 4

byte b1; // 1

byte b2; // 1

int i2;  // 4

ObjectB obj; //4

byte b3;  // 1

}

private static class ObjectB {
}

}

计算结果如下:

32

和我们之前计算结果是一致的,证明是正确的。

最后再来测试一下数组对象的长度。有两个类如下:

[java]

view

plaincopy

private

static

class

ObjectC {
ObjectD[] array = new

ObjectD[

2

];

}

private

static

class

ObjectD {
int

value;

}

它们在内存的大体分布如下图:

我们可以手工计算一下ObjectC obj = new ObjectC()的大小:

ObjectC的Shallow size = 8(_mark) + 4(oop指针)  +

4(ObjectD[]引用) = 16

new ObjectD[2]数组的长度 =  8(_mark) + 4(oop指针) +

4(数组长度占4个字节) + 4(ObjectD[0]引用) + 4(ObjectD[1]引用) = 24

由于ObjectD[]数组没有指向具体的对象大小,所以我们手工计算的结果是16 + 24 = 40

使用Unsafe对象的方式来计算一下:

[java]

view

plaincopy

public

static

void

main(String[] args)

throws

IllegalAccessException {
final

ClassIntrospector ci =

new

ClassIntrospector();

ObjectInfo res;

res = ci.introspect( new

ObjectC() );

System.out.println( res.getDeepSize() );

}

计算结果如下,和我们计算的结果是一致的,证明是正确的:

40

再给ObjectD[]数组指向具体的ObjectD对象,再测试一下结果:

[java]

view

plaincopy

public

static

void

main(String[] args)

throws

IllegalAccessException {
final

ClassIntrospector ci =

new

ClassIntrospector();

ObjectInfo res;

res = ci.introspect( new

ObjectC() );

System.out.println( res.getDeepSize() );

}

private

static

class

ObjectC {
ObjectD[] array = new

ObjectD[

2

];

public

ObjectC(){
array[0

] =

new

ObjectD();

array[1

] =

new

ObjectD();

}

}

private

static

class

ObjectD {
int

value;

}

我们可以手工计算一下ObjectC obj = new ObjectC()的大小:

ObjectC的Shallow size = 8(_mark) + 4(oop指针)  +

4(ObjectD[]引用) = 16

new ObjectD[2]数组的长度 =  8(_mark) + 4(oop指针) +

4(数组长度占4个字节) + 4(ObjectD[0]引用) + 4(ObjectD[1]引用) = 24

ObjectD对象长度 = 8(_mark) + 4(oop指针) + 4(value) = 16

所以ObjectC实际占用的空间 = 16 + 24 + 2 * 16 = 72

使用Unsafe的方式计算的结果也是72,和我们手工计算的方式一致。

参考:  Memory

introspection using sun.misc.Unsafe and reflection
————————————————
版权声明:本文为CSDN博主「西从南来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_30834169/article/details/114043010

猜你喜欢

转载自blog.csdn.net/sunboylife/article/details/114275416