Java์์ Object ํด๋์ค์ hashCode() ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด, ์ผ๋ฐ์ ์ผ๋ก๋ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์
๋ ๊ทธ ์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ฐํ ์์น
๋ฅผ ํด์ ์ฝ๋๋ก ๋ฐํํ๊ฒ ๋๋ค.
(hashCode() ๋ฉ์๋์ ๋ํด์๋ ํด๋น ๊ธ์ ์ ๋ฆฌํด๋์๋ค.)
์ฌ๊ธฐ์ ์ค์ํ ์ ์ hashCode() == ๋ฉ๋ชจ๋ฆฌ ์ฃผ์
๊ฐ ์๋๋ผ, ํด์ ์ฝ๋๋ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ or ์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ฐํ ์์น
๋ผ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ ์ด์ ๋ํด์ ํผ๋ํ๋ ์ฌ๋๋ค์ด ๋ง๊ณ , ์ค์ ๋ก ๊ด๋ จํ ๋ง์ ์ ๋ณด๋ค์ด ์ธํฐ๋ท ์์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฒ ๊ธ์ ํตํด์ ์ฌ์ค ์ ๋ฌด๋ฅผ ํ์ธํด ๋ณด๋ ค๊ณ ํ๋ค.
์ฌ์ฉ Java ๋ฒ์ :
OpenJDK17
@IntrinsicCandidate
public native int hashCode();
Object ํด๋์ค์ ํด์ ์ฝ๋ ๋ฉ์๋์ด๋ค.
์ด๋ ์ผ๋ฐ์ ์ธ ๋ฉ์๋์ ๋ค๋ฅด๊ฒ, native
๋ฉ์๋์ด๊ธฐ ๋๋ฌธ์ JVM ๋ด๋ถ(๋ค์ดํฐ๋ธ ์ฝ๋)
์ ๊ตฌํ๋์ด ์๋ค.
JNI(Java Native Interface)
๋ฅผ ํตํด JVM ๋ ๋ฒจ, ์์คํ
๋ ๋ฒจ์์ ์๋ํ๋ ๊ตฌ์กฐ์ด๋ค.
๊ทธ๋ ๊ธฐ์ ์ด๋ JVM์ ์์กดํ๋ ๋์์ด๋ฉฐ, ๋ด๋ถ ๊ตฌํ ๋ฐฉ์์ด JVM์ ๋ฒ์ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์๋ค๋ ์๋ฏธ๊ฐ ๋๋ค.
์์ ์ฝ๋๋ฅผ ํตํด์ hashCode()์ ์ค์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ๋น๊ตํด ๋ณด์.
import org.openjdk.jol.vm.VM;
public class Test {
public static void main(String[] args) {
Object obj = new Object();
// hashCode ์ถ๋ ฅ
int hashCode = obj.hashCode();
System.out.println("obj.hashCode(): " + hashCode); // 2060468723
// identityHashCode ์ถ๋ ฅ
int identityHashCode = System.identityHashCode(obj);
System.out.println("System.identityHashCode(obj): " + identityHashCode); // 2060468723
// ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ์ถ๋ ฅ (JOL ์ฌ์ฉ)
long address = VM.current().addressOf(obj);
System.out.println("Memory address: " + address); // 30331364672
// toString ํ์ธ
System.out.println("obj.toString(): " + obj.toString()); // java.lang.Object@7ad041f3
// hashCode 16์ง์ ๋ณํ
System.out.println("hashCode in HEX: " + Integer.toHexString(hashCode)); // 7ad041f3
}
}
hashCode
์identityHashCode
๋ ๋์ผํ๋ค.
hashCode
์๋ฉ๋ชจ๋ฆฌ ์ฃผ์
๋ ๋์ผํ์ง ์๋ค.
2060468723
์ 30331364672
toString()
์์@
๋ค์ ๋์ค๋ ๊ฐ๊ณผ,hashCode๋ฅผ 16์ง์๋ก ๋ณํํ ๊ฐ
์ ๋์ผํ๋ค.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
์ฌ๊ธฐ๊น์ง ์๊ฒ ๋ ์ฌ์ค์ ๋ค์๊ณผ ๊ฐ๋ค.
hashCode()
์toString() @ ์ดํ์ ๊ฐ
์ ์ฌ์ค์ ๊ฐ์ ๊ฐ์ด๋ค.hashCode()
์๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์
๋ ๋์ผํ์ง ์๋ค.
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@link
* equals(Object) equals} method, then calling the {@code
* hashCode} method on each of the two objects must produce the
* same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link equals(Object) equals} method, then
* calling the {@code hashCode} method on each of the two objects
* must produce distinct integer results. However, the programmer
* should be aware that producing distinct integer results for
* unequal objects may improve the performance of hash tables.
* </ul>
*
* @implSpec
* As far as is reasonably practical, the {@code hashCode} method defined
* by class {@code Object} returns distinct integers for distinct objects.
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
@IntrinsicCandidate
public native int hashCode();
Object ํด๋์ค์ hashCode() ๋ฉ์๋์ ์๋ ์ฃผ์์ ์ดํด๋ณด๋ฉด ๋ง์ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค.
- ๊ฐ์ ๊ฐ์ฒด์ ๋ํด ์ฌ๋ฌ ๋ฒ ํธ์ถํ๋ฉด ๊ฐ์ ๊ฐ์ ๋ฐํํด์ผ ํ๋ค. (๋จ, equals์ ์ฌ์ฉ๋๋ ๊ฐ์ด ๋ณํ์ง ์์ ๋)
- equals๊ฐ true์ธ ๋ ๊ฐ์ฒด๋ hashCode๋ ๊ฐ์์ผ ํ๋ค.
- equals๊ฐ false์ธ ๋ ๊ฐ์ฒด๋ hashCode๊ฐ ๋ค๋ฅผ ํ์๋ ์์ง๋ง, ๋ค๋ฅด๋ฉด ์ฑ๋ฅ์ด ์ข๋ค.
- Object.hashCode๋ ๊ฐ๋ฅํ ํ ์๋ก ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ํด ์๋ก ๋ค๋ฅธ ๊ฐ์ ๋ฐํํ๋๋ก ํ๋ค.
์ฌ๊ธฐ์๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ๊ด๋ จํ ๋ด์ฉ์ ์์ง๋ง, ์ด๋ณด๋ค ํ์ JDK ๋ฒ์ ์์๋ ๊ธฐ์ฌ๋ ๊ฒฝ์ฐ๋ ์๋ ๋ฏํ๋ค.
๊ทธ๋ ๋ค๋ฉด ์ค์ ๋ก ์ด๋ ํ ๋ก์ง์ ํตํด์ JVM์ ํด์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์๋์ง ์์๋ณด์. JVM ๋ด๋ถ๋ ๋๋ถ๋ถ C++๋ก ๊ตฌํ๋์ด ์๋ค.
์๋ก ์์ ๋งํ๋ฏ์ด OpenJDK 17
๋ฒ์ ์ ์ฌ์ฉํ์๋ค.
JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
// as implemented in the classic virtual machine; return 0 if object is NULL
return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END
JVM_ENTRY
๋งคํฌ๋ก๋Object.hashCode()
ํธ์ถ์ JVM ๋ด๋ถ์JVM_IHashCode
๋ค์ดํฐ๋ธ ํจ์์ ์ฐ๊ฒฐํด ์ค๋ค.
handle == NULL
์ด๋ฉด 0 ๋ฐํ, ์๋๋ฉดFastHashCode
๋ฅผ ํตํด ํด์์ฝ๋๋ฅผ ๊ณ์ฐํ๋ค.
JNIHandles::resolve_non_null(handle)
๋ JNI ํธ๋ค์ ์ค์ ๊ฐ์ฒด ์ฐธ์กฐ๋ก ๋ณํํ๋ค.
intptr_t ObjectSynchronizer::FastHashCode(Thread* current, oop obj) {
if (UseBiasedLocking) {
// NOTE: many places throughout the JVM do not expect a safepoint
// to be taken here. However, we only ever bias Java instances and all
// of the call sites of identity_hash that might revoke biases have
// been checked to make sure they can handle a safepoint. The
// added check of the bias pattern is to avoid useless calls to
// thread-local storage.
if (obj->mark().has_bias_pattern()) {
// Handle for oop obj in case of STW safepoint
Handle hobj(current, obj);
if (SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::revoke_at_safepoint(hobj);
} else {
BiasedLocking::revoke(current->as_Java_thread(), hobj);
}
obj = hobj();
assert(!obj->mark().has_bias_pattern(), "biases should be revoked by now");
}
}
while (true) {
ObjectMonitor* monitor = NULL;
markWord temp, test;
intptr_t hash;
markWord mark = read_stable_mark(obj);
// object should remain ineligible for biased locking
assert(!mark.has_bias_pattern(), "invariant");
if (mark.is_neutral()) { // if this is a normal header
hash = mark.hash();
if (hash != 0) { // if it has a hash, just return it
return hash;
}
hash = get_next_hash(current, obj); // get a new hash
temp = mark.copy_set_hash(hash); // merge the hash into header
// try to install the hash
test = obj->cas_set_mark(temp, mark);
if (test == mark) { // if the hash was installed, return it
return hash;
}
// Failed to install the hash. It could be that another thread
// installed the hash just before our attempt or inflation has
// occurred or... so we fall thru to inflate the monitor for
// stability and then install the hash.
} else if (mark.has_monitor()) {
monitor = mark.monitor();
temp = monitor->header();
assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value());
hash = temp.hash();
if (hash != 0) {
// It has a hash.
// Separate load of dmw/header above from the loads in
// is_being_async_deflated().
// dmw/header and _contentions may get written by different threads.
// Make sure to observe them in the same order when having several observers.
OrderAccess::loadload_for_IRIW();
if (monitor->is_being_async_deflated()) {
// But we can't safely use the hash if we detect that async
// deflation has occurred. So we attempt to restore the
// header/dmw to the object's header so that we only retry
// once if the deflater thread happens to be slow.
monitor->install_displaced_markword_in_object(obj);
continue;
}
return hash;
}
// Fall thru so we only have one place that installs the hash in
// the ObjectMonitor.
} else if (current->is_Java_thread()
&& current->as_Java_thread()->is_lock_owned((address)mark.locker())) {
// This is a stack lock owned by the calling thread so fetch the
// displaced markWord from the BasicLock on the stack.
temp = mark.displaced_mark_helper();
assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value());
hash = temp.hash();
if (hash != 0) { // if it has a hash, just return it
return hash;
}
// WARNING:
// The displaced header in the BasicLock on a thread's stack
// is strictly immutable. It CANNOT be changed in ANY cases.
// So we have to inflate the stack lock into an ObjectMonitor
// even if the current thread owns the lock. The BasicLock on
// a thread's stack can be asynchronously read by other threads
// during an inflate() call so any change to that stack memory
// may not propagate to other threads correctly.
}
// Inflate the monitor to set the hash.
// An async deflation can race after the inflate() call and before we
// can update the ObjectMonitor's header with the hash value below.
monitor = inflate(current, obj, inflate_cause_hash_code);
// Load ObjectMonitor's header/dmw field and see if it has a hash.
mark = monitor->header();
assert(mark.is_neutral(), "invariant: header=" INTPTR_FORMAT, mark.value());
hash = mark.hash();
if (hash == 0) { // if it does not have a hash
hash = get_next_hash(current, obj); // get a new hash
temp = mark.copy_set_hash(hash) ; // merge the hash into header
assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value());
uintptr_t v = Atomic::cmpxchg((volatile uintptr_t*)monitor->header_addr(), mark.value(), temp.value());
test = markWord(v);
if (test != mark) {
// The attempt to update the ObjectMonitor's header/dmw field
// did not work. This can happen if another thread managed to
// merge in the hash just before our cmpxchg().
// If we add any new usages of the header/dmw field, this code
// will need to be updated.
hash = test.hash();
assert(test.is_neutral(), "invariant: header=" INTPTR_FORMAT, test.value());
assert(hash != 0, "should only have lost the race to a thread that set a non-zero hash");
}
if (monitor->is_being_async_deflated()) {
// If we detect that async deflation has occurred, then we
// attempt to restore the header/dmw to the object's header
// so that we only retry once if the deflater thread happens
// to be slow.
monitor->install_displaced_markword_in_object(obj);
continue;
}
}
// We finally get the hash.
return hash;
}
}
FastHashCode()
๋ Object.hashCode()
์ ๋ค์ดํฐ๋ธ ๋ ๋ฒจ ๊ตฌํ์ผ๋ก, ๊ฐ์ฒด์ hashCode ๊ฐ์ ๋ค์ ์ค ํ๋๋ก ๋ฐํํ๋ค.
- ์ด๋ฏธ ์์ฑ๋์ด
๊ฐ์ฒด header/monitor
์ ๊ธฐ๋ก๋ hash- ์๋ก ์์ฑํ hash (์ถฉ๋ ๋ฐฉ์ง, ๋๊ธฐํ ๋ณด์ฅ)
if (UseBiasedLocking) {
if (obj->mark().has_bias_pattern()) {
// Bias lock ํด์
Handle hobj(current, obj);
if (SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::revoke_at_safepoint(hobj);
} else {
BiasedLocking::revoke(current->as_Java_thread(), hobj);
}
obj = hobj();
assert(!obj->mark().has_bias_pattern(), "biases should be revoked by now");
}
}
๐ก ๊ฐ์ฒด๊ฐ bias locking
์ํ๋ฉด bias๋ฅผ ํด์ โ hashCode๋ bias์ ์๋ฆฝ ๋ถ๊ฐ.
biased locking (ํธํฅ ๋ฝ)?
- ๋ฝ์ ์ป์ ๋ ๋น์ฉ์ ์ค์ด๊ธฐ ์ํด ์ค๊ณ๋ JVM ์ต์ ํ ๊ธฐ๋ฒ
- ๋๊ธฐํ๋ ๋ธ๋ก์ ํญ์ ๊ฐ์ ์ค๋ ๋๊ฐ ์ฌ์ฉํ ๋ ๋ฝ ์์ฒด๋ฅผ ์๋ต(ํธํฅ)ํ๋ ๋ฐฉ์์ด๋ค.
- ์ฆ, ๊ฐ์ฒด์
mark word
์ ๋ฝ์ ๊ฑธ์ด๋ ์ค๋ ๋ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ , ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ทธ ๊ฐ์ฒด์ ๋ฝ์ ๊ฑธ๊ธฐ ์ ๊น์ง ๋ฝ ๊ฒฝ์ ์์ด ๊ทธ๋๋ก ์ฌ์ฉํ๋ค.- ๋จ์ผ ์ค๋ ๋๊ฐ lock/unlock์ ๋ฐ๋ณตํ๋ ๊ฒฝ์ฐ, ๋ฝ ์ค๋ฒํค๋๊ฐ ๊ฑฐ์ 0์ ๊ฐ๊น๊ธฐ ๋๋ฌธ์, JVM์ ํธํฅ ๋ฝ์ default on์ผ๋ก ๋๊ณ ํ์ ์ revokeํ๋ค.
revoke?
revoke
๋biased lock
์ ํด์ ํ๋ ์์ ์ด๋ค.- ๊ฐ์ฒด๊ฐ biased ์ํ์ผ ๋, ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ ๊ทผํ๊ฑฐ๋ / hashCode๊ฐ ํ์ํ๋ฉด bias๋ฅผ ํด์ ํ๊ณ ๋ฝ์ ์ผ๋ฐ ๋ฝ์ผ๋ก ์ ํํ๋ค.
hashCode์ bias locking์ ์ ์๋ฆฝ ๋ถ๊ฐ์ธ๊ฐ?
- JVM์ ๊ฐ์ฒด์
mark word
๋ผ๋ ๊ณณ์ ๋ฝ ์ ๋ณด, hashCode, GC ์ ๋ณด ๋ฑ์ ์ ์ฅํ๋ค.
- hashCode()๋ฅผ ํธ์ถํ๋ฉด ๊ฐ์ mark word์ ํด์ ๊ฐ์ ๊ธฐ๋กํด์ผ ํ๋๋ฐ, ์ด๋ฏธ ์ค๋ ๋ ID ๋ฑ์ผ๋ก ๊ณต๊ฐ์ด ์ฐจ์ง๋์ด ์์ด ์ธ ์ ์๋ค.
- ๋ฐ๋ผ์ ๋์ ๋์์ ์ ์งํ ์ ์๊ณ , hashCode๋ฅผ ๊ธฐ๋กํ๊ธฐ ์ํด biased locking์ ํด์ (bias revoke) ํ๊ฒ ๋๋ค.
๐ก safepoint
์ฌ๋ถ์ ๋ฐ๋ผ revoke ๋ฐฉ์ ๊ฒฐ์ .
safepoint?
- JVM์์ ๋ชจ๋ ์ค๋ ๋๋ฅผ ์ ์ ๋ฉ์ถ๊ณ ํน์ ์์ (GC, biased locking revoke ๋ฑ)์ ์์ ํ๊ฒ ์ํํ๊ธฐ ์ํ ์์ ์ด๋ค.
- safepoint์์๋ JVM ๋ด๋ถ ๊ตฌ์กฐ ๋ณ๊ฒฝ, ํ ๊ฒ์ฌ, GC ์์ ๋ฑ์ ์ํํ ์ ์๋ค.
while (true) {
...
}
๐ก hash ๊ฐ์ด ์์ฑ/์ค์ ๋ ๋๊น์ง ์ฌ๋ฌ ์๋๋ฅผ ๋ฐ๋ณตํ๋ค.
markWord mark = read_stable_mark(obj);
๐ก ๊ฐ์ฒด header ์ค ์ผ๋ถ์ธ mark word
๋ฅผ ์ฝ๋ ๋ถ๋ถ์ด๋ค.
๐ก (a) Neutral ์ํ (lock ๊ฑธ๋ฆฌ์ง ์์)
Neutral ์ํ?
Java ๊ฐ์ฒด์ mark word ๋ด๋ถ
biased ์ํ
์ค ํ๋์ด๋ค.Neutral
์ ๊ฐ์ฒด๊ฐ ๋ฝ์ด ๊ฑธ๋ ค ์์ง ์์ ์ํ๋ฅผ ์๋ฏธํ๋ค.
if (mark.is_neutral()) {
hash = mark.hash();
if (hash != 0) return hash;
hash = get_next_hash(current, obj);
temp = mark.copy_set_hash(hash);
test = obj->cas_set_mark(temp, mark);
if (test == mark) return hash;
}
CAS
๋ก markWord์ ๊ธฐ๋ก โ ์ฑ๊ณตํ๋ฉด ๋ฐํํ๋ค.CAS?
CAS(Compare-And-Swap)
๋ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์์ ์์์ (atomic)์ผ๋ก ๊ฐ ๊ฐฑ์ ์ ๋ณด์ฅํ๋ ์ฐ์ฐ์ด๋ค.Java๋ JVM ๋ด๋ถ์์ ๋ฝ ์์ด ์์ ํ๊ฒ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ฉฐ, ํนํ mark word๋ ๊ฐ์ฒด์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ๋ ๋ง์ด ์ฌ์ฉ๋๋ค.
๐ก (b) ์ด๋ฏธ inflated (monitor ์ฌ์ฉ ์ค)
Inflated?
- inflated ์ํ๋ ๊ฐ์ฒด๊ฐ
monitor (ObjectMonitor)
๋ฅผ ์ฌ์ฉ ์ค์ด๋ผ๋ ๋ป- ์ฆ, ๊ฐ์ฒด์ lock ์ํ๊ฐ ๊ฒฝ๋ ๋ฝ(lightweight lock) ๋๋ bias ๋ฅผ ๋์ด ๋ฌด๊ฑฐ์ด(monitor) ๋ฝ์ผ๋ก ์น๊ฒฉ๋ ์ํ์ด๋ค.
- ์๋์ ๊ฒฝ์ฐ์ Inflated ์ํ๊ฐ ๋ ์ ์๋ค.
- lock ๊ฒฝ์์ด ์ฌํด์ ๋ ์ด์ ๊ฒฝ๋ ๋ฝ์ผ๋ก๋ ๋๊ธฐํ ์ ์ง๊ฐ ์ด๋ ค์
- ํน์ JVM ๋ด๋ถ ๋์ (์: hashCode ๊ธฐ๋ก) ๋๋ฌธ์ monitor๊ฐ ํ์ํจ
์ด ๊ฒฝ์ฐ
mark word
์๋ monitor์ ์ฃผ์๊ฐ ์ ์ฅ๋๋ค.
Monitor?
monitor
๋ JVM ๋ด๋ถ์์ ๋ฝ๊ณผ ๊ด๋ จ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ด๋ ๋๊ธฐํ ๊ฐ์ฒด์ด๋ค.C++์ ObjectMonitor ๊ตฌ์กฐ์ฒด
๋ก ๊ตฌํ๋์ด ์๋ค.- ์๋์ ์ ๋ณด๋ค์ด ๋ค์ด๊ฐ ์ ์๋ค.
- ๋ฝ ์์ ์ ์ ๋ณด
- ๋๊ธฐ ํ (๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ด ๊ฐ์ฒด์ ๋ฝ์ ๊ธฐ๋ค๋ฆด ๋)
- displaced mark word (์๋ mark word ๋ณต์ฌ๋ณธ)
- hashCode (ํ์์ ๊ธฐ๋ก)
- ๊ธฐํ ์ํ ์ ๋ณด
monitor๋ ํ์ ํ ๋น๋๋ฉฐ, mark word๋ ์ด monitor์ ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํจ๋ค.
else if (mark.has_monitor()) {
monitor = mark.monitor();
temp = monitor->header();
hash = temp.hash();
if (hash != 0) {
if (monitor->is_being_async_deflated()) {
monitor->install_displaced_markword_in_object(obj);
continue;
}
return hash;
}
}
monitor
์์ hash ์ฝ์ โ ์์ผ๋ฉด ๋ฐํํ๋ค.deflation race
๋ฐ๊ฒฌ ์ ๋ค์ ๋ฃจํ๋ฅผ ๋ฐ๋ณตํ๋ค.Deflation race?
deflation
์ ์ฌ์ฉํ์ง ์๋ monitor๋ฅผ ์ ๋ฆฌํด mark word๋ฅผ ์๋ ์ํ๋ก ๋๋๋ฆฌ๋ ๊ณผ์ ์ด๋ค.deflation race
๋ monitor๋ฅผ deflate (ํด์ )ํ๋ ๋์ค, ๋ค๋ฅธ ์ค๋ ๋๊ฐ monitor ์ ๋ณด๋ฅผ ์ฝ๊ฑฐ๋ ์ฐ๋ ์์ ๊ณผ ์ถฉ๋(race condition)ํ๋ ์ํฉ์ด๋ค.๋๋ฌธ์ deflation ์ค์ด๋ฉด mark word๋ฅผ ๋ณต์ํ๊ณ ๋ฃจํ๋ฅผ ๋๋ฉฐ ๋ค์ ์๋ํ๊ฒ ๋๋ค.
๐ก (c) stack lock ์ํ (ํ์ฌ ์ค๋ ๋๊ฐ ์์ )
else if (current->is_Java_thread() && current->as_Java_thread()->is_lock_owned((address)mark.locker())) {
temp = mark.displaced_mark_helper();
hash = temp.hash();
if (hash != 0) return hash;
}
displaced markWord
์์ hash๋ฅผ ํ์ธํ๋ค.inflate
ํ์ โ race ๋ฐฉ์ง.monitor = inflate(current, obj, inflate_cause_hash_code);
mark = monitor->header();
hash = mark.hash();
if (hash == 0) {
hash = get_next_hash(current, obj);
temp = mark.copy_set_hash(hash);
uintptr_t v = Atomic::cmpxchg((volatile uintptr_t*)monitor->header_addr(), mark.value(), temp.value());
...
return hash;
}
monitor inflate
โ ์์ ์ hash ๊ธฐ๋ก ์ค๋นCAS
๋ก ๊ธฐ๋กํ๋ค.Monitor inflate?
monitor inflate
๋ JVM์ด mark word์ ๊ณต๊ฐ ๋์ObjectMonitor ๋ผ๋ ๋ณ๋ ๊ตฌ์กฐ์ฒด(ํ์ ์์น)
๋ฅผ ์์ฑํ๊ณ ์ฐ๊ฒฐํ๋ ์์ ์ ๋งํ๋ค.- ์ด ๊ณผ์ ์
inflate (ํฝ์ฐฝ)
๋ผ๊ณ ๋ถ๋ฅด๋ ์ด์ ๋,
๋จ์ mark word โ mark word + monitor ๊ตฌ์กฐ์ฒด ์กฐํฉ์ผ๋ก ๊ด๋ฆฌ ๋จ์๊ฐ ์ปค์ง๊ธฐ ๋๋ฌธ์ด๋ค.
์ ๋ชจ๋ ๊ณผ์ ์ ํตํด์ hashCode๋ฅผ ํ๋ณดํ๊ณ , ์ต์ข ์ ์ผ๋ก ๋ฐํํ๋ค.
์ฌ๊ธฐ๊น์ง JVM ๋ด๋ถ์์ hashCode๋ฅผ ์ด๋ป๊ฒ ์์ฑํ๋์ง๊น์ง ์์๋ณด์๋ค. ๋ก์ง์ ๋ณด๋ฉด ์๊ฒ ์ง๋ง, ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ด์ฉํ๋ ๋ถ๋ถ์ ์์๋ค.
๊ทธ๋ฐ๋ฐ ์ ์ด๋ฐ ์คํด๊ฐ ์์๋ ๊ฒ์ผ๊น? ์๋์ ์ด์ ์ ๋๊ฐ ์์ ๋ฏํ๋ค.
- toString() ์ถ๋ ฅ๊ฐ, ์ฆ
@ ๋ค hashCode์ 16์ง์
๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ฒ๋ผ ๋ณด์ธ๋ค.
- ์ค๋๋ JVM ์ผ๋ถ ์ต์ ์์, ์ค์ ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
- ๋ฌธ์๋ ๊ฐ์์์ ๋จ์ํํ์ฌ ์ค๋ช ํ์๋ค.
identityHashCode == ๋ฉ๋ชจ๋ฆฌ ์ฃผ์
๋ผ๋ ์๋ชป๋ ์ดํด๊ฐ ์๋ค.
์ฌ๊ธฐ์ 2๋ฒ์ ์ฃผ๋ชฉํ ๋งํ๋ค. ์ด์ ๋ํด์๋ ์ค์ ์ฝ๋๋ก ์ดํด๋ณผ ์๊ฐ ์๋ค.
ObjectSynchronizer::FastHashCode
์์ get_next_hash
๋ฅผ ํธ์ถํ๋๋ฐ ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
static inline intptr_t get_next_hash(Thread* current, oop obj) {
intptr_t value = 0;
if (hashCode == 0) {
// This form uses global Park-Miller 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 addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
} else if (hashCode == 2) {
value = 1; // for sensitivity testing
} else if (hashCode == 3) {
value = ++GVars.hc_sequence;
} else if (hashCode == 4) {
value = cast_from_oop<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 = current->_hashStateX;
t ^= (t << 11);
current->_hashStateX = current->_hashStateY;
current->_hashStateY = current->_hashStateZ;
current->_hashStateZ = current->_hashStateW;
unsigned v = current->_hashStateW;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
current->_hashStateW = v;
value = v;
}
value &= markWord::hash_mask;
if (value == 0) value = 0xBAD;
assert(value != markWord::no_hash, "invariant");
return value;
}
if (hashCode == 0) {
// 0๏ธโฃ Park-Miller RNG
value = os::random();
}
๊ธ๋ก๋ฒ ๋์ ์์ฑ๊ธฐ(Park-Miller RNG) os::random()
์ ํตํด ์ ์ญ ๋์๋ฅผ ์์ฑํ๋ค.else if (hashCode == 1) {
// 1๏ธโฃ ์ฃผ์ ๊ธฐ๋ฐ + stw_random
intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
}
๊ฐ์ฒด ์ฃผ์ ์ผ๋ถ + XOR + STW ๋๋ค ๋ฐฉ์
์ผ๋ก, ๊ฐ์ฒด ์ฃผ์ ๋นํธ๋ฅผ ์ํํธํ๊ณ XOR ์ฐ์ฐํ์ฌ ์์ฑํ๋ค.GVars.stw_rando
m์ stop-the-world
์์ ๋์๋ก GC ์ดํ์๋ idempotent (๋ฉฑ๋ฑ, ๊ฐ ์ผ๊ด์ฑ ๋ณด์ฅ)
ํ๋ค.else if (hashCode == 2) {
// 2๏ธโฃ ๊ฐ๋ ํ
์คํธ์ฉ ์์
value = 1;
}
ํด์ ๊ฐ์ด ๋ฌด์กฐ๊ฑด 1
์ด๋ค.else if (hashCode == 3) {
// 3๏ธโฃ ์์ฐจ ์ฆ๊ฐ๊ฐ
value = ++GVars.hc_sequence;
}
else if (hashCode == 4) {
// 4๏ธโฃ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ cast
value = cast_from_oop<intptr_t>(obj);
}
GC Compaction
๋ฑ์ผ๋ก ๊ฐ์ฒด๊ฐ ์ด๋ํ๋ฉด ์ฃผ์๊ฐ ๋ฐ๋ ์ ์์ด์ ์ฃผ์ํด์ผ ํ๋ค.๊ฐ์ฒด ํค๋
mark word
๋ด๋ถ์ ํด์ ์ฝ๋๋ฅผ ์ ์ฅํ๋ค๋ฉด GC ์์ ์ด ์ด๋ฃจ์ด์ ธ ์ฃผ์๊ฐ ๋ณ๊ฒฝ๋์ด๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.๋ฐ๋ฉด, ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ํด์ ์ฝ๋๋ก ํ์ฉํ๋ค๋ฉด GC Compaction ๋จ๊ณ ๋ฑ์์ ์ํฅ์ ๋ฐ์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ด ์ถ์ฒํ์ง ์๋ ๊ฒ์ด๋ค.
else {
// 5๏ธโฃ Marsaglia xor-shift
unsigned t = current->_hashStateX;
t ^= (t << 11);
current->_hashStateX = current->_hashStateY;
current->_hashStateY = current->_hashStateZ;
current->_hashStateZ = current->_hashStateW;
unsigned v = current->_hashStateW;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
current->_hashStateW = v;
value = v;
}
์ค๋ ๋ ๋ก์ปฌ xor-shift ๋์
๋ก, Marsaglia xor-shift ๋ฐฉ์ ๋์
์ด๋ค.์ค์ ๋ก ์ต์ 4๋ก ์ค์ ํ๊ณ ํด์ ์ฝ๋๋ฅผ ์ถ๋ ฅํ๋ฉด ์ด๋ป๊ฒ ๋์ฌ๊น?
์์์ ์์ ์ธํ ๋ฆฌ์ ์ด์์ VM ์ต์ ์ ์ค์ ํด์ค๋ค.
- Run ์์์
Edit Configuration
ํด๋ฆญ- ์คํํ ํด๋์ค ์ ํ (ํ Test)
- VM Options ์ ๋ ฅ
->-XX:hashCode=4
์ด๋ ๊ฒ๊น์ง๋ง ์ค์ ํ๊ณ ์คํํ๋ฉด ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฌ๋ค.
Error: VM option 'hashCode' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.
์ด๋ ํด๋น hashCode ์ต์
์ด ์คํ์ (Experimental) ์ต์
์ด๊ธฐ ๋๋ฌธ์ JVM์ด ๊ธฐ๋ณธ์ ์ผ๋ก ๋ง๊ณ ์๋ค๋ ์๋ฏธ์ด๋ค.
๊ทธ๋์ ์คํ์ ์ต์ ์ ํ์ฑํํ๋๋ก ์ถ๊ฐ JVM ์ต์ ์ ์ค์ผ๋ง ํ๋ค.
์์ ๊ฐ์ด -XX:+UnlockExperimentalVMOptions
์ต์
์ ์์ ์ถ๊ฐํด์ฃผ์.
import org.openjdk.jol.vm.VM;
public class Test {
public static void main(String[] args) {
Object obj = new Object();
// hashCode ์ถ๋ ฅ
int hashCode = obj.hashCode();
System.out.println("obj.hashCode(): " + hashCode);
// JOL์ ์ด์ฉํ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ์ถ๋ ฅ
long address = VM.current().addressOf(obj);
System.out.println("Memory address : " + address);
// ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ int๋ก ์บ์คํ
(ํ์ 32๋นํธ)
int addressAsInt = (int) address;
System.out.println("Memory address (int cast) : " + addressAsInt);
// hashCode์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ int ์บ์คํ
๊ฐ ๋น๊ต
if (hashCode == addressAsInt) {
System.out.println("hashCode == (int) memory address : ๋์ผํฉ๋๋ค.");
} else {
System.out.println("hashCode != (int) memory address : ๋ค๋ฆ
๋๋ค.");
}
}
}
obj.hashCode(): 266593480
Memory address : 30331364552
Memory address (int cast) : 266593480
hashCode == (int) memory address : ๋์ผํฉ๋๋ค.
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด Object ํด๋์ค์ hashCode() ๊ฐ๊ณผ, ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ ์ํ์ผ๋ก ์บ์คํ ํ ๊ฐ์ด ๋์ผํ ๊ฒ์ ๋ณผ ์ ์๋ค!
๐ง๐ปโ๐ป ์ฆ, ์ค์ ๋ก ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ํด์ ์ฝ๋๋ก ํ์ฉํ๋ ์ต์ ์ ์กด์ฌํ๋ค.
Object.hashCode()
๋ JVM ๊ตฌํ์ ๋ฐ๋ผ ๋์์ด ๋ฌ๋ผ์ง๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์๋ ๋ฌด๊ดํ ๊ฐ(๋์, XOR-Shift ๋ฑ)์ ๋ฐํํ๋ค.
hashCode()
์ toString()์ @๋ค ๊ฐ
์ ๊ฐ์ง๋ง, ์ด๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๊ฐ ์๋ ํด์ ์ฝ๋์ 16์ง์ ํํ์ผ ๋ฟ์ด๋ค.
ํน์ JVM ์ต์
(-XX:hashCode=4)
์ ์ ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ธฐ๋ฐ hashCode๋ฅผ ๋ฐํํ๋๋ก ์ค์ ํ ์ ์๋ค. (๋ค๋ง ์ด ์ต์
์ ์คํ์ ์ด๋ฉฐ ๊ธฐ๋ณธ๊ฐ์ ์๋๋ค.)