java對象結構 對象頭 Markword


概述

對象實例由對象頭、實例數據組成,其中對象頭包括markword和類型指針,如果是數組,還包括數組長度;

| 類型 | 32位JVM | 64位JVM|
| ------ ---- | ------------| --------- |
| markword | 32bit | 64bit |
| 類型指針 | 32bit |64bit ,開啟指針壓縮時為32bit |
| 數組長度 | 32bit |32bit |

header.png

compressed_header.png

可以看到

  • 開啟指針壓縮時,markword占用8bytes,類型指針占用8bytes,共占用16bytes;
  • 未開啟指針壓縮時,markword占用8bytes,類型指針占用4bytes,但由於java內存地址按照8bytes對齊,長度必須是8的倍數,因此會從12bytes補全到16bytes;
  • 數組長度為4bytes,同樣會進行對齊,補足到8bytes;

另外從上面的截圖可以看到,開啟指針壓縮之后,對象類型指針為0xf800c005,但實際的類型指針為0x7c0060028;那么指針是如何壓縮的呢?實際上由於java地址一定是8的倍數,因此將0xf800c005*8即可得到實際的指針0x7c0060028,關於指針壓縮的更多知識可參考官方文檔

markword結構

markword的結構,定義在markOop.hpp文件:

  1. 32 bits:
  2. --------
  3. hash: 25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
  4. JavaThread*: 23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
  5. size: 32 ------------------------------------------>| (CMS free block)
  6. PromotedObject*: 29 ---------->| promo_bits:3 ----->| (CMS promoted object)
  7.  
  8. 64 bits:
  9. --------
  10. unused: 25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
  11. JavaThread*: 54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
  12. PromotedObject*: 61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
  13. size: 64 ----------------------------------------------------->| (CMS free block)
  14.  
  15. unused: 25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
  16. JavaThread*: 54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
  17. narrowOop: 32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
  18. unused: 21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
  19. [ ptr | 00] locked ptr points to real header on stack
  20. [ header | 0 | 01] unlocked regular object header
  21. [ ptr | 10] monitor inflated lock (header is wapped out)
  22. [ptr | 11] marked used by markSweep to mark an object
  23.  
  24.  

由於目前基本都在使用64位JVM,此處不再對32位的結構進行詳細說明:

偏向鎖標識位 鎖標識位 鎖狀態 存儲內容
0 01 未鎖定 hash code(31),年齡(4)
1 01 偏向鎖 線程ID(54),時間戳(2),年齡(4)
00 輕量級鎖 棧中鎖記錄的指針(64)
10 重量級鎖 monitor的指針(64)
11 GC標記 空,不需要記錄信息

此處,有幾點要注意:

  • 如果對象沒有重寫hashcode方法,那么默認是調用os::random產生hashcode,可以通過System.identityHashCode獲取;os::random產生hashcode的規則為:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存儲;另外一旦生成了hashcode,JVM會將其記錄在markword中;
  • GC年齡采用4位bit存儲,最大為15,例如MaxTenuringThreshold參數默認值就是15;
  • 當處於輕量級鎖、重量級鎖時,記錄的對象指針,根據JVM的說明,此時認為指針仍然是64位,最低兩位假定為0;當處於偏向鎖時,記錄的為獲得偏向鎖的線程指針,該指針也是64位;
  1. We assume that stack/thread pointers have the lowest two bits cleared.
  2. ObjectMonitor* monitor() const {
  3. assert(has_monitor(), "check");
  4. // Use xor instead of &~ to provide one extra tag-bit check.
  5. return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右兩位為10,因此異或之后最右兩位為0
  6. }
  7. JavaThread* biased_locker() const {
  8. assert(has_bias_pattern(), "should not call this otherwise");
  9. return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
  10. //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)為11111111111111111111110010000000,計算后的結果中,低10位全部為0;
  11. }

由於java中內存地址都是8的倍數,因此可以理解為最低3bit為0,因此假設輕量級和重量級鎖的最低2位為0是成立的;但為什么偏向鎖的最低10位都是0?查看markOop.hpp文件,發現有這么一句話:

  1. // Alignment of JavaThread pointers encoded in object header required by biased locking
  2. enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)
  3. //epoch_shift+epoch_bits=10
  4. };

thread.hpp中重載了operator new:

  1. void* operator new(size_t size) { return allocate(size, true); }
  2.  
  3. // ======= Thread ========
  4. // Support for forcing alignment of thread objects for biased locking
  5. void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {
  6. if (UseBiasedLocking) {
  7. const int alignment = markOopDesc::biased_lock_alignment;//10
  8. size_t aligned_size = size + (alignment - sizeof(intptr_t));
  9. void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)
  10. : os:: malloc(aligned_size, flags, CURRENT_PC);
  11. void* aligned_addr = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);
  12. assert((( uintptr_t) aligned_addr + (uintptr_t) size) <=
  13. (( uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),
  14. "JavaThread alignment code overflowed allocated storage");
  15. if (TraceBiasedLocking) {
  16. if (aligned_addr != real_malloc_addr)
  17. tty->print_cr( "Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,
  18. real_malloc_addr, aligned_addr);
  19. }
  20. ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;
  21. return aligned_addr;
  22. } else {
  23. return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)
  24. : os:: malloc(size, flags, CURRENT_PC);
  25. }
  26. }

如果開啟了偏移鎖,在創建線程時,線程地址會進行對齊處理,保證低10位為0

實例數據

實例數據中主要包括對象的各種成員變量,包括基本類型和引用類型;static類型的變量會放到java/lang/Class中,而不會放到實例數據中;
對於引用類型的成員(包括string),存儲的指針;對於基本類型,直接存儲內容;通常會將基本類型存儲在一起,引用類型存儲在一起;
例如類Test的成員定義如下:

  1. private static Test t1=new Test();
  2. private Test t2;
  3. private int a=5;
  4. private Integer b=7;
  5. private String c="112";
  6. private BigDecimal d=new BigDecimal("5");
  7. private long e=9l;

body.png

可以看到long e、int a為基本類型,存儲在一起;其它的引用類型存儲在一起;int占用4bytes,不足8bytes,自動補足到8bytes;

 

鏈接:https://www.jianshu.com/p/ec28e3a59e80


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM