您好,登錄后才能下訂單哦!
這篇文章主要介紹Java對象在內(nèi)存中實現(xiàn)布局的方法,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
對象在內(nèi)存中的布局首要相關(guān)配置就是FieldsAllocationStyle,這個配置有3個可選值,即0、1、2。當(dāng)值為2的時候,會經(jīng)過一些邏輯判斷最終轉(zhuǎn)化為0或者1.
-XX:FieldsAllocationStyle=0 表示先分配對象,然后再按照double/long、ints、chars/shorts、bytes/booleans的順序分配其他字段,也就是類中聲明的相同寬度的字段總是會被分配在一起,而相同寬度字段的順序則是它們在class文件中聲明的順序。-
XX:FieldsAllocationStyle=1表示先按照double/long、ints、chars/shorts、bytes/booleans的順序分配屬性,然后再分配對象,分配過程中的其他原則上面為0時是保持一致的,同時這也是JVM默認(rèn)的分配策略。
當(dāng)然,上面這2種分配策略只是針對大部分正常情況而言,有以下幾種情況是會有所區(qū)別的(只是有部分區(qū)別,大致是沒有問題的)
如果是特定的類、例如基本類型的包裝類、String、Class、ClassLoader、軟引用等類,會先分配對象,然后再按照double/long、ints、chars/shorts、bytes/booleans的順序分配,同時-XX:+CompactFields和-XX:FieldsAllocationStyle=1都不會生效。
如果配置-XX:+CompactFields,會將ints、shorts/chars、bytes/booleans、oops的順序?qū)⒆侄翁畛涞綄ο箢^信息與字段起始偏移位置的間隙中去
如果當(dāng)前類或者類中使用了注解@sun.misc.Contended, 也會打亂上述布局
其他:
由于在計算對象字段的布局(字段基于對象起始位置的偏移量)時,當(dāng)前類上述各種類型變量的個數(shù)是已知的,所以每種類型的起始偏移量就可以通過計算得到,如下:
next_nonstatic_word_offset = next_nonstatic_double_offset + (nonstatic_double_count * BytesPerLong); next_nonstatic_short_offset = next_nonstatic_word_offset + (nonstatic_word_count * BytesPerInt); next_nonstatic_byte_offset = next_nonstatic_short_offset + (nonstatic_short_count * BytesPerShort); next_nonstatic_padded_offset = next_nonstatic_byte_offset + nonstatic_byte_count;
而對于oops對象的偏移量處理會比較特殊,如果-XX:FieldsAllocationStyle=0, 那么oops的偏移量起始位置就為對象頭之后,如果-XX:FieldsAllocationStyle=1, 則會進行下列處理,使得next_nonstatic_padded_offset與heapOopSize
是對齊的。如下:
// let oops jump before padding with this allocation style if( allocation_style == 1 ) { next_nonstatic_oop_offset = next_nonstatic_padded_offset; if( nonstatic_oop_count > 0 ) { next_nonstatic_oop_offset = align_size_up(next_nonstatic_oop_offset, heapOopSize); } next_nonstatic_padded_offset = next_nonstatic_oop_offset + (nonstatic_oop_count * heapOopSize); }
同時由于這個oops補齊操作以及計算完所有字段的偏移量之后,會再進行補齊操作,與heapOopSize
進行對齊,heapOopSize
在開啟和關(guān)閉壓縮指針的情況下,值分表為4和8。
-XX:CompactFields表示是否將對象中較窄的數(shù)據(jù)插入到間隙中,-XX:+CompactFields表示插入,-XX:-CompactFields則是不插入。默認(rèn)JVM是開啟插入的。
那么這兒就要討論一下為什么會插入,以及怎么插入?
首先需要了解Java對象的大致內(nèi)存布局,最開始的一塊區(qū)域存放對象標(biāo)記以及元數(shù)據(jù)指針,然后才是實例數(shù)據(jù),如下圖所示:
它們分別對應(yīng)普通對象與數(shù)組對象在內(nèi)存中的布局。由于對象字段布局是在Class文件解析的時候計算的,而數(shù)組類沒有對應(yīng)的Class文件,所以數(shù)組對象的布局這兒不做討論。
繼續(xù)回到剛剛的話題,將對象中較窄數(shù)據(jù)的插入間隙,可以細分為2種情況
當(dāng)前類沒有父類或者是父類中沒有實例數(shù)據(jù),此時會將實例數(shù)據(jù)前的對象標(biāo)記和對象元數(shù)據(jù)指針按照8字節(jié)對齊,如上圖所示,在開啟壓縮指針的情況下,對齊前占用12個字節(jié),對齊后到16字節(jié),此時存在4個字節(jié)的間隙,那么會將類中存在的字段按照 ints、chars/shorts、bytes/booleans、oops的順序進行填充,直到將間隙填充完畢,由于對齊之后的間隙要么是0,要么是4,所以填充間隙最多1個ints、2個chars/shorts、4個bytes/booleans、1個oops。
當(dāng)前類存在父類,并且父類中存在實例數(shù)據(jù),此時會將實例數(shù)據(jù)前的對象標(biāo)記和對象元數(shù)據(jù)指針 + 父類的實例數(shù)據(jù)大小按照8字節(jié)對齊,然后再進行填充,由于整個類在計算完所有字段偏移之后,會再與heapOopSize
進行對齊,所以父類的實例數(shù)據(jù)大小肯定是heapOopSize
的倍數(shù),也就是與第一種情況類似,不同的是,子類中的字段屬性需要在父類字段之后進行分配。
最終可以得到如下圖所示:
間隙插入受-XX:CompactFields影響外,還受到配置-XX:-UseCompressedOops的影響,回到上面的對齊,在開啟壓縮指針的情況下,元數(shù)據(jù)指針占8個字節(jié),這時候按照上面的細分情況1,也就不存在對齊了,而細分的情況二,由于父類在計算完字段偏移量之后會與heapOopSize
對齊,heapOopSize
在開啟壓縮指針的情況下為jintSize
, 關(guān)閉的情況下為oopSize
,分別對應(yīng)4和8, 也就是關(guān)閉壓縮指針的情況下,無論如何都不會發(fā)生間隙插入。
@sun.misc.Contended也會影響對象在內(nèi)存中的布局,這個注解是為了解決偽共享(False Sharing)的問題,關(guān)于偽共享的問題這兒就不講解了。
@sun.misc.Contended 可以用于修飾類、也可以用于修飾字段。
對于在類上的修飾來講,會在2個地方增加ContendedPaddingWidth,這個變量值為128。
一個地方是對象標(biāo)記和元數(shù)據(jù)指針 + 父類實例數(shù)據(jù)(當(dāng)前可能沒有父類實例數(shù)據(jù))之后 + ContendedPaddingWidth,然后再與8位進行對齊,另一個地方是,所有的非Contended實例字段偏移量計算完畢后,再加上ContendedPaddingWidth。
處理完類,接下來是字段,這兒的字段偏移量計算跟上面不一樣,并沒有按照double/long、ints、chars/shorts、bytes/booleans的順序來,而是按照@sun.misc.Contended對應(yīng)的group來進行計算,相同group的字段會放在一起,不同group的字段之間會以ContendedPaddingWidth來隔開,這兒比較特殊的情況是默認(rèn)分組,默認(rèn)分組為0,這個分組對應(yīng)的每個字段在計算完偏移量之后都會加上ContendedPaddingWidth。所以@sun.misc.Contended修飾的字段布局如下圖所示:
同時在計算每個字段偏移前,會使當(dāng)前的偏移量與當(dāng)前字段類型所對應(yīng)的字節(jié)數(shù)對齊,例如int,當(dāng)前偏移量會以4字節(jié)進行對齊,對齊之后的偏移量為當(dāng)前int字段的偏移量。
靜態(tài)字段的偏移量計算不受-XX:FieldsAllocationStyle和-XX:CompactFields的影響,會直接按照
oops、double/long、ints、chars/shorts、bytes/booleans的順序進行偏移量的計算。
同時給靜態(tài)字段 加上@sun.misc.Contended不會起到任何作用。
測試代碼:
final class NoChild { private Boolean value = Boolean.TRUE; private byte b; private int i; } @Test public void test() { declaredFields = NoChildContended.class.getDeclaredFields(); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers())) { long offset = unsafe.staticFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " static field " + field.getName() + " offset is " + offset); } else { long offset = unsafe.objectFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " field " + field.getName() + " offset is " + offset); } } }
運行結(jié)果:
-XX:FieldsAllocationStyle=0 -XX:-UseCompressedOops
//可以看到對象實例數(shù)據(jù)順序為value、int、byte,這兒由于沒有開啟指針壓縮,所以對象引用占了8個字節(jié)。
org.yamikaze.NoChild field value offset is 16
org.yamikaze.NoChild field b offset is 28
org.yamikaze.NoChild field i offset is 24
-XX:FieldsAllocationStyle=1 -XX:-UseCompressedOops
//可以看到先分配 int變量 i,其次byte變量 b,最后才是對象 value
//這兒的byte變量b偏移是20,占用大小1字節(jié),而經(jīng)過對齊之后,會產(chǎn)生3個字節(jié)的align
org.yamikaze.NoChild field value offset is 24
org.yamikaze.NoChild field b offset is 20
org.yamikaze.NoChild field i offset is 16
測試代碼:
class Parent { private long value; private int j; private byte b; } class Child2 extends Parent { private byte d; private long a; private int f; } @Test public void test() { Child2 c = new Child2(); Unsafe unsafe = UnSafeUtils.getUnsafe(); Field[] declaredFields = c.getClass().getSuperclass().getDeclaredFields(); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers())) { long offset = unsafe.staticFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " static field " + field.getName() + " offset is " + offset); } else { long offset = unsafe.objectFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " field " + field.getName() + " offset is " + offset); } } declaredFields = c.getClass().getDeclaredFields(); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers())) { long offset = unsafe.staticFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " static field " + field.getName() + " offset is " + offset); } else { long offset = unsafe.objectFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " field " + field.getName() + " offset is " + offset); } } }
測試結(jié)果:
-XX:+CompactFields -XX:+UseCompressedOops
//可以看到子類Child2的變量f并沒有按照double/long、ints、shorts/chars、bytes/booleans的順序計算偏移量,
//而是插入到了間隙里面
org.yamikaze.Parent field value offset is 16
org.yamikaze.Parent field j offset is 12
org.yamikaze.Parent field b offset is 24
org.yamikaze.Child2 field d offset is 40
org.yamikaze.Child2 field a offset is 32
org.yamikaze.Child2 field f offset is 28
-XX:-CompactFields -XX:+UseCompressedOops
//由于關(guān)閉了CompactFields,所以變量f的按照上面的順序進行偏移量計算
org.yamikaze.Parent field value offset is 16
org.yamikaze.Parent field j offset is 24
org.yamikaze.Parent field b offset is 28
org.yamikaze.Child2 field d offset is 44
org.yamikaze.Child2 field a offset is 32
org.yamikaze.Child2 field f offset is 40
測試代碼:
@Contended final class NoChildContended { private byte b; @Contended("aaa") private double value; @Contended("bbb") private int value1; } @Test public void test() { declaredFields = NoChildContended.class.getDeclaredFields(); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers())) { long offset = unsafe.staticFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " static field " + field.getName() + " offset is " + offset); } else { long offset = unsafe.objectFieldOffset(field); System.out.println(field.getDeclaringClass().getName() + " field " + field.getName() + " offset is " + offset); } } }
測試結(jié)果:
-XX:-RestrictContended
同分組時:
org.yamikaze.NoChildContended field b offset is 140
org.yamikaze.NoChildContended field value offset is 272
org.yamikaze.NoChildContended field value1 offset is 280
不同分組(默認(rèn)分組):
org.yamikaze.NoChildContended field b offset is 140
org.yamikaze.NoChildContended field value offset is 272
org.yamikaze.NoChildContended field value1 offset is 408
可以看到,由于Class上有@sun.misc.Contended注解修飾,導(dǎo)致byte變量的偏移量很大(12 + 128) 同樣byte變量之后的value,偏移量再次增加了128,達到272(141 + 128 = 269然后與4字節(jié)對齊得到272),然后相同分組的value1緊跟著value,而在不同分組的情況下,value1和value之間又隔了128。
//實例字段 unsafe.objectFieldOffset(field); //靜態(tài)字段 unsafe.staticFieldOffset(field);
回到上文的偏移量計算,在經(jīng)過計算后,每個字段相對于對象頭的偏移量都是已知的,這個偏移量會保存到字段信息里面去,那么獲取字段偏移量也很簡單,直接拿到字段相關(guān)信息取得offset即可,而通過CAS操作改變字段的值也很簡單,當(dāng)前對象指針加上字段偏移量就是當(dāng)前字段在內(nèi)存中的地址,直接通過指針更字段值即可。
以上是“Java對象在內(nèi)存中實現(xiàn)布局的方法”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(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)容。