您好,登錄后才能下訂單哦!
java對象結(jié)構(gòu) 對象頭 Markword?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
概述
對象實例由對象頭、實例數(shù)據(jù)組成,其中對象頭包括markword和類型指針,如果是數(shù)組,還包括數(shù)組長度;
| 類型 | 32位JVM | 64位JVM|
| ------ ---- | ------------| --------- |
| markword | 32bit | 64bit |
| 類型指針 | 32bit |64bit ,開啟指針壓縮時為32bit |
| 數(shù)組長度 | 32bit |32bit |
header.png
compressed_header.png
可以看到
開啟指針壓縮時,markword占用8bytes,類型指針占用8bytes,共占用16bytes;
未開啟指針壓縮時,markword占用8bytes,類型指針占用4bytes,但由于java內(nèi)存地址按照8bytes對齊,長度必須是8的倍數(shù),因此會從12bytes補全到16bytes;
數(shù)組長度為4bytes,同樣會進行對齊,補足到8bytes;
另外從上面的截圖可以看到,開啟指針壓縮之后,對象類型指針為0xf800c005,但實際的類型指針為0x7c0060028;那么指針是如何壓縮的呢?
實際上由于java地址一定是8的倍數(shù),因此將0xf800c005*8即可得到實際的指針0x7c0060028,關(guān)于指針壓縮的更多知識可參考官方文檔。
markword結(jié)構(gòu)
markword的結(jié)構(gòu),定義在markOop.hpp文件:
32 bits: -------- hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) size:32 ------------------------------------------>| (CMS free block) PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object) 64 bits: -------- unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object) JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object) size:64 ----------------------------------------------------->| (CMS free block) unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object) JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object) narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object) unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block) [ptr | 00] locked ptr points to real header on stack [header | 0 | 01] unlocked regular object header [ptr | 10] monitor inflated lock (header is wapped out) [ptr | 11] marked used by markSweep to mark an object
由于目前基本都在使用64位JVM,此處不再對32位的結(jié)構(gòu)進行詳細說明:
偏向鎖標(biāo)識位 | 鎖標(biāo)識位 | 鎖狀態(tài) | 存儲內(nèi)容 |
---|---|---|---|
0 | 01 | 未鎖定 | hash code(31),年齡(4) |
1 | 01 | 偏向鎖 | 線程ID(54),時間戳(2),年齡(4) |
無 | 00 | 輕量級鎖 | 棧中鎖記錄的指針(64) |
無 | 10 | 重量級鎖 | monitor的指針(64) |
無 | 11 | GC標(biāo)記 | 空,不需要記錄信息 |
此處,有幾點要注意:
如果對象沒有重寫hashcode方法,那么默認是調(diào)用os::random產(chǎn)生hashcode,可以通過System.identityHashCode獲??;os::random產(chǎn)生hashcode的規(guī)則為:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存儲;另外一旦生成了hashcode,JVM會將其記錄在markword中;
GC年齡采用4位bit存儲,最大為15,例如MaxTenuringThreshold參數(shù)默認值就是15;
當(dāng)處于輕量級鎖、重量級鎖時,記錄的對象指針,根據(jù)JVM的說明,此時認為指針仍然是64位,最低兩位假定為0;當(dāng)處于偏向鎖時,記錄的為獲得偏向鎖的線程指針,該指針也是64位;
We assume that stack/thread pointers have the lowest two bits cleared. ObjectMonitor* monitor() const { assert(has_monitor(), "check"); // Use xor instead of &~ to provide one extra tag-bit check. return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右兩位為10,因此異或之后最右兩位為0 } JavaThread* biased_locker() const { assert(has_bias_pattern(), "should not call this otherwise"); return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)))); //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)為11111111111111111111110010000000,計算后的結(jié)果中,低10位全部為0; }
由于java中內(nèi)存地址都是8的倍數(shù),因此可以理解為最低3bit為0,因此假設(shè)輕量級和重量級鎖的最低2位為0是成立的;但為什么偏向鎖的最低10位都是0?查看markOop.hpp文件,發(fā)現(xiàn)有這么一句話:
// Alignment of JavaThread pointers encoded in object header required by biased locking enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits) //epoch_shift+epoch_bits=10 };
thread.hpp中重載了operator new:
void* operator new(size_t size) { return allocate(size, true); } // ======= Thread ======== // Support for forcing alignment of thread objects for biased locking void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) { if (UseBiasedLocking) { const int alignment = markOopDesc::biased_lock_alignment;//10 size_t aligned_size = size + (alignment - sizeof(intptr_t)); void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC) : os::malloc(aligned_size, flags, CURRENT_PC); void* aligned_addr = (void*) align_size_up((intptr_t) real_malloc_addr, alignment); assert(((uintptr_t) aligned_addr + (uintptr_t) size) <= ((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size), "JavaThread alignment code overflowed allocated storage"); if (TraceBiasedLocking) { if (aligned_addr != real_malloc_addr) tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT, real_malloc_addr, aligned_addr); } ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr; return aligned_addr; } else { return throw_excpt? AllocateHeap(size, flags, CURRENT_PC) : os::malloc(size, flags, CURRENT_PC); } }
如果開啟了偏移鎖,在創(chuàng)建線程時,線程地址會進行對齊處理,保證低10位為0
實例數(shù)據(jù)
實例數(shù)據(jù)中主要包括對象的各種成員變量,包括基本類型和引用類型;static類型的變量會放到j(luò)ava/lang/Class中,而不會放到實例數(shù)據(jù)中;
對于引用類型的成員(包括string),存儲的指針;對于基本類型,直接存儲內(nèi)容;通常會將基本類型存儲在一起,引用類型存儲在一起;
例如類Test的成員定義如下:
private static Test t1=new Test(); private Test t2; private int a=5; private Integer b=7; private String c="112"; private BigDecimal d=new BigDecimal("5"); private long e=9l;
body.png
可以看到long e、int a為基本類型,存儲在一起;其它的引用類型存儲在一起;int占用4bytes,不足8bytes,自動補足到8bytes;
補充知識:java的對象物理結(jié)構(gòu),以及對象頭中MarkWord與鎖的關(guān)系
java 對象頭
我們都知道,Java對象存儲在堆(Heap)內(nèi)存。那么一個Java對象到底包含什么呢?概括起來分為對象頭、對象體和對齊字節(jié)。
如下圖所示:
對象的幾個部分的作用:
1.對象頭中的Mark Word(標(biāo)記字)主要用來表示對象的線程鎖狀態(tài),另外還可以用來配合GC、存放該對象的hashCode;
2.Klass Word是一個指向方法區(qū)中Class信息的指針,意味著該對象可隨時知道自己是哪個Class的實例;
3.數(shù)組長度也是占用64位(8字節(jié))的空間,這是可選的,只有當(dāng)本對象是一個數(shù)組對象時才會有這個部分;
4.對象體是用于保存對象屬性和值的主體部分,占用內(nèi)存空間取決于對象的屬性數(shù)量和類型;
5.對齊字是為了減少堆內(nèi)存的碎片空間(不一定準(zhǔn)確)。
了解了對象的總體結(jié)構(gòu),接下來深入地了解對象頭的三個部分。
一、Mark Word(標(biāo)記字)
以上是Java對象處于5種不同狀態(tài)時,Mark Word中64個位的表現(xiàn)形式,上面每一行代表對象處于某種狀態(tài)時的樣子。其中各部分的含義如下:
lock:2位的鎖狀態(tài)標(biāo)記位,由于希望用盡可能少的二進制位表示盡可能多的信息,所以設(shè)置了lock標(biāo)記。該標(biāo)記的值不同,整個Mark Word表示的含義不同。biased_lock和lock一起,表達的鎖狀態(tài)含義如下:
biased_lock:對象是否啟用偏向鎖標(biāo)記,只占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。lock和biased_lock共同表示對象處于什么鎖狀態(tài)。
age:4位的Java對象年齡。在GC中,如果對象在Survivor區(qū)復(fù)制一次,年齡增加1。當(dāng)對象達到設(shè)定的閾值時,將會晉升到老年代。默認情況下,并行GC的年齡閾值為15,并發(fā)GC的年齡閾值為6。由于age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因。
identity_hashcode:31位的對象標(biāo)識hashCode,采用延遲加載技術(shù)。調(diào)用方法System.identityHashCode()計算,并會將結(jié)果寫到該對象頭中。當(dāng)對象加鎖后(偏向、輕量級、重量級),MarkWord的字節(jié)沒有足夠的空間保存hashCode,因此該值會移動到管程Monitor中。
thread:持有偏向鎖的線程ID。
epoch:偏向鎖的時間戳。
ptr_to_lock_record:輕量級鎖狀態(tài)下,指向棧中鎖記錄的指針。
ptr_to_heavyweight_monitor:重量級鎖狀態(tài)下,指向?qū)ο蟊O(jiān)視器Monitor的指針。
二、Klass Word(類指針)
這一部分用于存儲對象的類型指針,該指針指向它的類元數(shù)據(jù),JVM通過這個指針確定對象是哪個類的實例。該指針的位長度為JVM的一個字大小,即32位的JVM為32位,64位的JVM為64位。
如果應(yīng)用的對象過多,使用64位的指針將浪費大量內(nèi)存,統(tǒng)計而言,64位的JVM將會比32位的JVM多耗費50%的內(nèi)存。為了節(jié)約內(nèi)存可以使用選項+UseCompressedOops開啟指針壓縮,其中,oop即ordinary object pointer普通對象指針。
開啟該選項后,下列指針將壓縮至32位:
每個Class的屬性指針(即靜態(tài)變量)
每個對象的屬性指針(即對象變量)
普通對象數(shù)組的每個元素指針
當(dāng)然,也不是所有的指針都會壓縮,一些特殊類型的指針JVM不會優(yōu)化,比如指向PermGen的Class對象指針(JDK8中指向元空間的Class對象指針)、本地變量、堆棧元素、入?yún)?、返回值和NULL指針等。
三、數(shù)組長度
如果對象是一個數(shù)組,那么對象頭還需要有額外的空間用于存儲數(shù)組的長度,這部分數(shù)據(jù)的長度也隨著JVM架構(gòu)的不同而不同:32位的JVM上,長度為32位;64位JVM則為64位。
64位JVM如果開啟+UseCompressedOops選項,該區(qū)域長度也將由64位壓縮至32位。
看完上述內(nèi)容,你們掌握java對象結(jié)構(gòu) 對象頭 Markword的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。