๐ ์๋ก
Java์์ Object ํด๋์ค์ hashCode() ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด, ์ผ๋ฐ์ ์ผ๋ก๋ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ ๊ทธ ์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ฐํ ์์น๋ฅผ ํด์ ์ฝ๋๋ก ๋ฐํํ๊ฒ ๋๋ค.
(hashCode() ๋ฉ์๋์ ๋ํด์๋ ํด๋น ๊ธ์ ์ ๋ฆฌํด๋์๋ค.)
์ฌ๊ธฐ์ ์ค์ํ ์ ์ hashCode() == ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ฐ ์๋๋ผ, ํด์ ์ฝ๋๋ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ or ์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ฐํ ์์น ๋ผ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ ์ด์ ๋ํด์ ํผ๋ํ๋ ์ฌ๋๋ค์ด ๋ง๊ณ , ์ค์ ๋ก ๊ด๋ จํ ๋ง์ ์ ๋ณด๋ค์ด ์ธํฐ๋ท ์์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฒ ๊ธ์ ํตํด์ ์ฌ์ค ์ ๋ฌด๋ฅผ ํ์ธํด ๋ณด๋ ค๊ณ ํ๋ค.
์ฌ์ฉ Java ๋ฒ์ : OpenJDK17
๐ JVM์์์ hashCode() ๊ธฐ๋ณธ ๋์
Object.hashCode()
@IntrinsicCandidate
public native int hashCode();Object ํด๋์ค์ ํด์ ์ฝ๋ ๋ฉ์๋์ด๋ค.
์ด๋ ์ผ๋ฐ์ ์ธ ๋ฉ์๋์ ๋ค๋ฅด๊ฒ, native ๋ฉ์๋์ด๊ธฐ ๋๋ฌธ์ JVM ๋ด๋ถ(๋ค์ดํฐ๋ธ ์ฝ๋) ์ ๊ตฌํ๋์ด ์๋ค.
JNI(Java Native Interface) ๋ฅผ ํตํด JVM ๋ ๋ฒจ, ์์คํ
๋ ๋ฒจ์์ ์๋ํ๋ ๊ตฌ์กฐ์ด๋ค.
๊ทธ๋ ๊ธฐ์ ์ด๋ JVM์ ์์กดํ๋ ๋์์ด๋ฉฐ, ๋ด๋ถ ๊ตฌํ ๋ฐฉ์์ด JVM์ ๋ฒ์ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์๋ค๋ ์๋ฏธ๊ฐ ๋๋ค.
๐ hashCode()์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๋น๊ต
์์ ์ฝ๋๋ฅผ ํตํด์ 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๋ ๋์ผํ๋ค.
- ์ฌ์ ์ํ์ง ์์, Object ํด๋์ค์ ๊ธฐ๋ณธ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ identityHashCode()์ ๋์ผํ ๊ฐ์ด ๋์ค๊ฒ ๋๋ค.
hashCode์๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ ๋์ผํ์ง ์๋ค.
2060468723์30331364672- ์ซ์๋ง ๋ณด์์๋ ์ ์ฌ์ฑ์ ์์์ฐจ๋ฆฌ๊ธฐ๊ฐ ํ๋ค๋ค.
toString()์์@๋ค์ ๋์ค๋ ๊ฐ๊ณผ,hashCode๋ฅผ 16์ง์๋ก ๋ณํํ ๊ฐ์ ๋์ผํ๋ค.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}- ์ด๋ Object ํด๋์ค์์์ toString() ๋ฉ์๋๊ฐ ๊ทธ๋ ๊ฒ ๊ตฌํ๋์ด์์ผ๋ฏ๋ก ๋น์ฐํ ๊ฒฐ๊ณผ์ด๋ค.
์ฌ๊ธฐ๊น์ง ์๊ฒ ๋ ์ฌ์ค์ ๋ค์๊ณผ ๊ฐ๋ค.
hashCode()์toString() @ ์ดํ์ ๊ฐ์ ์ฌ์ค์ ๊ฐ์ ๊ฐ์ด๋ค.hashCode()์๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ ๋์ผํ์ง ์๋ค.
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 ๋ฒ์ ์์๋ ๊ธฐ์ฌ๋ ๊ฒฝ์ฐ๋ ์๋ ๋ฏํ๋ค.
OpenJDK JVM์ hashCode()
๊ทธ๋ ๋ค๋ฉด ์ค์ ๋ก ์ด๋ ํ ๋ก์ง์ ํตํด์ JVM์ ํด์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์๋์ง ์์๋ณด์. JVM ๋ด๋ถ๋ ๋๋ถ๋ถ C++๋ก ๊ตฌํ๋์ด ์๋ค.
์๋ก ์์ ๋งํ๋ฏ์ด OpenJDK 17 ๋ฒ์ ์ ์ฌ์ฉํ์๋ค.
JVM_ENTRY
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 ํธ๋ค์ ์ค์ ๊ฐ์ฒด ์ฐธ์กฐ๋ก ๋ณํํ๋ค.
ObjectSynchronizer::FastHashCode
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 (์ถฉ๋ ๋ฐฉ์ง, ๋๊ธฐํ ๋ณด์ฅ)
1. Biased Locking ์ํ ์ฒดํฌ
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 ์์ ๋ฑ์ ์ํํ ์ ์๋ค.
2. ๋ฌดํ ๋ฃจํ (hash ๊ฐ ์ป์ ๋๊น์ง ๋ฐ๋ณต)
while (true) {
...
}๐ก hash ๊ฐ์ด ์์ฑ/์ค์ ๋ ๋๊น์ง ์ฌ๋ฌ ์๋๋ฅผ ๋ฐ๋ณตํ๋ค.
3. markWord ์ฝ๊ธฐ
markWord mark = read_stable_mark(obj);๐ก ๊ฐ์ฒด header ์ค ์ผ๋ถ์ธ mark word๋ฅผ ์ฝ๋ ๋ถ๋ถ์ด๋ค.
4. header ์ํ์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ
๐ก (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;
}- ์ด๋ฏธ hash๊ฐ ์์ผ๋ฉด ๋ฐํํ๋ค.
- ์์ผ๋ฉด ์ hash๋ฅผ ์์ฑํ๋ค. (get_next_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;
}- ์คํ lock ์ํ๋ผ๋ฉด
displaced markWord์์ hash๋ฅผ ํ์ธํ๋ค. - ์์ผ๋ฉด
inflateํ์ โ race ๋ฐฉ์ง.
5. monitor inflate ๋ฐ hash ์ค์
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 ๊ธฐ๋ก ์ค๋น- hash ์์ผ๋ฉด ์๋ก ์์ฑ ํ
CAS๋ก ๊ธฐ๋กํ๋ค.
Monitor inflate?
monitor inflate๋ JVM์ด mark word์ ๊ณต๊ฐ ๋์ObjectMonitor ๋ผ๋ ๋ณ๋ ๊ตฌ์กฐ์ฒด(ํ์ ์์น)๋ฅผ ์์ฑํ๊ณ ์ฐ๊ฒฐํ๋ ์์ ์ ๋งํ๋ค.- ์ด ๊ณผ์ ์
inflate (ํฝ์ฐฝ)๋ผ๊ณ ๋ถ๋ฅด๋ ์ด์ ๋, ๋จ์ mark word โ mark word + monitor ๊ตฌ์กฐ์ฒด ์กฐํฉ์ผ๋ก ๊ด๋ฆฌ ๋จ์๊ฐ ์ปค์ง๊ธฐ ๋๋ฌธ์ด๋ค.
6. ์ต์ข ๋ฐํ
์ ๋ชจ๋ ๊ณผ์ ์ ํตํด์ hashCode๋ฅผ ํ๋ณดํ๊ณ , ์ต์ข ์ ์ผ๋ก ๋ฐํํ๋ค.
๐ ์คํด๊ฐ ์๊ธด ์ด์ ?
์ฌ๊ธฐ๊น์ง JVM ๋ด๋ถ์์ hashCode๋ฅผ ์ด๋ป๊ฒ ์์ฑํ๋์ง๊น์ง ์์๋ณด์๋ค. ๋ก์ง์ ๋ณด๋ฉด ์๊ฒ ์ง๋ง, ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ด์ฉํ๋ ๋ถ๋ถ์ ์์๋ค.
๊ทธ๋ฐ๋ฐ ์ ์ด๋ฐ ์คํด๊ฐ ์์๋ ๊ฒ์ผ๊น? ์๋์ ์ด์ ์ ๋๊ฐ ์์ ๋ฏํ๋ค.
- toString() ์ถ๋ ฅ๊ฐ, ์ฆ
@ ๋ค hashCode์ 16์ง์๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ฒ๋ผ ๋ณด์ธ๋ค.
- ์ค๋๋ JVM ์ผ๋ถ ์ต์ ์์, ์ค์ ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
- ๋ฌธ์๋ ๊ฐ์์์ ๋จ์ํํ์ฌ ์ค๋ช ํ์๋ค.
identityHashCode == ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ผ๋ ์๋ชป๋ ์ดํด๊ฐ ์๋ค.
์ฌ๊ธฐ์ 2๋ฒ์ ์ฃผ๋ชฉํ ๋งํ๋ค. ์ด์ ๋ํด์๋ ์ค์ ์ฝ๋๋ก ์ดํด๋ณผ ์๊ฐ ์๋ค.
get_next_hash
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;
}์ต์ 0
if (hashCode == 0) {
// 0๏ธโฃ Park-Miller RNG
value = os::random();
}๊ธ๋ก๋ฒ ๋์ ์์ฑ๊ธฐ(Park-Miller RNG) os::random()์ ํตํด ์ ์ญ ๋์๋ฅผ ์์ฑํ๋ค.- ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ๋ฉด ์บ์ ์ผ๊ด์ฑ(Coherency Traffic) ๋น์ฉ์ด ์ปค์ง ์ ์๋ค.
- OpenJDK 6/7 ๊ธฐ๋ณธ๊ฐ์ด๋ค.
์ต์ 1
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_random์stop-the-world์์ ๋์๋ก GC ์ดํ์๋idempotent (๋ฉฑ๋ฑ, ๊ฐ ์ผ๊ด์ฑ ๋ณด์ฅ)ํ๋ค.- ์ฃผ์ ๊ธฐ๋ฐ ๊ฐ์ ๋ณด์ฌ๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ง์ ์ฐ์ง ์๋๋ค. (์กฐํฉ/์ฐ์ฐ ๊ฒฐ๊ณผ)
์ต์ 2
else if (hashCode == 2) {
// 2๏ธโฃ ๊ฐ๋ ํ
์คํธ์ฉ ์์
value = 1;
}- ํ
์คํธ์ฉ ์์๋ก,
ํด์ ๊ฐ์ด ๋ฌด์กฐ๊ฑด 1์ด๋ค. - ํด์ ์ถฉ๋์ ๊ฐ์ ๋ก ์ ๋ฐํด์ ํด์๋งต, ํด์์ ๋ฑ์ ์ถฉ๋ ์ฒ๋ฆฌ ์ฑ๋ฅ์ ํ ์คํธํ ๋ ์ฌ์ฉํ ์ ์๋ค.
์ต์ 3
else if (hashCode == 3) {
// 3๏ธโฃ ์์ฐจ ์ฆ๊ฐ๊ฐ
value = ++GVars.hc_sequence;
}- ๊ธ๋ก๋ฒ ์์ฐจ ID ๋ฐฉ์์ผ๋ก, ๊ฐ์ฒด๋ง๋ค ์์ฐจ์ ์ผ๋ก ์ฆ๊ฐํ๋ ํด์ ์ฝ๋์ด๋ค.
- ์ถฉ๋์ด ์๊ณ ์์๊ฐ ๋ณด์ฅ๋์ง๋ง, ๋ฉํฐ์ค๋ ๋์์ CAS ๋น์ฉ์ด ์กด์ฌํ๋ค. (์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ฆ๊ฐ์ํค๋ ค ํ ์ ์๊ธฐ ๋๋ฌธ.)
์ต์ 4
else if (hashCode == 4) {
// 4๏ธโฃ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ cast
value = cast_from_oop<intptr_t>(obj);
}- ๊ฐ์ฒด ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ int๋ก ๋ณํํ๋, ๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ธฐ๋ฐ hashCode ๋ฐฉ์์ด๋ค.
GC Compaction๋ฑ์ผ๋ก ๊ฐ์ฒด๊ฐ ์ด๋ํ๋ฉด ์ฃผ์๊ฐ ๋ฐ๋ ์ ์์ด์ ์ฃผ์ํด์ผ ํ๋ค.- ๋๋ฌธ์ ์ผ๋ฐ JVM์์ ๋ํดํธ ๊ฐ์ด ์๋๋ฉฐ, ์ฃผ์ ๊ธฐ๋ฐ hashCode๋ ๋น์ถ์ฒ๋๋ค.
๊ฐ์ฒด ํค๋
mark word๋ด๋ถ์ ํด์ ์ฝ๋๋ฅผ ์ ์ฅํ๋ค๋ฉด GC ์์ ์ด ์ด๋ฃจ์ด์ ธ ์ฃผ์๊ฐ ๋ณ๊ฒฝ๋์ด๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
๋ฐ๋ฉด, ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ํด์ ์ฝ๋๋ก ํ์ฉํ๋ค๋ฉด GC Compaction ๋จ๊ณ ๋ฑ์์ ์ํฅ์ ๋ฐ์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ด ์ถ์ฒํ์ง ์๋ ๊ฒ์ด๋ค.
์ต์ 5
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 ๋ฐฉ์ ๋์์ด๋ค.- ์ค๋ ๋ ๋ก์ปฌ ์ํ(_hashStateX ๋ฑ) ๊ธฐ๋ฐ์ผ๋ก, ๋น ๋ฅด๊ณ ์ถฉ๋์ด ์ ๋ค.
- OpenJDK 8+ ๋ํดํธ์ด๋ค.
๐ ์ต์ 4๋ก ํ ์คํธ
์ค์ ๋ก ์ต์ 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๋ฅผ ๋ฐํํ๋๋ก ์ค์ ํ ์ ์๋ค. (๋ค๋ง ์ด ์ต์ ์ ์คํ์ ์ด๋ฉฐ ๊ธฐ๋ณธ๊ฐ์ ์๋๋ค.)