Java的Object.hashCode()的返回值到底是不是对象内存地址?
2016-12-25 00:57
579 查看
刚学Java的时候我也有过这种怀疑,但一直没有验证;最近在OSCHINA上看到有人在回答问题时也这么说,于是萌生了一探究竟的想法——java.lang.Object.hashCode()的返回值到底是不是对象内存地址?
(顺带回顾一下JNI)
说到这个问题,大家的第一反应一定和我一样——去查Object.hashCode的源码,但翻开源码,看到的却是这样的(Oracle JDK 8):
view
sourceprint?
Object.hashCode是一个native方法,看不到源码(Java代码,Oracle的JDK是看不到的,OpenJDK或其他开源JRE是可以找到对应的C/C++代码)。
上面这段注释指出了Object.hashCode()在JRE(Java Runtime Library)中应该遵循的一些契约(contract):
(PS:所谓契约当然是大家一致达成的,各个JVM厂商都会遵循)
一致性(consistent),在程序的一次执行过程中,对同一个对象必须一致地返回同一个整数。
如果两个对象通过equals(Object)比较,结果相等,那么对这两个对象分别调用hashCode方法应该产生相同的整数结果。(PS:这里equals和hashCode说的都是Object类的)
如果两个对象通过java.lang.Object.equals(java.lang.Ojbect)比较,结果不相等,不必保证对这两个对象分别调用hashCode也返回两个不相同的整数。
实际上java.lang包里面的类,都是JRE必须的,属于运行时库(Runtime Library),这也是为什么很多JRE下该类的class文件被打包到rt.jar中的原因(应该是Runtime的简写)。
而这些运行时库一般都是跟JDK/JRE一起发布的;所以,对于不同的JRE环境,问题的答案未必相同。
考虑到具体JVM厂商实现的Object.hashCode相对较复杂,下面先通过另一思路对开头提出的问题进行探究。
最后我们再找一些开源JRE的Object.hashCode的具体实现作简要分析。
看不到Object.hashCode的源码,反过来,我们可以得到对象的内存地址和Object.hashCode比较,也能得出结论。
要验证这个问题自然需要一种得到对象内存地址的方法,但Java本身并没有提供类似的方法;这也是我在初学Java时没有验证这个问题的原因。
“内存地址”在Java里得不到,但在C/C++中却很容易得到。于是,我们想到——通过JNI让Java代码调用一段C/C++代码来得到对象内存地址。
这里可能需要考虑的还有一点——用Java什么类型能放得下C/C++的指针?
在64位机器上,C/C++的指针是8字节;32位是4字节。
嗯(⊙_⊙)~ 不管怎样Java的long都是8字节,足矣~
假设我们已经有了一个NativeUtils.java:
view
sourceprint?
并且已经实现了getNativePointer方法。
那么验证开头提出的问题就变得异常简单了:
view
sourceprint?
好了说干就干,现在就差那个native的getNativePointer了。
用javah生成对应的.h文件:
$ javah NativeUtils
该命令执行后生成了NativeUtils.h:
view
sourceprint?
接着实现这个Java_NativeUtils_getNativePointer,文件命名为NativeUtils.cc:
view
sourceprint?
编译为动态库:
$ g++-shared -o libnative-utils.so NativeUtils.cc
可能因为找不到jni.h,报错:
NativeUtils.h:2:17: fatal error: jni.h: No such file or directory
在JDK安装目录下查找jni.h:
user@host:/usr/lib/jvm/java-7-openjdk-amd64$ find . -name jni.h
./include/jni.h
知道jni.h路径后,用-I选项加到编译命令上再次编译:
$ g++-shared -I/usr/lib/jvm/java-7-openjdk-amd64/include/ -o libnative-utils.so NativeUtils.cc
OK, 编译成功,生成了 libnative-utils.so 文件。
下面就让TestNativeUtils在shell环境执行起来。
首先,编译NativeUtils和TestNativeUtils:
$ javac NativeUtils.java
$ javac TestNativeUtils.java
分别生成了NativeUtils.class和TestNativeUtils.class
好了,就差临门一脚了——执行class文件:
$ java TestNativeUtils
居然出错了:
view
sourceprint?
到目前为止,我们的Java代码并没有实现NativeUtils.getNativePointer;所以,会有上面的错误。
必须在调用NativeUtils.getNativePointer前,将我们编译好的动态库加载上。可以用static代码块,以保证在调用前完成加载;修改后的NativeUtils:
view
sourceprint?
再次编译、执行:
$ javac NativeUtils.java
$ java TestNativeUtils
又有错误,但已经和刚才不一样了:
view
sourceprint?
这次错误是:没有在java.library.path中找到native-utils,可以在javac命令-D参数上将当期目录加到java.library.path上,让其能够找到native-utils:
$ java -Djava.library.path=. TestNativeUtils
hash: 4f5f1ace,nptr: 7f223a5fb958
上面的多个命令可以写到一个Makefile里,可以实现“一键执行”:
view
sourceprint?
说道开源的Java运行环境,首先能想到的自然是OpenJDK和Android,下面分别简要分析。
Android的Object.hashCode和Oracle JDK的略有不同:
view
sourceprint?
(PS:估计shadow$_monitor_应该是hash值的一个cache,第一次需要计算一下,以后都不用计算了)
第一次执行时,shadow$_monitor_的值为0,将会调用System.identityHashCode:
view
sourceprint?
而它是一个native的方法,对应代码(java_lang_System.cc):
view
sourceprint?
很显然,这里的关键在于mirror::Object::IdentityHashCode:
view
sourceprint?
这段代码可以看出——ART的Object.hashCode确实是有cache的,对于同一个Ojbect,第一次调用Object.hashCode将会执行实际的计算并记入cache,以后直接从cache中取出。
真正计算hashcode的是GenerateIdentityHashCode:
view
sourceprint?
从GenerateIdentityHashCode可以看出,ART的Object.hashCode的返回值和对象的地址并没有直接的关系。
OpenJDK项目首页:openjdk.java.net
TODO: OpenJDK上的hashCode具体实现和简要分析
Object.c
view
sourceprint?
这段代码指出了Object.hashCode对应的C函数为JVM_IHashCode,下面需要找到JVM_IHashCode的代码jvm.cpp:
view
sourceprint?
这里只是一个包装,实际计算hashCode的是ObjectSynchronizer::FastHashCode,位于synchronizer.cpp:
view
sourceprint?
又是假牙,实际计算hashCode的是get_next_hash,代码和ObjectSynchronizer::FastHashCode相邻:
view
sourceprint?
这段代码可以看出OpenJDK一共实现了5中不同的计算hash值的方法,通过
这段代码中hashCode进行切换。其中hashCode == 4的是直接使用地址的(前面的实验说明OpenJDK默认情况下并没有使用这种方式,或许可以通过运行/编译时参数进行选择)。
我的运行环境:
OS:
Ubuntu 12.04 64bit Desktop |
JDK:
java version “1.7.0_55”
OpenJDK Runtime Environment (IcedTea 2.4.7) (7u55-2.4.7-1ubuntu1~0.12.04.2) \
OpenJDK 64-Bit Server VM (build 24.51-b03,mixed mode)
(顺带回顾一下JNI)
hashCode契约
说到这个问题,大家的第一反应一定和我一样——去查Object.hashCode的源码,但翻开源码,看到的却是这样的(Oracle JDK 8):view
sourceprint?
01.
<code
class
=
"language-java hljs "
>
/**
02.
* Returns a hash code value for the object. This method is
03.
* supported for the benefit of hash tables such as those provided by
04.
* {@link java.util.HashMap}.
05.
* <p>
06.
* The general contract of {@code hashCode}is:
07.
* <ul>
08.
* <li>Whenever it is invoked on the same object more than once during
09.
* an execution of a Java application,the {@code hashCode}method
10.
* must consistently return the same integer,provided no information
11.
* used in {@code equals}comparisons on the object is modified.
12.
* This integer need not remain consistent from one execution of an
13.
* application to another execution of the same application.
14.
* <li>If two objects are equal according to the {@code equals(Object)}
15.
* method,then calling the {@code hashCode}method on each of
16.
* the two objects must produce the same integer result.
17.
* <li>It is <em>not</em> required that if two objects are unequal
18.
* according to the {@link java.lang.Object#equals(java.lang.Object)}
19.
* method,then calling the {@code hashCode}method on each of the
20.
* two objects must produce distinct integer results. However,the
21.
* programmer should be aware that producing distinct integer results
22.
* for unequal objects may improve the performance of hash tables.
23.
* </ul>
24.
* <p>
25.
* As much as is reasonably practical,the hashCode method defined by
26.
* class {@code Object}does return distinct integers for distinct
27.
* objects. (This is typically implemented by converting the internal
28.
* address of the object into an integer,but this implementation
29.
* technique is not required by the
30.
* Java™ programming language.)
31.
*
32.
* @return a hash code value for this object.
33.
* @see java.lang.Object#equals(java.lang.Object)
34.
* @see java.lang.System#identityHashCode
35.
*/
36.
public
native
int
hashCode();</code>
Object.hashCode是一个native方法,看不到源码(Java代码,Oracle的JDK是看不到的,OpenJDK或其他开源JRE是可以找到对应的C/C++代码)。
上面这段注释指出了Object.hashCode()在JRE(Java Runtime Library)中应该遵循的一些契约(contract):
(PS:所谓契约当然是大家一致达成的,各个JVM厂商都会遵循)
一致性(consistent),在程序的一次执行过程中,对同一个对象必须一致地返回同一个整数。
如果两个对象通过equals(Object)比较,结果相等,那么对这两个对象分别调用hashCode方法应该产生相同的整数结果。(PS:这里equals和hashCode说的都是Object类的)
如果两个对象通过java.lang.Object.equals(java.lang.Ojbect)比较,结果不相等,不必保证对这两个对象分别调用hashCode也返回两个不相同的整数。
实际上java.lang包里面的类,都是JRE必须的,属于运行时库(Runtime Library),这也是为什么很多JRE下该类的class文件被打包到rt.jar中的原因(应该是Runtime的简写)。
而这些运行时库一般都是跟JDK/JRE一起发布的;所以,对于不同的JRE环境,问题的答案未必相同。
考虑到具体JVM厂商实现的Object.hashCode相对较复杂,下面先通过另一思路对开头提出的问题进行探究。
最后我们再找一些开源JRE的Object.hashCode的具体实现作简要分析。
Java中如何获得对象内存地址?
看不到Object.hashCode的源码,反过来,我们可以得到对象的内存地址和Object.hashCode比较,也能得出结论。要验证这个问题自然需要一种得到对象内存地址的方法,但Java本身并没有提供类似的方法;这也是我在初学Java时没有验证这个问题的原因。
“内存地址”在Java里得不到,但在C/C++中却很容易得到。于是,我们想到——通过JNI让Java代码调用一段C/C++代码来得到对象内存地址。
这里可能需要考虑的还有一点——用Java什么类型能放得下C/C++的指针?
在64位机器上,C/C++的指针是8字节;32位是4字节。
嗯(⊙_⊙)~ 不管怎样Java的long都是8字节,足矣~
Think in Java——接口和测试
假设我们已经有了一个NativeUtils.java:view
sourceprint?
1.
<code
class
=
"language-java hljs "
>
class
NativeUtils {
2.
public
native
static
long
getNativePointer(Object o);
3.
}</code>
并且已经实现了getNativePointer方法。
那么验证开头提出的问题就变得异常简单了:
view
sourceprint?
01.
<code
class
=
"language-java hljs "
>
class
TestNativeUtils {
02.
public
static
void
main(String args[]) {
03.
Object o =
new
Object();
04.
05.
long
nptr = NativeUtils.getNativePointer(o);
06.
long
hash = o.hashCode();
07.
08.
System.out.println(String.format(
"hash: %x,nptr: %x"
, hash,nptr));
09.
}
10.
}</code>
Think in C++——实现native方法
好了说干就干,现在就差那个native的getNativePointer了。用javah生成对应的.h文件:
$ javah NativeUtils
该命令执行后生成了NativeUtils.h:
view
sourceprint?
01.
<code
class
=
"language-c hljs "
>
/* DO NOT EDIT THIS FILE - it is machine generated */
02.
#include <jni.h>
03.
/* Header for class NativeUtils */
04.
05.
#ifndef _Included_NativeUtils
06.
#define _Included_NativeUtils
07.
#ifdef __cplusplus
08.
extern
"C"
{
09.
#endif
10.
/*
11.
* Class: NativeUtils
12.
* Method: getNativePointer
13.
* Signature: (Ljava/lang/Object;)J
14.
*/
15.
JNIEXPORT jlong JNICALL Java_NativeUtils_getNativePointer
16.
(JNIEnv *,jclass,jobject);
17.
18.
#ifdef __cplusplus
19.
}
20.
#endif
21.
#endif</code>
接着实现这个Java_NativeUtils_getNativePointer,文件命名为NativeUtils.cc:
view
sourceprint?
1.
<code
class
=
"language-c hljs "
>#include
"NativeUtils.h"
2.
3.
JNIEXPORT jlong JNICALL
4.
Java_NativeUtils_getNativePointer(JNIEnv *env,jclass clazz,jobject o)
5.
{
6.
return
reinterpret_cast<jlong>(o);
7.
}</code>
编译为动态库:
$ g++-shared -o libnative-utils.so NativeUtils.cc
可能因为找不到jni.h,报错:
NativeUtils.h:2:17: fatal error: jni.h: No such file or directory
在JDK安装目录下查找jni.h:
user@host:/usr/lib/jvm/java-7-openjdk-amd64$ find . -name jni.h
./include/jni.h
知道jni.h路径后,用-I选项加到编译命令上再次编译:
$ g++-shared -I/usr/lib/jvm/java-7-openjdk-amd64/include/ -o libnative-utils.so NativeUtils.cc
OK, 编译成功,生成了 libnative-utils.so 文件。
Run in shell——在shell环境运行
下面就让TestNativeUtils在shell环境执行起来。首先,编译NativeUtils和TestNativeUtils:
$ javac NativeUtils.java
$ javac TestNativeUtils.java
分别生成了NativeUtils.class和TestNativeUtils.class
好了,就差临门一脚了——执行class文件:
$ java TestNativeUtils
居然出错了:
view
sourceprint?
1.
<code
class
=
" hljs avrasm"
>Exception in thread
"main"
java.lang.UnsatisfiedLinkError: NativeUtils.getNativePointer(Ljava/lang/Object;)J
2.
at NativeUtils.getNativePointer(Native Method)
3.
at TestNativeUtils.main(TestNativeUtils.java:
5
)</code>
加载动态库到我们的程序中
到目前为止,我们的Java代码并没有实现NativeUtils.getNativePointer;所以,会有上面的错误。必须在调用NativeUtils.getNativePointer前,将我们编译好的动态库加载上。可以用static代码块,以保证在调用前完成加载;修改后的NativeUtils:
view
sourceprint?
1.
<code
class
=
"language-java hljs "
>
class
NativeUtils {
2.
static
{
3.
System.loadLibrary(
"native-utils"
);
4.
}
5.
6.
public
native
static
long
getNativePointer(Object o);
7.
}</code>
让JVM能找到动态库
再次编译、执行:$ javac NativeUtils.java
$ java TestNativeUtils
又有错误,但已经和刚才不一样了:
view
sourceprint?
1.
<code
class
=
" hljs avrasm"
>Exception in thread
"main"
java.lang.UnsatisfiedLinkError: no
native
-utils in java.library.path
2.
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:
1886
)
3.
at java.lang.Runtime.loadLibrary0(Runtime.java:
849
)
4.
at java.lang.System.loadLibrary(System.java:
1088
)
5.
at NativeUtils.<clinit>(NativeUtils.java:
3
)
6.
at TestNativeUtils.main(TestNativeUtils.java:
5
)</code>
这次错误是:没有在java.library.path中找到native-utils,可以在javac命令-D参数上将当期目录加到java.library.path上,让其能够找到native-utils:
$ java -Djava.library.path=. TestNativeUtils
hash: 4f5f1ace,nptr: 7f223a5fb958
All in one —— Makefile
上面的多个命令可以写到一个Makefile里,可以实现“一键执行”:view
sourceprint?
01.
<code
class
=
"language-Makefile hljs http"
>test: runtest
02.
03.
all: libnative-utils.so
04.
05.
JNI_INCLUDE=/usr/lib/jvm/java-
7
-openjdk-amd64/include
06.
07.
NativeUtils.
class
: NativeUtils.java
08.
javac NativeUtils.java
09.
10.
TestNativeUtils.
class
: TestNativeUtils.java
11.
javac TestNativeUtils.java
12.
13.
NativeUtils.h: NativeUtils.java
14.
javah -jni NativeUtils
15.
16.
libnative-utils.so: NativeUtils.cc NativeUtils.h
17.
g++ -shared -I${JNI_INCLUDE}-o libnative-utils.so NativeUtils.cc
18.
19.
runtest: TestNativeUtils.
class
libnative-utils.so
20.
@echo
"run test:"
21.
java -Djava.library.path=. TestNativeUtils
22.
23.
clean:
24.
rm -v *.
class
*.so *.h</code>
几个JRE的具体实现
说道开源的Java运行环境,首先能想到的自然是OpenJDK和Android,下面分别简要分析。
hashCode on Android
Android的Object.hashCode和Oracle JDK的略有不同:view
sourceprint?
01.
<code
class
=
"language-java hljs "
>
private
transient
int
shadow$_monitor_;
02.
03.
public
int
hashCode() {
04.
int
lockWord = shadow$_monitor_;
05.
final
int
lockWordMask =
0xC0000000
;
// Top 2 bits.
06.
final
int
lockWordStateHash =
0x80000000
;
// Top 2 bits are value 2 (kStateHash).
07.
if
((lockWord & lockWordMask) == lockWordStateHash) {
08.
return
lockWord & ~lockWordMask;
//
09.
}
10.
return
System.identityHashCode(
this
);
11.
}</code>
(PS:估计shadow$_monitor_应该是hash值的一个cache,第一次需要计算一下,以后都不用计算了)
第一次执行时,shadow$_monitor_的值为0,将会调用System.identityHashCode:
view
sourceprint?
1.
<code
class
=
"language-java hljs "
>
public
static
native
int
identityHashCode(Object anObject);</code>
而它是一个native的方法,对应代码(java_lang_System.cc):
view
sourceprint?
1.
<code
class
=
"language-c hljs "
>
static
jint System_identityHashCode(JNIEnv* env,jclass,jobject javaObject) {
2.
if
(UNLIKELY(javaObject == nullptr)) {
3.
return
0
;
4.
}
5.
ScopedFastNativeObjectAccess soa(env);
6.
mirror::Object* o = soa.Decode<mirror::Object*>(javaObject);
7.
return
static_cast<jint>(o->IdentityHashCode());
8.
}</code>
很显然,这里的关键在于mirror::Object::IdentityHashCode:
view
sourceprint?
01.
<code
class
=
" hljs cpp"
>int32_t Object::IdentityHashCode()
const
{
02.
mirror::Object* current_this = const_cast<mirror::Object*>(
this
);
03.
while
(
true
) {
04.
LockWord lw = current_this->GetLockWord(
false
);
05.
switch
(lw.GetState()) {
06.
case
LockWord::kUnlocked: {
// kUnlocked 是 LockWord的默认State值
07.
// Try to compare and swap in a new hash,if we succeed we will return the hash on the next
08.
// loop iteration.
09.
LockWord hash_<a href=
"http://www.it165.net/edu/ebg/"
target=
"_blank"
class
=
"keylink"
>word</a>(LockWord::FromHashCode(GenerateIdentityHashCode()));
10.
DCHECK_EQ(hash_<a href=
"http://www.it165.net/edu/ebg/"
target=
"_blank"
class
=
"keylink"
>word</a>.GetState(), LockWord::kHashCode);
11.
if
(const_cast<Object*>(
this
)->CasLockWordWeakRelaxed(lw, hash_word)) {
12.
return
hash_word.GetHashCode();
13.
}
14.
break
;
15.
}
16.
case
LockWord::kThinLocked: {
17.
// Inflate the thin lock to a monitor and stick the hash code inside of the monitor. May
18.
// fail spuriously.
19.
Thread* self = Thread::Current();
20.
StackHandleScope<
1
> hs(self);
21.
Handle<mirror::Object> h_this(hs.NewHandle(current_this));
22.
Monitor::InflateThinLocked(self, h_this,lw,GenerateIdentityHashCode());
23.
// A GC may have occurred when we switched to kBlocked.
24.
current_this = h_this.Get();
25.
break
;
26.
}
27.
case
LockWord::kFatLocked: {
28.
// Already inflated,return the has stored in the monitor.
29.
Monitor* monitor = lw.FatLockMonitor();
30.
DCHECK(monitor != nullptr);
31.
return
monitor->GetHashCode();
32.
}
33.
case
LockWord::kHashCode: {
// 以后调用
34.
return
lw.GetHashCode();
35.
}
36.
default
: {
37.
LOG(FATAL) <<
"Invalid state during hashcode "
<< lw.GetState();
38.
break
;
39.
}
40.
}
41.
}
42.
LOG(FATAL) <<
"Unreachable"
;
43.
return
0
;
44.
}</code>
这段代码可以看出——ART的Object.hashCode确实是有cache的,对于同一个Ojbect,第一次调用Object.hashCode将会执行实际的计算并记入cache,以后直接从cache中取出。
真正计算hashcode的是GenerateIdentityHashCode:
view
sourceprint?
01.
<code
class
=
" hljs cpp"
>int32_t Object::GenerateIdentityHashCode() {
02.
static
AtomicInteger seed(
987654321
+ std::time(nullptr));
03.
int32_t expected_value,new_value;
04.
do
{
05.
expected_value = static_cast<uint32_t>(seed.LoadRelaxed());
06.
new_value = expected_value *
1103515245
+
12345
;
07.
}
while
((expected_value & LockWord::kHashMask) ==
0
||
08.
!seed.CompareExchangeWeakRelaxed(expected_value, new_value));
09.
return
expected_value & LockWord::kHashMask;
10.
}</code>
从GenerateIdentityHashCode可以看出,ART的Object.hashCode的返回值和对象的地址并没有直接的关系。
hashCode on OpenJDK
OpenJDK项目首页:openjdk.java.netTODO: OpenJDK上的hashCode具体实现和简要分析
Object.c
view
sourceprint?
01.
<code
class
=
" hljs coffeescript"
>
static
JNINativeMethod methods[] = {
02.
{
"hashCode"
,
"()I"
, (
void
*)&JVM_IHashCode},
03.
{
"wait"
,
"(J)V"
, (
void
*)&JVM_MonitorWait},
04.
{
"notify"
,
"()V"
, (
void
*)&JVM_MonitorNotify},
05.
{
"notifyAll"
,
"()V"
, (
void
*)&JVM_MonitorNotifyAll},
06.
{
"clone"
,
"()Ljava/lang/Object;"
, (
void
*)&JVM_Clone},
07.
};
08.
09.
JNIEXPORT
void
JNICALL
10.
Java_java_lang_Object_registerNatives(JNIEnv *env,jclass cls)
11.
{
12.
(*env)->RegisterNatives(env, cls,
13.
methods, sizeof(methods)/sizeof(methods[
0
]));
14.
}
15.
16.
JNIEXPORT jclass JNICALL
17.
Java_java_lang_Object_getClass(JNIEnv *env,jobject
this
)
18.
{
19.
if
(
this
== NULL) {
20.
JNU_ThrowNullPointerException(env, NULL);
21.
return
0
;
22.
}
else
{
23.
return
(*env)->GetObjectClass(env,
this
);
24.
}
25.
}</code>
这段代码指出了Object.hashCode对应的C函数为JVM_IHashCode,下面需要找到JVM_IHashCode的代码jvm.cpp:
view
sourceprint?
1.
<code
class
=
" hljs lasso"
>JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env,jobject handle))
2.
JVMWrapper(
"JVM_IHashCode"
);
3.
// as implemented in the classic virtual machine; return 0 if object is NULL
4.
return
handle == NULL ?
0
: ObjectSynchronizer::FastHashCode (THREAD,JNIHandles::resolve_non_null(handle)) ;
5.
JVM_END</code>
这里只是一个包装,实际计算hashCode的是ObjectSynchronizer::FastHashCode,位于synchronizer.cpp:
view
sourceprint?
001.
<code
class
=
" hljs lasso"
>intptr_t ObjectSynchronizer::FastHashCode (Thread * Self,oop obj) {
002.
if
(UseBiasedLocking) {
003.
// NOTE: many places throughout the JVM do not expect a safepoint
004.
// to be taken here,in particular most operations on perm gen
005.
// objects. However,we only ever bias Java instances and all of
006.
// the call sites of identity_hash that might revoke biases have
007.
// been checked to make sure they can handle a safepoint. The
008.
// added check of the bias pattern is to avoid useless calls to
009.
// thread-local storage.
010.
if
(obj->mark()->has_bias_pattern()) {
011.
// Box and unbox the raw reference just in case we cause a STW safepoint.
012.
Handle hobj (Self,obj) ;
013.
// Relaxing assertion for bug 6320749.
014.
assert
(Universe::verify_in_progress() ||
015.
!SafepointSynchronize::is_at_safepoint(),
016.
"biases should not be seen by VM thread here"
);
017.
BiasedLocking::revoke_and_rebias(hobj,
false
, JavaThread::current());
018.
obj = hobj() ;
019.
assert
(!obj->mark()->has_bias_pattern(),
"biases should be revoked by now"
);
020.
}
021.
}
022.
023.
// hashCode() is a heap mutator ...
024.
// Relaxing assertion for bug 6320749.
025.
assert
(Universe::verify_in_progress() ||
026.
!SafepointSynchronize::is_at_safepoint(),
"invariant"
) ;
027.
assert
(Universe::verify_in_progress() ||
028.
Self->is_Java_thread() ,
"invariant"
) ;
029.
assert
(Universe::verify_in_progress() ||
030.
((JavaThread *)Self)->thread_state() != _thread_blocked,
"invariant"
) ;
031.
032.
ObjectMonitor* monitor = NULL;
033.
markOop temp,test;
034.
intptr_t hash;
035.
markOop mark = ReadStableMark (obj);
036.
037.
// object should remain ineligible for biased locking
038.
assert
(!mark->has_bias_pattern(),
"invariant"
) ;
039.
040.
if
(mark->is_neutral()) {
041.
hash = mark->hash();
// this is a normal header
042.
if
(hash) {
// if it has hash,just return it
043.
return
hash;
044.
}
045.
hash
= get_next_hash(Self,obj);
// allocate a new hash code
046.
temp = mark->copy_set_hash(hash);
// merge the hash code into header
047.
// use (machine word version) atomic operation to install the hash
048.
test = (markOop) Atomic::cmpxchg_ptr(temp,obj->mark_addr(),mark);
049.
if
(test == mark) {
050.
return
hash;
051.
}
052.
// If atomic operation failed,we must inflate the header
053.
// into heavy weight monitor. We could add more code here
054.
// for fast path,but it does not worth the complexity.
055.
}
else
if
(mark->has_monitor()) {
056.
monitor = mark->monitor();
057.
temp = monitor->header();
058.
assert
(temp->is_neutral(),
"invariant"
) ;
059.
hash = temp->hash();
060.
if
(hash) {
061.
return
hash;
062.
}
063.
// Skip to the following code to reduce code size
064.
}
else
if
(Self->is_lock_owned((address)mark->locker())) {
065.
temp = mark->displaced_mark_helper();
// this is a lightweight monitor owned
066.
assert
(temp->is_neutral(),
"invariant"
) ;
067.
hash = temp->hash();
// by current thread,check if the displaced
068.
if
(hash) {
// header contains hash code
069.
return
hash;
070.
}
071.
// WARNING:
072.
// The displaced header is strictly immutable.
073.
// It can NOT be changed in ANY cases. So we have
074.
// to inflate the header into heavyweight monitor
075.
// even the current thread owns the lock. The reason
076.
// is the BasicLock (stack slot) will be asynchronously
077.
// read by other threads during the inflate() function.
078.
// Any change to stack may not propagate to other threads
079.
// correctly.
080.
}
081.
082.
// Inflate the monitor to set hash code
083.
monitor = ObjectSynchronizer::inflate(Self,obj);
084.
// Load displaced header and check it has hash code
085.
mark = monitor->header();
086.
assert
(mark->is_neutral(),
"invariant"
) ;
087.
hash = mark->hash();
// 取出缓存
088.
if
(hash ==
0
) {
089.
hash = get_next_hash(Self,obj);
// 实际计算
090.
temp = mark->copy_set_hash(hash);
// merge hash code into header
091.
assert
(temp->is_neutral(),
"invariant"
) ;
092.
test = (markOop) Atomic::cmpxchg_ptr(temp,monitor,mark);
093.
if
(test != mark) {
094.
// The only update to the header in the monitor (outside GC)
095.
// is install the hash code. If someone add new usage of
096.
// displaced header,please update this code
097.
hash = test->hash();
098.
assert
(test->is_neutral(),
"invariant"
) ;
099.
assert
(hash !=
0
,
"Trivial unexpected object/monitor header usage."
);
100.
}
101.
}
102.
// We finally get the hash
103.
return
hash;
104.
}</code>
又是假牙,实际计算hashCode的是get_next_hash,代码和ObjectSynchronizer::FastHashCode相邻:
view
sourceprint?
01.
<code
class
=
" hljs lasso"
>
// hashCode() generation :
02.
//
03.
// Possibilities:
04.
// * MD5Digest of {obj,stwRandom}
05.
//
* CRC32 of {obj,stwRandom}or any linear-feedback shift register function.
06.
// * A DES- or AES-style SBox[] mechanism
07.
//
* One of the Phi-based schemes,such as:
08.
// 2654435761 = 2^32 * Phi (golden ratio)
09.
// HashCodeValue = ((uintptr_t(obj) >> 3) * 2654435761) ^ GVars.stwRandom ;
10.
// * A variation of Marsaglia's shift-xor RNG scheme.
11.
//
* (obj ^ stwRandom) is appealing,but can result
12.
// in undesirable regularity in the hashCode values of adjacent objects
13.
//
(objects allocated back-to-back,in particular). This could potentially
14.
// result in hashtable collisions and reduced hashtable efficiency.
15.
// There are simple ways to "diffuse" the middle address bits over the
16.
// generated hashCode values:
17.
//
18.
19.
static
inline
intptr_t get_next_hash(Thread * Self,oop obj) {
20.
intptr_t value =
0
;
21.
if
(hashCode ==
0
) {
22.
// This form uses an unguarded global Park-Miller RNG,
23.
// so it's possible for two threads to race and generate the same RNG.
24.
//
On MP system we'll have lots of RW access to a global,so the
25.
// mechanism induces lots of coherency traffic.
26.
value = os::random() ;
// 随机数
27.
}
else
28.
if
(hashCode ==
1
) {
29.
// This variation has the property of being stable (idempotent)
30.
// between STW operations. This can be useful in some of the 1-0
31.
// synchronization schemes.
32.
// 地址基础上hack
33.
intptr_t addrBits = intptr_t(obj) >>
3
;
34.
value = addrBits ^ (addrBits >>
5
) ^ GVars.stwRandom ;
35.
}
else
36.
if
(hashCode ==
2
) {
37.
value =
1
;
// for sensitivity testing, 实际不会使用
38.
}
else
39.
if
(hashCode ==
3
) {
40.
value =++GVars.hcSequence ;
41.
}
else
42.
if
(hashCode ==
4
) {
43.
value =intptr_t(obj) ;
// 直接用地址
44.
}
else
{
45.
// Marsaglia's xor-shift scheme with thread-specific state
46.
// This is probably the best overall implementation -- we'll
47.
// likely make this the default in future releases.
48.
unsigned t = Self->_hashStateX ;
49.
t ^= (t <<
11
) ;
50.
Self->_hashStateX = Self->_hashStateY ;
51.
Self->_hashStateY = Self->_hashStateZ ;
52.
Self->_hashStateZ = Self->_hashStateW ;
53.
unsigned v = Self->_hashStateW ;
54.
v = (v ^ (v >>
19
)) ^ (t ^ (t >>
8
)) ;
55.
Self->_hashStateW = v ;
56.
value =v ;
57.
}
58.
59.
value &= markOopDesc::hash_mask;
60.
if
(value ==
0
) value =
0xBAD
;
61.
assert
(value
!= markOopDesc::no_hash,
"invariant"
) ;
62.
TEVENT (hashCode: GENERATE) ;
63.
return
value;
64.
}</code>
这段代码可以看出OpenJDK一共实现了5中不同的计算hash值的方法,通过
这段代码中hashCode进行切换。其中hashCode == 4的是直接使用地址的(前面的实验说明OpenJDK默认情况下并没有使用这种方式,或许可以通过运行/编译时参数进行选择)。
结论
前面通过JNI验证已经能够得到很显然的结论,hashCode返回的并不一定是对象的(虚拟)内存地址,具体取决于运行时库和JVM的具体实现。我的运行环境:
OS:
Ubuntu 12.04 64bit Desktop |
JDK:
java version “1.7.0_55”
OpenJDK Runtime Environment (IcedTea 2.4.7) (7u55-2.4.7-1ubuntu1~0.12.04.2) \
OpenJDK 64-Bit Server VM (build 24.51-b03,mixed mode)
相关文章推荐
- Java的Object.hashCode()的返回值到底是不是对象内存地址?
- Java的Object.hashCode()的返回值到底是不是对象内存地址?
- Java 深入学习(7) —— Object.hashCode()的返回值与对象内存地址的关系
- Java语言中Object对象的hashCode()取值的底层算法是怎样实现的?,object hashcode
- Java语言中Object对象的hashCode()取值的底层算法是怎样实现的?
- JAVA Object对象(toString、equals、hashCode方法)、String类、StringBuffer、StringBuider、System、Runtime、Date、Mat
- 一个对象toString()方法如果没有被重写,那么默认调用它的父类Object的toString()方法,而Object的toString()方法是打印该对象的hashCode,一般hashCode就是此对象的内存地址
- java.lang.Object 对象中 hashCode 和 equals 方法详解及其延伸
- Java语言中Object对象的hashCode()取值的底层算法是怎样实现的?
- java Object对象中hashCode和equals方法学习
- 重写Java Object对象的hashCode和equals方法实现集合元素按内容判重
- 重写Java Object对象的hashCode和equals方法实现集合元素按内容判重
- Java中Object对象的hashCode方法和String对象的hashCode
- 【java基础】java中Object对象中的Hashcode方法的作用
- Java中的对象类型的引用到底是不是指针
- [Java基础要义] Java语言中Object对象的hashCode()取值的底层算法是怎样实现的?
- java语言中Object对象的hashCode()取值的底层算法是怎样实现的
- Java中的对象类型的引用到底是不是指针
- Java管理对象神奇之Object类的equals和hashcode
- [Java基础要义] Java语言中Object对象的hashCode()取值的底层算法是怎样实现的?