<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0
    在EHCache中,可以設置maxBytesLocalHeap、maxBytesLocalOffHeap、maxBytesLocalDisk值,以控制Cache占用的內存、磁盤的大小(注:這里Off Heap是指Element中的值已被序列化,但是還沒寫入磁盤的狀態,貌似只有企業版的EHCache支持這種配置;而這里maxBytesLocalDisk是指在最大在磁盤中的數據大小,而不是磁盤文件大小,因為磁盤文中有一些數據是空閑區),因而EHCache需要有一種機制計算一個類在內存、磁盤中占用的字節數,其中在磁盤中占用的字節大小計算比較容易,只需要知道序列化后字節數組的大小,并且加上一些統計信息,如過期時間、磁盤位置、命中次數等信息即可,而要計算一個對象實例在內存中占用的大小則要復雜一些。

    計算一個實例內存占用大小思路
    在Java中,除了基本類型,其他所有通過字段包含其他實例的關系都是引用關系,因而我們不能直接計算該實例占用的內存大小,而是要遞歸的計算其所有字段占用的內存大小的和。在Java中,我們可以將所有這些通過字段引用簡單的看成一種樹狀結構,這樣就可以遍歷這棵樹,計算每個節點占用的內存大小,所有這些節點占用的內存大小的總和就當前實例占用的內存大小,遍歷的算法有:先序遍歷、中序遍歷、后序遍歷、層級遍歷等。但是在實際情況中很容易出現環狀引用(最簡單的是兩個實例之間的直接引用,還有是多個實例構成的一個引用圈),而破壞這種樹狀結構,而讓引用變成圖狀結構。然而圖的遍歷相對比較復雜(至少對我來說),因而我更愿意把它繼續看成一顆樹狀圖,采用層級遍歷,通過一個IdentitySet紀錄已經計算過的節點(實例),并且使用一個Queue來紀錄剩余需要計算的節點。算法步驟如下:
    1. 先將當前實例加入Queue尾中。
    2. 循環取出Queue中的頭節點,計算它占用的內存大小,加到總內存大小中,并將該節點添加到IdentitySet中。
    3. 找到該節點所有非基本類型的子節點,對每個子節點,如果在IdentityMap中沒有這個子節點的實例,則將該實例加入的Queue尾。
    4. 回到2繼續計算直到Queue為空。
    剩下的問題就是如何計算一個實例本身占用的內存大小了。這個以我目前的經驗,我只能想到遍歷一個實例的所有實例字段,根據每個字段的類型來判斷每個字段占用的內存大小,然后它們的和就是該實例占用的總內存的大小。對于字段的類型,首先是基本類型字段,byte、boolean占一個字節,short、char占2個字節,int、float占4個字節,double占8個字節等;然后是引用類型,對類型,印象中虛擬機規范中沒有定義其大小,但是一般來說對32位系統占4個字節,對64位系統占8個字節;再就是對數組,基本類型的數組,byte每個元素占1個字節,short、char每個元素占2個字節,int每個元素占4個字節,double每個元素占8個字節,引用類型的數組,先計算每個引用元素占用的字節數,然后是引用本省占用的字節數。
    以上是我對EHCache中計算一個實例邏輯不了解的時候的個人看法,那么接下來我們看看EHCache怎么來計算。

    Java對象內存結構(以Sun JVM為例)
    參考:http://www.importnew.com/1305.html,之所以把參考鏈接放在開頭是因為下面基本上是對鏈接所在文章的整理,之所以要整理一遍,一是怕原鏈接文章消失,二則是為了加深自己的理解。
    在Sun JVM中,除數組以外的對象都有8個字節的頭部(數組還有額外的4個字節頭部用于存放長度信息),前面4個字節包含這個對象的標識哈希碼以及其他一些flag,如鎖狀態、年齡等標識信息,后4個字節包含一個指向對象的類實例(Class實例)的引用。在這頭部8個字節之后的內存結構遵循一下5個規則:
    規則1: 任何對象都是以8個字節為粒度進行對齊的。
    比如對一個Object類,因為它沒有任何實例,因而它只有8個頭部直接,則它占8個字節大小。而對一個只包含一個byte字段的實例,它需要填上(padding)7個字節的大小,因而它占16個字節,典型的如一個Boolean實例要占用16個字節的內存!
    class MyClass {
        byte a;
    }
    [HEADER:    8 bytes] 8
    [a:             1 byte ] 9
    [padding:    7 bytes] 16
    規則2: 類屬性按照如下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型;最后是引用類型。這些屬性都按照各自的單位對齊。
    在Java對象內存結構中,對象以上述的8個字節的頭部開始,然后對象屬性緊隨其后。為了節省內存,Sun VM并沒有按照屬性聲明時順序來進行內存布局,而是使用如下順序排列:
    1. 雙精度型(double)和長整型(long),8字節。
    2. 整型(int)和浮點型(float),4字節。
    3. 短整型(short)和字符型(char),2字節。
    4. 布爾型(boolean)和字節型(byte),2字節。
    5. 引用類型。
    并且對象屬性總是以它們的單位對齊,對于不滿4字節的數據類型,會填充未滿4字節的部分。之所以要填充是出于性能考慮:因為從內存中讀取4字節數據到4字節寄存器的動作,如果數據以4字節對齊的情況小,效率要高的多。
    class MyClass {
        byte a;
        int c;
        boolean d;
        long e;
        Object f;
    }
    //如果JVM不對其重排序,它要占40個字節
    [HEADER:    8 bytes] 8
    [a:             1 byte ] 9
    [padding:    3 bytes] 12
    [c:             4 bytes] 16
    [d:             1 byte ] 17
    [padding:    7 bytes] 24
    [e:             8 bytes] 32
    [f:              4 bytes] 36
    [padding:     4 bytes] 40
    //經JVM重排序后,只需要占32個字節
    [HEADER:       8 bytes] 8
    [e:                8 bytes] 16
    [c:                4 bytes] 20
    [a:                1 byte ] 21
    [d:                1 byte ] 22
    [padding:       2 bytes] 24
    [f:                4 bytes] 28
    [padding:       4 bytes] 32
    規則3: 不同類繼承關系中的成員不能混合排列。首先按照規則2處理父類中的成員,接著才是子類的成員。
    class A {
        long a;
        int b;
        int c;
    }
    class B extends A {
        long d;
    }
    [HEADER:      8 bytes] 8
    [a:               8 bytes] 16
    [b:               4 bytes] 20
    [c:               8 bytes] 32
    規則4: 當父類最后一個屬性和子類第一個屬性之間間隔不足4字節時,必須擴展到4個字節的基本單位。
    class A {
        byte a;
    }
    class B extends A {
        byte b;
    }
    [HEADER:    8 bytes] 8
    [a:             1 byte ] 9
    [padding:    3 bytes] 12
    [b:             1 byte ] 13
    [padding:    3 bytes] 16
    規則5: 如果子類第一個成員時一個雙精度或長整型,并且父類沒有用完8個字節,JVM會破壞規則2,按整型(int)、短整型(short)、字節型(byte)、引用類型(reference)的順序向未填滿的空間填充。
    class A {
        byte a;
    }
    class B extends A {
        long b;
        short c;
        byte d;
    }
    [HEADER:    8 bytes] 8
    [a:             1 byte ] 9
    [padding:    3 bytes] 12
    [c:             2 bytes] 14
    [d:             1 byte ] 15
    [padding:    8 bytes] 24
    數組內存布局
    數組對象除了作為對象而存在的頭以外,還存在一個額外的頭部成員用來存放數組的長度,它占4個字節。
    //三個元素的字節數組
    [HEADER:    12 bytes] 12
    [[0]:             1  byte ] 13
    [[1]:              1 byte ] 14
    [[2]:              1 byte ] 15
    [padding:      1 byte ] 16
    //三個元素的長整型數組
    [HEADER:     12 bytes] 12
    [padding:     4 bytes ] 16
    [[0]:               8 bytes] 24
    [[1]:               8 bytes] 32
    [[2]:               8 bytes] 40
    非靜態內部類
    非靜態內不累它又一個額外的“隱藏”成員,這個成員時一個指向外部類的引用變量。這個成員是一個普通引用,因此遵循引用內存布局的規則。因此內部類有4個字節的額外開銷。

    EHCache計算一個實例占用的內存大小

    EHCache中計算一個實例占用內存大小的基本思路和以上類似:遍歷實例數上的所有節點,對每個節點計算其占用的內存大小。不過它結構設計的更好,而且它有三種用于計算一個實例占用內存大小的實現。我們先來看這三種用于計算一個實例占用內存大小的邏輯:
    1. ReflectionSizeOf
      使用反射的方式計算計算一個實例占用的內存大小就是我上面想到的這種方法。

      因為使用反射計算一個實例占用內存大小的根據不同虛擬機的特性是來判斷一個實例的各個字段占用的大小以及該實例存儲額外信息占用的大小,因而EHCache中采用JvmInformation枚舉類型來抽象這種對不同虛擬機實現的不同:
      JVM Desc PointerSize JavaPointerSize MinimumObjectSize ObjectAlignment ObjectHeaderSize FieldOffsetAdjustment AgentSizeOfAdjustment
      HotSpot 32-Bit 4 4 8 8 8 0 0
      HotSpot 32-Bit with Concurrent Mark-and-Sweep GC 4 4 16 8 8 0 0
      HotSpot 64-Bit 8 8 8 8 16 0 0
      HotSpot 64-Bit With Concurrent Mark-and-Sweep GC 8 8 24 8 16 0 0
      HotSpot 64-Bit with Compressed OOPs 8 4 8 8 12 0 0
      HotSpot 64-Bit with Compressed OOPs and Concurrent Mark-and-Sweep GC 8 4 24 8 12 0 0
      JRockit 32-Bit 4 4 8 8 16 8 8
      JRockit 64-Bit(with no reference compression) 4 4 8 8 16 8 8
      JRockit 64-Bit with 4GB compressed References 4 4 8 8 16 8 8
      JRockit 64-Bit with 32GB Compressed References 4 4 8 8 16 8 8
      JRockit 64-Bit with 64GB Compressed References 4 4 16 16 24 16 16
      IBM 64-Bit with Compressed References 4 4 8 8 16 0 0
      IBM 64-Bit with no reference compression 8 8 8 8 24 0 0
      IBM 32-Bit 4 4 8 8 16 0 0
      UNKNOWN 32-Bit 4 4 8 8 8 0 0
      UNKNOWN 64-Bit 8 8 8 8 16 0 0

      ObjectAligment default: 8
      MinimumObjectSize default equals ObjectAligment
      ObjectHeaderSize default: PointerSize + JavaPointerSize
      FIeldOffsetAdjustment default: 0
      AgentSizeOfAdjustment default: 0
      ReferenceSize equals JavaPointerSize
      ArrayHeaderSize: ObjectHeaderSize + 4(INT Size)
      JRockit and IBM JVM do not support ReflectionSizeOf


      而對基本類型,則因為虛擬機的規范,它們都是相同的,EHCache中采用PrimitiveType枚舉類型來定義不同基本類型的長度:
      enum PrimitiveType {
          BOOLEAN(boolean.class, 1),
          BYTE(byte.class, 1),
          CHAR(char.class, 2),
          SHORT(short.class, 2),
          INT(int.class, 4),
          FLOAT(float.class, 4),
          DOUBLE(double.class, 8),
          LONG(long.class, 8);

          private Class<?> type;
          private int size;

          public static int getReferenceSize() {
              return CURRENT_JVM_INFORMATION.getJavaPointerSize();
          }
          public static long getArraySize() {
              return CURRENT_JVM_INFORMATION.getObjectHeaderSize() + INT.getSize();
          }
      }

      反射計算一個實例(instance)占用內存大小(size)步驟如下:
      a. 如果instance為null,size為0,直接返回。
      b. 如果instance是數組類型,size為數組頭部大小+每個數組元素占用大小*數組長度+填充到對象對齊最小單位,最后保證如果size要比對象最小大小大過相等。
      c. 如果instance是普通實例,size初始值為對象頭部大小,然后找到對象對應類的所有繼承類,從最頂層類開始遍歷所有類(規則3),對每個類,紀錄長整型和雙精度型、整型和浮點型、短整型和字符型、布爾型和字節型以及引用類型的非靜態字段的個數。如果整型和雙精度型字段個數不為0,且當前size沒有按長整型的大小對齊(規則5),選擇部分其他類型字段排在長整型和雙精度型之前,直到填充到以長整型大小對齊,然后按照先規則2的順序排列個字計算不同類型字段的大小。在每個類之間如果沒有按規定大小對齊,則填充缺少的字節(規則4)。在所有類計算完成后,如果沒有按照類的對齊方式,則按類對齊規則對齊(規則1)。最后保證一個對象實例的大小要一個對象最小大小要大或相等。

    2. UnsafeSizeOf中
      UnsafeSizeOf的實現比反射的實現要簡單的多,它使用Sun內部庫的Unsafe類來獲取字段的offset值來計算一個類占用的內存大小(個人理解,這個應該只支持Sun JVM,但是怎么JRockit中有對FieldOffsetAdjustment的配置,而該方法只在這個類中被使用。。。)。對數組,它使用Unsafe.arrayBaseOffset()方法返回數組頭大小,使用Unsafe.arrayIndexScale()方法返回一個數組元素占用的內存大小,其他計算和反射機制類似。這里在最后計算填充前有對FieldOffsetAdjustment的調整,貌似在JRockit JVM中使用到了,不了解為什么它需要這個調整。對實例大小的計算也比較簡單,它首先遍歷當前類和父類的所有非靜態字段,通過Unsafe.objectFieldOffset()找到最后一個字段的offset,根據之前Java實例內存結構,要找到最后一個字段,只需從當前類到最頂層父類遍歷第一個有非靜態字段的類的所有非靜態字段即可。在找到最后一個字段的offset以后也需要做FieldOffsetAdjustment調整,之后還需要加1(因為有對象對齊大小對齊,因而通過加1而避免考慮最后一個字段類型的問題,很巧妙的代碼!)。最后根據規則以對對象以對象對齊大小對齊。

    3. AgentSizeOf
      在Java 1.5以后,提供了Instrumentation接口,可以調用該接口的getObjectSize方法獲取一個對象實例占用的內存大小。對Instrumentation的機制不熟,但是從EHCache代碼的實現角度上,它首先需要有一個sizeof-agent.jar的包(包含在net.sf.ehcache.pool.sizeof中),在該jar包的MANIFEST.MF文件中指定Premain-Class類,這個類實現兩個靜態的premain、agentmain方法。在實際運行時,EHCache會將sizeof-agent.jar拷貝到臨時文件夾中,然后調用Sun工具包中的VirtualMachine的靜態attach方法,獲取一個VirtualMachine實例,然后調用其實例方法loadAgent方法,傳入sizeof-agent.jar文件全路徑,即可將一個SizeOfAgent類附著到當前實例中,而我們就可以通過SizeOfAgent類來獲取它的Instrumentation實例來計算一個實例的大小。
    我們可以使用一下一個簡單的例子來測試一下各種不同計算方法得出的結果:
    public class EhcacheSizeOfTest {
        public static void main(String[] args) {
            MyClass ins = new MyClass();
            
            System.out.println("ReflectionSizeOf: " + calculate(new ReflectionSizeOf(), ins));
            System.out.println("UnsafeSizeOf: " + calculate(new UnsafeSizeOf(), ins));
            System.out.println("AgentSizeOf: " + calculate(new AgentSizeOf(), ins));
        }
        
        private static long calculate(SizeOf sizeOf, Object instance) {
            return sizeOf.sizeOf(instance);
        }
        
        public static class MyClass {
            byte a;
            int c;
            boolean d;
            long e;
            Object f;
        }
    }
    //輸出結果如下(問題:這里的JVM是64-Bit HotSpot JVM with Compressed OOPs,它的實例頭部占用了12個字節大小,但是它占用內存的大小還是和32位的大小一樣,這是為什么?):
    [31 23:21:19,598 INFO ] [main] sizeof.JvmInformation - Detected JVM data model settings of: 64-Bit HotSpot JVM with Compressed OOPs
    ReflectionSizeOf: 32
    UnsafeSizeOf: 32
    [31 23:26:52,479 INFO ] [main] sizeof.AgentLoader - Located valid 'tools.jar' at 'C:\Program Files\Java\jdk1.7.0_25\jre\..\lib\tools.jar'
    [31 23:26:52,729 INFO ] [main] sizeof.AgentLoader - Extracted agent jar to temporary file C:\Users\DINGLE~1\AppData\Local\Temp\ehcache-sizeof-agent6171098352070763093.jar
    [31 23:26:52,729 INFO ] [main] sizeof.AgentLoader - Trying to load agent @ C:\Users\DINGLE~1\AppData\Local\Temp\ehcache-sizeof-agent6171098352070763093.jar
    AgentSizeOf: 32

    Deep SizeOf計算
    EHCache中的SizeOf類中還提供了deepSize計算,它的步驟是:使用ObjectGraphWalker遍歷一個實例的所有對象引用,在遍歷中通過使用傳入的SizeOfFilter過濾掉那些不需要的字段,然后調用傳入的Visitor對每個需要計算的實例做計算。
    ObjectGraphWalker的實現算法和我之前所描述的類似,稍微不同的是它使用了Stack,我更傾向于使用Queue,只是這個也只是影響遍歷的順序,這里有點深度優先還是廣度優先的味道。另外,它抽象了SizeOfFilter接口,可以用于過濾掉一些不想用于計算內存大小的字段,如Element中的key字段。SizeOfFilter提供了對類和字段的過濾:
    public interface SizeOfFilter {
        // Returns the fields to walk and measure for a type
        Collection<Field> filterFields(Class<?> klazz, Collection<Field> fields);
        // Checks whether the type needs to be filtered
        boolean filterClass(Class<?> klazz);
    }
    SizeOfFilter的實現類可以用于過濾過濾掉@IgnoreSizeOf注解的字段和類,以及通過net.sf.ehcache.sizeof.filter系統變量定義的文件,讀取其中的每一行為包名或字段名作為過濾條件。最后,為了性能考慮,它對一些計算結果做了緩存。

    ObjectGraphWalker中,它還會忽略一些系統原本就存在的一些靜態變量以及類實例,所有這些信息都定義在FlyweightType類中。

    SizeOfEngine類
    SizeOfEngine是EHCache中對使用不同方式做SizeOf計算的抽象,如在計算內存中對象的大小需要使用SizeOf類來實現,而計算磁盤中數據占用的大小直接使用其size值即可,因而在EHCache中對SizeOfEngine有兩個實現:DefaultSizeOfEngine和DiskSizeOfEngine。對DiskSizeOfEngine比較簡單,其container參數必須是DiskMarker類型,并且直接返回其size字段即可;對DefaultSizeOfEngine,則需要配置SizeOfFilter和SizeOf子類實現問題,對SizeOfFilter,它會默認加入AnnotationSizeOfFilter、使用builtin-sizeof.filter文件中定義的類、字段配置的ResourceSizeOfFilter、用戶通過net.sf.ehcache.sizeof.filter配置的filter文件的ResourceSizeOfFilter;對SizeOf的子類實現問題,它優先選擇AgentSizeOf,如果不支持則使用UnsafeSizeOf,最后才使用ReflectionSizeOf。
    public interface SizeOfEngine {
        Size sizeOf(Object key, Object value, Object container);
        SizeOfEngine copyWith(int maxDepth, boolean abortWhenMaxDepthExceeded);
    }

    還可以參考

    http://www.javamex.com/tutorials/memory/
    posted on 2013-11-01 11:03 DLevin 閱讀(8522) 評論(1)  編輯  收藏 所屬分類: EHCache

    FeedBack:
    # re: Java Cache-EHCache系列之計算實例占用的內存大小(SizeOf引擎)
    2013-11-02 10:53 | 零柒鎖業
    而破壞這種樹狀結構,而讓引用變成圖狀結構  回復  更多評論
      
    主站蜘蛛池模板: 在线观看亚洲免费| 日韩亚洲Av人人夜夜澡人人爽| 国产精品青草视频免费播放| 亚洲av无码一区二区乱子伦as| 免费h片在线观看网址最新| 亚洲a∨无码一区二区| 亚洲国产精品无码AAA片| 无码中文字幕av免费放| 无遮挡a级毛片免费看| 777亚洲精品乱码久久久久久 | 亚洲AV无码成人精品区天堂| 免费人成在线视频| 久久久WWW成人免费精品| 亚洲一区二区三区高清视频| 亚洲一区精品伊人久久伊人| 免费国产成人高清在线观看网站| 国产成人 亚洲欧洲| 久久久亚洲AV波多野结衣| 免费v片在线观看无遮挡| 7x7x7x免费在线观看| 曰批全过程免费视频观看免费软件| 亚洲精品美女在线观看播放| 亚洲精品综合久久| 久久国内免费视频| 日韩免费电影网址| 一级毛片a免费播放王色电影| 亚洲午夜久久久精品电影院| 国产亚洲精AA在线观看SEE| 国产精品成人四虎免费视频| 最近中文字幕电影大全免费版 | 亚洲国产美女精品久久| 中文字幕第一页亚洲| 日本一道本高清免费| 国产卡一卡二卡三免费入口 | 成年在线网站免费观看无广告 | 亚洲欧美综合精品成人导航| 2022年亚洲午夜一区二区福利| 亚洲无码精品浪潮| 夜色阁亚洲一区二区三区| 毛片在线看免费版| 成人免费视频69|