某團(tuán)面試題:hashCode 和對(duì)象的內(nèi)存地址有什么關(guān)系?
點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”

來(lái)源 | juejin.cn/post/6971946031764209678
先看一個(gè)最簡(jiǎn)單的打印
System.out.println(new Object());
會(huì)輸出該類(lèi)的全限定類(lèi)名和一串字符串:
java.lang.Object@6659c656
@符號(hào)后面的是什么?是 hashcode 還是對(duì)象的內(nèi)存地址?還是其他的什么值?
其實(shí)@后面的只是對(duì)象的 hashcode 值,16進(jìn)制展示的 hashcode 而已,來(lái)驗(yàn)證一下:
Object o = new Object();
int hashcode = o.hashCode();
// toString
System.out.println(o);
// hashcode 十六進(jìn)制
System.out.println(Integer.toHexString(hashcode));
// hashcode
System.out.println(hashcode);
// 這個(gè)方法,也是獲取對(duì)象的 hashcode;不過(guò)和 Object.hashcode 不同的是,該方法會(huì)無(wú)視重寫(xiě)的hashcode
System.out.println(System.identityHashCode(o));
輸出結(jié)果:
java.lang.Object@6659c656
6659c656
1717159510
1717159510
那對(duì)象的 hashcode 到底是怎么生成的呢?真的就是內(nèi)存地址嗎?
本文內(nèi)容基于 JAVA 8 HotSpot
hashCode 的生成邏輯
JVM 里生成 hashCode 的邏輯并沒(méi)有那么簡(jiǎn)單,它提供了好幾種策略,每種策略的生成結(jié)果都不同。
來(lái)看一下 openjdk 源碼里生成 hashCode 的核心方法:
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
從源碼里可以發(fā)現(xiàn),生成策略是由一個(gè) hashCode 的全局變量控制的,默認(rèn)為5;而這個(gè)變量的定義在另一個(gè)頭文件里:
product(intx, hashCode, 5,
"(Unstable) select hashCode generation algorithm" )
源碼里很清楚了……(非穩(wěn)定)選擇 hashCode 生成的算法,而且這里的定義,是可以由 jvm 啟動(dòng)參數(shù)來(lái)控制的,先來(lái)確認(rèn)下默認(rèn)值:
java -XX:+PrintFlagsFinal -version | grep hashCode
intx hashCode = 5 {product}
openjdk version "1.8.0_282"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_282-b08)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.282-b08, mixed mode)
所以我們可以通過(guò) jvm 的啟動(dòng)參數(shù)來(lái)配置不同的 hashcode 生成算法,測(cè)試不同算法下的生成結(jié)果:
-XX:hashCode=N
現(xiàn)在來(lái)看看,每種 hashcode 生成算法的不同表現(xiàn)。
第 0 種算法
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random();
}
這種生成算法,使用的一種Park-Miller RNG的隨機(jī)數(shù)生成策略。不過(guò)需要注意的是……這個(gè)隨機(jī)算法在高并發(fā)的時(shí)候會(huì)出現(xiàn)自旋等待
第 1 種算法
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
}
這個(gè)算法,真的是對(duì)象的內(nèi)存地址了,直接獲取對(duì)象的 intptr_t 類(lèi)型指針
第 2 種算法
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
}
這個(gè)就不用解釋了……固定返回 1,應(yīng)該是用于內(nèi)部的測(cè)試場(chǎng)景。
有興趣的同學(xué),可以試試-XX:hashCode=2來(lái)開(kāi)啟這個(gè)算法,看看 hashCode 結(jié)果是不是都變成 1 了。
第 3 種算法
if (hashCode == 3) {
value = ++GVars.hcSequence ;
}
這個(gè)算法也很簡(jiǎn)單,自增嘛,所有對(duì)象的 hashCode 都使用這一個(gè)自增變量。來(lái)試試效果:
System.out.println(new Object());
System.out.println(new Object());
System.out.println(new Object());
System.out.println(new Object());
System.out.println(new Object());
System.out.println(new Object());
//output
java.lang.Object@144
java.lang.Object@145
java.lang.Object@146
java.lang.Object@147
java.lang.Object@148
java.lang.Object@149
果然是自增的……有點(diǎn)意思
第 4 種算法
if (hashCode == 4) {
value = intptr_t(obj) ;
}
這里和第 1 種算法其實(shí)區(qū)別不大,都是返回對(duì)象地址,只是第 1 種算法是一個(gè)變體。
第 5 種算法
最后一種,也是默認(rèn)的生成算法,hashCode 配置不等于 0/1/2/3/4 時(shí)使用該算法:
else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
這里是通過(guò)當(dāng)前狀態(tài)值進(jìn)行異或(XOR)運(yùn)算得到的一個(gè) hash 值,相比前面的自增算法和隨機(jī)算法來(lái)說(shuō)效率更高,但重復(fù)率應(yīng)該也會(huì)相對(duì)增高,不過(guò) hashCode 重復(fù)又有什么關(guān)系呢……
本來(lái) jvm 就不保證這個(gè)值一定不重復(fù),像 HashMap 里的鏈地址法就是解決 hash 沖突用的.
后臺(tái)回復(fù) 學(xué)習(xí)資料 領(lǐng)取學(xué)習(xí)視頻
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
