引用類型(reference type: Integer)在 32 位系統上每一個占用 4bytes(即32bit, 才干管理 2^32=4G 的內存), 在 64 位系統上每一個占用 8bytes(開啟壓縮為 4 bytes)。
四. 對齊填充
HotSpot 的對齊方式為 8 字節對齊。不足的須要 Padding 填充對齊, 公式:(對象頭 + 實例數據 + padding)% 8 == 0 (0<= padding <8)
五. 計算 Java 對象占用空間大小
借助 Instrument 接口的 getObjectSize 方法計算對象占用空間
SizeOfAgent: 計算對象大小類
package com.wenniuwuren.objectsizeof;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;
/**
* 借助 Instrumentation 接口的 getObjectSize 方法計算對象占用空間
* 原來的 sizeOf 僅僅能計算本對象占用空間, 無法計算繼承下來的占用空間,
* 只是能夠用反射的方法把全部占用空間計算出來
*
* Created by zhuyb on 16/3/20.
*/
public class SizeOfAgent {
static Instrumentation instrumentation;
// 第一個參數由 –javaagent。 第二個參數由 JVM 傳入
public static void premain(String agentArgs, Instrumentation instP) {
instrumentation = instP;
}
// 返回沒有子類對象大小的大小
public static long sizeOf(Object o) {
if (instrumentation == null) {
throw new IllegalStateException("Can not access instrumentation environment.\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument.");
}
return instrumentation.getObjectSize(o);
}
/**
*
* 計算復合對象
* @param obj object to calculate size of
* @return object size
*/
public static long fullSizeOf(Object obj) {
Map visited = new IdentityHashMap();
Stack stack = new Stack();
long result = internalSizeOf(obj, stack, visited);
while (!stack.isEmpty()) {
result += internalSizeOf(stack.pop(), stack, visited);
}
visited.clear();
return result;
}
// 這個算法使每一個對象僅被計算一次。 避免循環引用,即死循環計算
private static boolean skipObject(Object obj, Map visited) {
if (obj instanceof String) {
// String 池里已有的不再計算
if (obj == ((String) obj).intern()) {
return true;
}
}
return (obj == null) // 已有對象不再計算
|| visited.containsKey(obj);
}
private static long internalSizeOf(Object obj, Stack stack, Map visited) {
if (skipObject(obj, visited)){
return 0;
}
visited.put(obj, null);
long result = 0;
// get size of object + primitive variables + member pointers
result += SizeOfAgent.sizeOf(obj);
// 處理全部數組內容
Class clazz = obj.getClass();
if (clazz.isArray()) {
// [I , [F 基本類型名字長度是2
if(clazz.getName().length() != 2) {// skip primitive type array
int length = Array.getLength(obj);
for (int i = 0; i < length; i++) {
stack.add(Array.get(obj, i));
}
}
return result;
}
// 處理對象的全部字段
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()) {
continue;
} else {
// 使 private 屬性可訪問
fields[i].setAccessible(true);
try {
// objects to be estimated are put to stack
Object objectToAdd = fields[i].get(obj);
if (objectToAdd != null) {
stack.add(objectToAdd);
}
} catch (IllegalAccessException ex) {
assert false;
}
}
}
}
clazz = clazz.getSuperclass();
}
return result;
}
}
使用上述代碼必須將上述代碼打成 jar 包, 而且 MANIFEST.MF 文件設置參數(
Premain-Class:sizeof.agent.SizeOfAgent
Boot-Class-Path:
Can-Redefine-Classes:false
)
假設使用 Maven 打包的話, 能夠直接在 pom.xml 里面設置 MANIFEST.MF 的參數 :
maven-jar-plugin
2.4
SizeOfAgent
com.wenniuwuren.objectsizeof.SizeOfAgent
false
false
測試類: SizeOfAgentTest
package com.wenniuwuren.objectsizeof;
import static com.wenniuwuren.objectsizeof.SizeOfAgent.*;
/**
* 下面結果在 64-bit JVM 下測試
* 啟動參數1(不壓縮指針長度):-javaagent:target/SizeOfAgent.jar -XX:-UseCompressedOops
*
* Created by zhuyb on 16/3/20.
*/
public class SizeOfAgentTest {
public static void main(String[] args) {
System.out.println("------------------空對象----------------------------");
// 16 bytes + 0 + 0 = 16 空對象, 僅僅有對象頭
System.out.println("sizeOf(new Object()) = " + sizeOf(new Object()));
System.out.println("fullSizeOf(new Object()) = " + fullSizeOf(new Object()));
System.out.println("----------------非空對象含有原始類型、引用類型------------------------------");
// 16 bytes + 8 + 4 + padding = 32
System.out.println("sizeOf(new A()) = " + sizeOf(new A()));
System.out.println("fullSizeOf(new A()) = " + fullSizeOf(new A()));
// 16 + 4 + padding =24 數據是一個 int
System.out.println("sizeOf(new Integer(1)) = " + sizeOf(new Integer(1)));
// (16 + int hash:4 + int hash32:4 + refer char value[]:8 + padding) = 32
// 靜態屬性(static)不計算空間。由于全部對象都是共享一塊空間的
// 不同版本號JDK可能 String 內部 Field 可能不同,本次測試使用JDK1.7
System.out.println("sizeOf(new String()) = " + sizeOf(new String()));
// (16 + 4 + 4 + 8 + padding) + (24 + 0 + padding) = 56
System.out.println("fullSizeOf(new String()) = " + fullSizeOf(new String()));
// (16 + 4 + 4 + 8 + padding) = 32
System.out.println("sizeOf(new String('a')) = " + sizeOf(new String("a")));
// (16 + 4 + 4 + 8 +padding) + (24 + 2 + padding) = 64
System.out.println("fullSizeOf(new String('a')) = " + fullSizeOf(new String("a")));
System.out.println("-------------------原始類型數組對象---------------------------");
// 24 bytes + 0*1 + 0 = 24 數組長度為 0,所以僅僅有對象頭的長度
System.out.println("sizeOf(new byte[0]) = " + sizeOf(new byte[0]));
System.out.println("fullSizeOf(new byte[0]) = " + fullSizeOf(new byte[0]));
// 24 + 1*1 + padding = 32
System.out.println("sizeOf(new byte[1]) = " + sizeOf(new byte[1]));
System.out.println("fullSizeOf(new byte[1]) = " + fullSizeOf(new byte[1]));
// 24 + 1*2 + padding = 32
System.out.println("sizeOf(new char[1]) = " + sizeOf(new char[1]));
System.out.println("fullSizeOf(new char[1]) = " + fullSizeOf(new char[1]));
// 24 + 9*1 + padding = 40
System.out.println("sizeOf(new byte[9]) = " + sizeOf(new byte[9]));
System.out.println("fullSizeOf(new byte[9]) = " + fullSizeOf(new byte[9]));
System.out.println("--------------------引用類型數組對象--------------------------");
// 24 bytes + 0*8 + 0 = 24 數組長度為 0
System.out.println("sizeOf(new Integer[0]) = " + sizeOf(new Integer[0]));
System.out.println("fullSizeOf(new Integer[0]) = " + fullSizeOf(new Integer[0]));
// 24 bytes + 1*8 + 0 = 32 引用對象 64-bit JVM 占用 8 bytes
System.out.println("sizeOf(new Integer[1]) = " + sizeOf(new Integer[1]));
System.out.println("fullSizeOf(new Integer[1]) = " + fullSizeOf(new Integer[1]));
// 24 bytes + 2*8 + padding = 40
System.out.println("sizeOf(new Integer[1]) = " + sizeOf(new Integer[1]));
System.out.println("fullSizeOf(new Integer[1]) = " + fullSizeOf(new Integer[1]));
// 24 + 3*8 + padding = 48
System.out.println("sizeOf(new Integer[3]) = " + sizeOf(new Integer[3]));
System.out.println("fullSizeOf(new Integer[3]) = " + fullSizeOf(new Integer[3]));
System.out.println("-------------------自己定義數組對象---------------------------");
// 16 + (4+8) + padding = 32
System.out.println("sizeOf(new B()) = " + sizeOf(new B()));
System.out.println("fullSizeOf(new B()) = " + fullSizeOf(new B()));
// 24 + 0*8 + padding = 24 引用對象 64-bit JVM 占用 8 bytes,
// 由于沒創建真實的 new B()所以 B類內部數據還未占用空間
System.out.println("sizeOf(new B[0]) = " + sizeOf(new B[0]));
System.out.println("fullSizeOf(new B[0]) = " + fullSizeOf(new B[0]));
// 24 + 1*8 + padding = 32
System.out.println("sizeOf(new B[1]) = " + sizeOf(new B[1]));
System.out.println("fullSizeOf(new B[1]) = " + fullSizeOf(new B[1]));
// 24 + 2*8 + padding = 40
System.out.println("sizeOf(new B[2]) = " + sizeOf(new B[2]));
System.out.println("fullSizeOf(new B[2]) = " + fullSizeOf(new B[2]));
// 24 + 3*8 + padding = 48
System.out.println("sizeOf(new B[3]) = " + sizeOf(new B[3]));
System.out.println("fullSizeOf(new B[3]) = " + fullSizeOf(new B[3]));
System.out.println("-------------------復合對象---------------------------");
// 16 + (4+8) + padding = 32 sizeOf 僅僅計算單層次占用空間大小
System.out.println("sizeOf(new C()) = " + sizeOf(new C()));
// (16 + (4+8) + padding1) + (24 + 2*8 + padding2) + 2*(16 + (4+8) + padding3) = 136
// 遞歸計算當前對象占用空間總大小,包含當前類和超類的實例字段大小以及實例字段引用對象大小
System.out.println("fullSizeOf(new C()) = " + fullSizeOf(new C()));
System.out.println("-------------------繼承關系---------------------------");
// 涉及繼承關系的時候有一個最主要的規則:首先存放父類中的成員,接著才是子類中的成員, 父類也要依照 8 byte 規定
// 16 + 1 + padding = 24
System.out.println("sizeOf(new D()) = " + sizeOf(new D()));
System.out.println("fullSizeOf(new D()) = " + fullSizeOf(new D()));
// 16 + 父類(1 + padding1) + 1 + padding2 = 32
System.out.println("sizeOf(new E()) = " + sizeOf(new E()));
System.out.println("fullSizeOf(new E()) = " + fullSizeOf(new E()));
}
public static class A {
int a;
Integer b;
}
public static class B {
int a;
Integer b;
}
public static class C{
int c;
B[] b = new B[2];
// 初始化
C() {
for (int i = 0; i < b.length; i++) {
b[i] = new B();
}
}
}
public static class D {
byte d1;
}
public static class E extends D {
byte e1;
}
}
執行:
假設在 IDE 執行時須要設置 JVM 參數:?-javaagent:target/SizeOfAgent.jar -XX:-UseCompressedOops;
假設在命令行執行命令:?java -javaagent:sizeofag.jar
-XX:-UseCompressedOops 主類名稱。
測試結果:
------------------空對象----------------------------
sizeOf(new Object()) = 16
fullSizeOf(new Object()) = 16
----------------非空對象含有原始類型、引用類型------------------------------
sizeOf(new A()) = 32
fullSizeOf(new A()) = 32
sizeOf(new Integer(1)) = 24
sizeOf(new String()) = 32
fullSizeOf(new String()) = 56
sizeOf(new String('a')) = 32
fullSizeOf(new String('a')) = 64
-------------------原始類型數組對象---------------------------
sizeOf(new byte[0]) = 24
fullSizeOf(new byte[0]) = 24
sizeOf(new byte[1]) = 32
fullSizeOf(new byte[1]) = 32
sizeOf(new char[1]) = 32
fullSizeOf(new char[1]) = 32
sizeOf(new byte[9]) = 40
fullSizeOf(new byte[9]) = 40
--------------------引用類型數組對象--------------------------
sizeOf(new Integer[0]) = 24
fullSizeOf(new Integer[0]) = 24
sizeOf(new Integer[1]) = 32
fullSizeOf(new Integer[1]) = 32
sizeOf(new Integer[1]) = 32
fullSizeOf(new Integer[1]) = 32
sizeOf(new Integer[3]) = 48
fullSizeOf(new Integer[3]) = 48
-------------------自己定義數組對象---------------------------
sizeOf(new B()) = 32
fullSizeOf(new B()) = 32
sizeOf(new B[0]) = 24
fullSizeOf(new B[0]) = 24
sizeOf(new B[1]) = 32
fullSizeOf(new B[1]) = 32
sizeOf(new B[2]) = 40
fullSizeOf(new B[2]) = 40
sizeOf(new B[3]) = 48
fullSizeOf(new B[3]) = 48
-------------------復合對象---------------------------
sizeOf(new C()) = 48
fullSizeOf(new C()) = 152
-------------------繼承關系---------------------------
sizeOf(new D()) = 24
fullSizeOf(new D()) = 24
sizeOf(new E()) = 32
fullSizeOf(new E()) = 32
測試類中復合對象計算可能較為麻煩, 能夠參照下圖較為清楚地看出 new C() 的占用空間計算:
六. 總結
總體的 Java 對象是依照一定規則進行的。 清楚了 JVM 對象的內存布局和分配規則。 計算 Java 對象的大小就比較簡單了。
Java 不像 C++ 能夠提供對象大小, 這是 Java 語言的設計初衷(自己主動內存管理), 可是隨著對 Java 的深入了解。 又到了對 JVM (使用 C、C++ 實現) 底層實現的問題上。
本文的參考資料為 2007 年的, 至今已有 9 年, 參考資料內容至今還是有效的,JVM 相關的東西變動確實小,挺有意思的
七. 參考資料