來源:juejin.cn/post/6971946031764209678
先看一個最簡單的列印
System.out.println(new Object());
會輸出該類的全限定類名和一串字串:
java.lang.Object@6659c656
@符號后面的是什么?是 hashcode 還是物件的記憶體地址?還是其他的什么值?
其實@后面的只是物件的 hashcode 值,16進制展示的 hashcode 而已,來驗證一下:
Object o = new Object();
int hashcode = o.hashCode();
// toString
System.out.println(o);
// hashcode 十六進制
System.out.println(Integer.toHexString(hashcode));
// hashcode
System.out.println(hashcode);
// 這個方法,也是獲取物件的 hashcode;不過和 Object.hashcode 不同的是,該方法會無視重寫的hashcode
System.out.println(System.identityHashCode(o));
輸出結果:
java.lang.Object@6659c656
6659c656
1717159510
1717159510
那物件的 hashcode 到底是怎么生成的呢?真的就是記憶體地址嗎?
本文內容基于 JAVA 8 HotSpot
hashCode 的生成邏輯
JVM 里生成 hashCode 的邏輯并沒有那么簡單,它提供了好幾種策略,每種策略的生成結果都不同,
來看一下 openjdk 原始碼里生成 hashCode 的核心方法:
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = https://www.cnblogs.com/javastack/archive/2021/10/23/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 = https://www.cnblogs.com/javastack/archive/2021/10/23/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 = https://www.cnblogs.com/javastack/archive/2021/10/23/v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash,"invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
從原始碼里可以發現,生成策略是由一個 hashCode 的全域變數控制的,默認為5;而這個變數的定義在另一個頭檔案里:
product(intx, hashCode, 5,
"(Unstable) select hashCode generation algorithm" )
原始碼里很清楚了……(非穩定)選擇 hashCode 生成的演算法,而且這里的定義,是可以由 jvm 啟動引數來控制的,先來確認下默認值:
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)
所以我們可以通過 jvm 的啟動引數來配置不同的 hashcode 生成演算法,測驗不同演算法下的生成結果:
-XX:hashCode=N
現在來看看,每種 hashcode 生成演算法的不同表現,
第 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 = https://www.cnblogs.com/javastack/archive/2021/10/23/os::random();
}
這種生成演算法,使用的一種Park-Miller RNG的亂數生成策略,不過需要注意的是……這個隨機演算法在高并發的時候會出現自旋等待
第 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 = https://www.cnblogs.com/javastack/archive/2021/10/23/addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
}
這個演算法,真的是物件的記憶體地址了,直接獲取物件的 intptr_t 型別指標,
另外,Java 系列面試題和答案全部整理好了,微信搜索?Java技術堆疊,在后臺發送:面試,?可以在線閱讀,
第 2 種演算法
if (hashCode == 2) {
value = https://www.cnblogs.com/javastack/archive/2021/10/23/1 ; // for sensitivity testing
}
這個就不用解釋了……固定回傳 1,應該是用于內部的測驗場景,
有興趣的同學,可以試試-XX:hashCode=2來開啟這個演算法,看看 hashCode 結果是不是都變成 1 了,
第 3 種演算法
if (hashCode == 3) {
value = https://www.cnblogs.com/javastack/archive/2021/10/23/++GVars.hcSequence ;
}
這個演算法也很簡單,自增嘛,所有物件的 hashCode 都使用這一個自增變數,來試試效果:
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
果然是自增的……有點意思
第 4 種演算法
if (hashCode == 4) {
value = https://www.cnblogs.com/javastack/archive/2021/10/23/intptr_t(obj) ;
}
這里和第 1 種演算法其實區別不大,都是回傳物件地址,只是第 1 種演算法是一個變體,
第 5 種演算法
最后一種,也是默認的生成演算法,hashCode 配置不等于 0/1/2/3/4 時使用該演算法:
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 = https://www.cnblogs.com/javastack/archive/2021/10/23/v ;
}
這里是通過當前狀態值進行異或(XOR)運算得到的一個 hash 值,相比前面的自增演算法和隨機演算法來說效率更高,但重復率應該也會相對增高,不過 hashCode 重復又有什么關系呢……
本來 jvm 就不保證這個值一定不重復,像 HashMap 里的鏈地址法就是解決 hash 沖突用的.
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/333249.html
標籤:其他
