计算 java_两种计算Java对象大小的方法(转)
原文: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
總結
以上是生活随笔為你收集整理的计算 java_两种计算Java对象大小的方法(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 野枇杷干的功效与作用、禁忌和食用方法
- 下一篇: 白鳝鱼的功效与作用、禁忌和食用方法