程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JVM —— Java 對象占用空間大小計算

JVM —— Java 對象占用空間大小計算

編輯:JAVA綜合教程

JVM —— Java 對象占用空間大小計算


零. 為什麼要知道 Java 對象占用空間大小
  1. 緩存的實現: 在設計 JVM 內緩存時(不是借助 Memcached、 Redis 等), 需要知道緩存的對象是否會超過 JVM 最大堆限制, 如果會超過要設置相應算法如 LRU 來丟棄一部分緩存數據以滿足後續內容的緩存JVM 參數設置: 如果知道對象會被創建, 可以幫助判斷 -Xmx 需要設置多少只是為了好玩   一. 對象的內存布局 HotSpot 虛擬機中,對象在內存中存儲的布局可以分為三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。   二. 對象頭 JVM 對象頭一般占用兩個機器碼,在 32-bit JVM 上占用 64bit, 在 64-bit JVM 上占用 128bit 即 8+8=16 bytes(開啟指針壓縮後占用 4+8=12 bytes)   64位機器上,數組對象的對象頭占用 24 bytes,啟用壓縮之後占用 16 bytes。之所以比普通對象占用內存多是因為需要額外的空間存儲數組的長度。   三. 實例數據

    原生類型(primitive type)的內存占用如下:

    Primitive Type Memory Required(bytes) boolean 1 byte 1 short 2 char 2 int 4 float 4 long 8 double 8   引用類型(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
    使用上述代碼必須將上述代碼打成 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 相關的東西變動確實小,挺有意思的

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved