Java中的Sizeof(一)

來源:互聯網
上載者:User
1.Java有類似於C語言中sizeof()的操作器嗎?
    表面答案是Java沒有提供任何類似於C語言的sizeof()的操作器。但是,我們應該想想為什麼Java 程式員偶爾也需要它。
    C語言程式員自己管理大多數的資料結構儲存分配,並且 sizeof()不負責瞭解分配的儲存塊的尺寸大小。C 儲存分配器如malloc(),只要涉及到對象初始化幾乎什麼事都不做:程式員必須設定作為更深一層對象指標的所有對象域。但是當所有的都說過並且編碼過的時候, C/C++ 儲存分配是相當有效。
    相比而言,Java對象分配和構造緊密結合(不可能使用一個已經分配但是沒有初始化的對象)。如果Java類定義了作為更深一層對象的引用的域,在構造階段設定他們也是很普遍的。Java對象分配器因此頻繁地分配互連對象:對象圖形。與自動垃圾收集耦合,所有這一切都太方便了,並且讓你覺得你根本不必擔心Java儲存分配的細節。
    當然,這隻有對簡單的Java應用才有效。相比C/C++而言,同樣的Java資料結構往往佔據更多的實體儲存體。在企業軟體開發中,接近當今32位JVM上的最大虛擬儲存是一個普遍的可縮放性限制。因此,Java 程式員可從sizeof() 或者其他類似的函數中獲益,因為這些函數能夠觀察它的資料結構是否過大或者是否包含儲存瓶頸。幸運的是,Java反射允許你相當容易的編寫這種工具。
接下來,我先討論幾個經常出現的對該問題的錯誤理解。
誤區1:因為Java類型的大小確定所以不需要 Sizeof()
    不錯,Java int在所有JVM和所有的平台上都是32位,但是這隻是一種語言規範要求,程式員可以接受的這種資料類型的寬度。這種int基本上是一種抽象的資料類型,並且可以被 64位裝置上的64位儲存空間字所支援。非初級的類型也不例外:Java語言規範根本沒有涉及這類問題:類域在實體儲存體中應該如何校準或者布爾排列在JVM內部不能作為一個簡單的位向量來實現。
誤區2: 將對象串列成一個位通量然後查看所產生的通量長度就可以測量對象的尺寸大小
    這個方法無效的原因就是串列布局只是真實儲存空間內布局的遠程反射。舉例說,通過觀察String是如何串列的:在儲存空間內每個char至少2個位元組,但是在串列的格式中 String是UTF-8編碼的,所以任何ASCII內容只佔了一半的空間。
另外一個解決方式

    你可能想起在"Java Tip 130: Do You Know Your Data Size?"一文中描述了一個技巧:在建立大量的標記類的基礎上,仔細的測量在JVM使用的堆棧尺寸中所產生的增長。如果合適的話,這個技巧相當有用,實際上我在本文中也用它來引導備用的方法。
     注意:Java 技巧130中的類Sizeof需要一個靜態JVM (這樣堆棧活動只能由測量線程請求的對象分配和垃圾收集的操作引起),還需要大量的同一對象執行個體。如果你想測量單一大型物件(可能作為調試跟蹤輸出的一部分)的尺寸大小,特別是如果你想測試出實際上是什麼使他變得這麼大的時候,這個方法就無效了。
2.什麼是對象的尺寸?
    上述討論突出了一個哲學問題:假設你經常處理對象圖形,那麼對象尺寸的定義是什麼呢?他是指你正在測量的對象執行個體的尺寸大小還是指根於對象執行個體的整個資料圖形?後者在實際生活中使用的更多一些。如你所見,事情不總是劃分得如此清楚,但是對於啟動程式來說你可以參照以下方法:
· 一個對象的所有非待用資料域(包括在超類中定義的域)的總和就是它的尺寸
· 與C++不同,類方法以及他們的虛擬不影響對象的尺寸
· 類超介面不影響對象尺寸(見該列表末尾的注釋)
· 完整的對象尺寸可作為根於啟始物件的整個對象圖形的閉合來獲得
   注釋:實現任何Java介面只對懷疑類做標記,而且不添加任何資料到它的定義上。實際上, JVM 甚至不校正介面實現有沒有提供介面所請求的所有方法:在目前的規範中,這嚴格說來是編譯器的責任。
    為了引導整個進程,對於初級資料類型,我使用Java 技巧130的Sizeof 類來測量物理尺寸。正如它所證明的一樣,對於普通的32位JVM來說,一個簡單的java.lang.Object 佔了8位,並且基礎資料型別 (Elementary Data Type)通常都是能夠適應語言要求的最少的物理尺寸 (除了boolean 要佔據整個位元組之外):


// java.lang.Object shell size in bytes:public static final int OBJECT_SHELL_SIZE   = 8;public static final int OBJREF_SIZE         = 4;public static final int LONG_FIELD_SIZE     = 8;public static final int INT_FIELD_SIZE      = 4;public static final int SHORT_FIELD_SIZE    = 2;public static final int CHAR_FIELD_SIZE     = 2;public static final int BYTE_FIELD_SIZE     = 1;public static final int BOOLEAN_FIELD_SIZE  = 1;public static final int DOUBLE_FIELD_SIZE   = 8;public static final int FLOAT_FIELD_SIZE    = 4;

    (這些常量不是永遠硬式編碼,並且對於一個給定的JVM,它們必須獨立測量,認識到這一點很重要)當然,幼稚的計算對象域尺寸總和往往忽略了JVM中的儲存隊列問題。儲存隊列真的很有關係(例如,Java Tip 130中的初級排列類型),但是我認為在這種低層級的細節上做文章是沒有用的。這種細節不但由JVM開發商決定,它們也處在程式員的控制之下。我們的目標是擷取對象尺寸的最好估測,並且希望在類域多餘、域應該簡單組裝、或者有必要更緊湊的嵌入資料庫等這些時候可以獲得提示。為了絕對的物理精度,你可以總是回到Java Tip 130中的Sizeof 類。
    為了協助組成對象執行個體的設定檔,我們的工具不僅僅計算尺寸,還建立一個附帶的有用的資料結構:由IObjectProfileNode組成的圖形:


interface IObjectProfileNode{    Object object ();    String name ();        int size ();    int refcount ();        IObjectProfileNode parent ();    IObjectProfileNode [] children ();    IObjectProfileNode shell ();        IObjectProfileNode [] path ();    IObjectProfileNode root ();    int pathlength ();        boolean traverse (INodeFilter filter, INodeVisitor visitor);    String dump ();} // End of interface

    IObjectProfileNodes採用與原始對象圖形非常類似的方法互連,它使用了返回每個節點所代表的實際對象的IObjectProfileNode.object()函數。IObjectProfileNode.size()返回以該節點的對象執行個體為根的對象子樹的總體尺寸(以位元組為單位)。如果對象執行個體通過非空執行個體域或者通過包含在排列域內部的引用連結到其他對象上,那麼IObjectProfileNode.children()將會變成按降序排列的子圖形節點的相應列表。相反,對於每個不是起始節點的節點來說,IObjectProfileNode.parent()返回他們的父節點。從而IObjectProfileNode的整個收集就會切斷原始對象,並且展示Object Storage Service在其內部如何分割。而且,圖形節點名源於類域,檢測圖形內的節點路徑(IObjectProfileNode.path())允許你回溯從原始對象執行個體到資料的任一部分的擁有權鏈結接。
    你可能已經注意到上述段落有些思想表達得有點含糊。如果在遍曆對象圖形過程中,你不止一次的遇到同一對象(如,在圖形中不止一個域指向它),你將如何分配它的所有權(父指標)?考慮下列程式碼片段:


Object obj = new String [] {new String ("JavaWorld"),                                new String ("JavaWorld")};

    每個java.lang.String執行個體都有類型char[]的內域,類型char[]具有真正的字串內容。String複製構造器的方式在Java 2平台標準版(J2SE) 1.4中有效,上述排列內的兩個 String執行個體共用同一個包含{'J', 'a', 'v', 'a', 'W', 'o', 'r', 'l', 'd'}字元序列的char[]排列。兩個字串同等的屬於這個排列,那麼像這種情況你怎麼辦?
    如果我總是想將單個父節點賦給圖形節點,那麼這個問題就沒有普遍適用的答案。但是實際上,許多這樣的對象執行個體可以回溯到一個單一的"自然的"父節點。這種自然的連結序列通常比其他的更迂迴的路徑要短。把執行個體域指向的資料看作更從屬於該執行個體而不是其它。把排列中的項看作更從屬於該排列自己。因此,如果內部對象執行個體可通過幾條路經到達,我們選擇最短的那條路經。如果路徑一樣長,我們就選最早發現的那一條。在最壞的情況下,這個萬能策略很有用。 考慮圖形遍曆和最短路徑應該注意這一點:寬度優先的搜尋,這個圖形遍曆能夠保證找到從起始節點到任何其他可到達的圖形節點之間的最短路徑。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.