[Java] ๐Ÿ“ซ hashCode == ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ์ผ๊นŒ?

Sangho Hanยท2025๋…„ 6์›” 19์ผ
2

โ˜•๏ธย Java

๋ชฉ๋ก ๋ณด๊ธฐ
19/20
post-thumbnail

๐Ÿ“Œ ์„œ๋ก 

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
    }
}
  1. hashCode์™€ identityHashCode๋Š” ๋™์ผํ•˜๋‹ค.
  • ์žฌ์ •์˜ํ•˜์ง€ ์•Š์€, Object ํด๋ž˜์Šค์˜ ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— identityHashCode()์™€ ๋™์ผํ•œ ๊ฐ’์ด ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.
  1. hashCode์™€ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋Š” ๋™์ผํ•˜์ง€ ์•Š๋‹ค.
  • 2060468723 ์™€ 30331364672
  • ์ˆซ์ž๋งŒ ๋ณด์•„์„œ๋Š” ์œ ์‚ฌ์„ฑ์„ ์•Œ์•„์ฐจ๋ฆฌ๊ธฐ๊ฐ€ ํž˜๋“ค๋‹ค.
  1. toString()์—์„œ @ ๋’ค์— ๋‚˜์˜ค๋Š” ๊ฐ’๊ณผ, hashCode๋ฅผ 16์ง„์ˆ˜๋กœ ๋ณ€ํ™˜ํ•œ ๊ฐ’์€ ๋™์ผํ•˜๋‹ค.
public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • ์ด๋Š” Object ํด๋ž˜์Šค์—์„œ์˜ toString() ๋ฉ”์„œ๋“œ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ๊ตฌํ˜„๋˜์–ด์žˆ์œผ๋ฏ€๋กœ ๋‹น์—ฐํ•œ ๊ฒฐ๊ณผ์ด๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ ์•Œ๊ฒŒ ๋œ ์‚ฌ์‹ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. hashCode()์™€ toString() @ ์ดํ›„์˜ ๊ฐ’์€ ์‚ฌ์‹ค์ƒ ๊ฐ™์€ ๊ฐ’์ด๋‹ค.
  2. 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() ๋ฉ”์„œ๋“œ์— ์žˆ๋Š” ์ฃผ์„์„ ์‚ดํŽด๋ณด๋ฉด ๋งŽ์€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

  1. ๊ฐ™์€ ๊ฐ์ฒด์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค. (๋‹จ, equals์— ์‚ฌ์šฉ๋˜๋Š” ๊ฐ’์ด ๋ณ€ํ•˜์ง€ ์•Š์„ ๋•Œ)
  1. equals๊ฐ€ true์ธ ๋‘ ๊ฐ์ฒด๋Š” hashCode๋„ ๊ฐ™์•„์•ผ ํ•œ๋‹ค.
  1. equals๊ฐ€ false์ธ ๋‘ ๊ฐ์ฒด๋Š” hashCode๊ฐ€ ๋‹ค๋ฅผ ํ•„์š”๋Š” ์—†์ง€๋งŒ, ๋‹ค๋ฅด๋ฉด ์„ฑ๋Šฅ์ด ์ข‹๋‹ค.
  1. 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

์ฝ”๋“œ GitHub ๋งํฌ

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;
  }
}

์ฝ”๋“œ GitHub ๋งํฌ

FastHashCode() ๋Š” Object.hashCode() ์˜ ๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ๋ฒจ ๊ตฌํ˜„์œผ๋กœ, ๊ฐ์ฒด์˜ hashCode ๊ฐ’์„ ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  1. ์ด๋ฏธ ์ƒ์„ฑ๋˜์–ด ๊ฐ์ฒด header/monitor์— ๊ธฐ๋ก๋œ hash
  2. ์ƒˆ๋กœ ์ƒ์„ฑํ•œ 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;
}
  1. ์ด๋ฏธ hash๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  2. ์—†์œผ๋ฉด ์ƒˆ hash๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. (get_next_hash)
  3. CAS๋กœ markWord์— ๊ธฐ๋ก โ†’ ์„ฑ๊ณตํ•˜๋ฉด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  4. ์‹คํŒจํ•˜๋ฉด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ธฐ๋กํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ โ†’ ๋ฃจํ”„๋ฅผ ๋ฐ˜๋ณตํ•œ๋‹ค.

CAS?

CAS(Compare-And-Swap) ๋Š” ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์›์ž์ (atomic)์œผ๋กœ ๊ฐ’ ๊ฐฑ์‹ ์„ ๋ณด์žฅํ•˜๋Š” ์—ฐ์‚ฐ์ด๋‹ค.

Java๋‚˜ JVM ๋‚ด๋ถ€์—์„œ ๋ฝ ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋ฉฐ, ํŠนํžˆ mark word๋‚˜ ๊ฐ์ฒด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค.

๐Ÿ’ก (b) ์ด๋ฏธ inflated (monitor ์‚ฌ์šฉ ์ค‘)

Inflated?

  • inflated ์ƒํƒœ๋Š” ๊ฐ์ฒด๊ฐ€ monitor (ObjectMonitor) ๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋Š” ๋œป
  • ์ฆ‰, ๊ฐ์ฒด์˜ lock ์ƒํƒœ๊ฐ€ ๊ฒฝ๋Ÿ‰ ๋ฝ(lightweight lock) ๋˜๋Š” bias ๋ฅผ ๋„˜์–ด ๋ฌด๊ฑฐ์šด(monitor) ๋ฝ์œผ๋กœ ์Šน๊ฒฉ๋œ ์ƒํƒœ์ด๋‹ค.
  • ์•„๋ž˜์˜ ๊ฒฝ์šฐ์— Inflated ์ƒํƒœ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.
  1. lock ๊ฒฝ์Ÿ์ด ์‹ฌํ•ด์„œ ๋” ์ด์ƒ ๊ฒฝ๋Ÿ‰ ๋ฝ์œผ๋กœ๋Š” ๋™๊ธฐํ™” ์œ ์ง€๊ฐ€ ์–ด๋ ค์›€
  2. ํŠน์ • JVM ๋‚ด๋ถ€ ๋™์ž‘ (์˜ˆ: hashCode ๊ธฐ๋ก) ๋•Œ๋ฌธ์— monitor๊ฐ€ ํ•„์š”ํ•จ

์ด ๊ฒฝ์šฐ mark word์—๋Š” monitor์˜ ์ฃผ์†Œ๊ฐ€ ์ €์žฅ๋œ๋‹ค.

Monitor?

  • monitor๋Š” JVM ๋‚ด๋ถ€์—์„œ ๋ฝ๊ณผ ๊ด€๋ จ๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ๋™๊ธฐํ™” ๊ฐ์ฒด์ด๋‹ค.
  • C++์˜ ObjectMonitor ๊ตฌ์กฐ์ฒด๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค.
  • ์•„๋ž˜์˜ ์ •๋ณด๋“ค์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.
  1. ๋ฝ ์†Œ์œ ์ž ์ •๋ณด
  2. ๋Œ€๊ธฐ ํ (๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ด ๊ฐ์ฒด์˜ ๋ฝ์„ ๊ธฐ๋‹ค๋ฆด ๋•Œ)
  3. displaced mark word (์›๋ž˜ mark word ๋ณต์‚ฌ๋ณธ)
  4. hashCode (ํ•„์š”์‹œ ๊ธฐ๋ก)
  5. ๊ธฐํƒ€ ์ƒํƒœ ์ •๋ณด

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;
    }
}
  1. monitor์—์„œ hash ์ฝ์Œ โ†’ ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  2. 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;
}
  1. ์Šคํƒ lock ์ƒํƒœ๋ผ๋ฉด displaced markWord ์—์„œ hash๋ฅผ ํ™•์ธํ•œ๋‹ค.
  2. ์—†์œผ๋ฉด 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;
}
  1. monitor inflate โ†’ ์•ˆ์ •์  hash ๊ธฐ๋ก ์ค€๋น„
  2. hash ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ ํ›„ CAS ๋กœ ๊ธฐ๋กํ•œ๋‹ค.

Monitor inflate?

  • monitor inflate๋Š” JVM์ด mark word์˜ ๊ณต๊ฐ„ ๋Œ€์‹  ObjectMonitor ๋ผ๋Š” ๋ณ„๋„ ๊ตฌ์กฐ์ฒด(ํž™์— ์œ„์น˜)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์—ฐ๊ฒฐํ•˜๋Š” ์ž‘์—…์„ ๋งํ•œ๋‹ค.
  • ์ด ๊ณผ์ •์„ inflate (ํŒฝ์ฐฝ) ๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ์ด์œ ๋Š”,
    ๋‹จ์ˆœ mark word โ†’ mark word + monitor ๊ตฌ์กฐ์ฒด ์กฐํ•ฉ์œผ๋กœ ๊ด€๋ฆฌ ๋‹จ์œ„๊ฐ€ ์ปค์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

6. ์ตœ์ข… ๋ฐ˜ํ™˜

์œ„ ๋ชจ๋“  ๊ณผ์ •์„ ํ†ตํ•ด์„œ hashCode๋ฅผ ํ™•๋ณดํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


๐Ÿ“Œ ์˜คํ•ด๊ฐ€ ์ƒ๊ธด ์ด์œ ?

์—ฌ๊ธฐ๊นŒ์ง€ JVM ๋‚ด๋ถ€์—์„œ hashCode๋ฅผ ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•˜๋Š”์ง€๊นŒ์ง€ ์•Œ์•„๋ณด์•˜๋‹ค. ๋กœ์ง์„ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ๊ฐ์ฒด์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ถ€๋ถ„์€ ์—†์—ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์™œ ์ด๋Ÿฐ ์˜คํ•ด๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฒƒ์ผ๊นŒ? ์•„๋ž˜์˜ ์ด์œ  ์ •๋„๊ฐ€ ์žˆ์„ ๋“ฏํ•˜๋‹ค.

  1. toString() ์ถœ๋ ฅ๊ฐ’, ์ฆ‰ @ ๋’ค hashCode์˜ 16์ง„์ˆ˜๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.
  1. ์˜ค๋ž˜๋œ JVM ์ผ๋ถ€ ์˜ต์…˜์—์„œ, ์‹ค์ œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.
  1. ๋ฌธ์„œ๋‚˜ ๊ฐ•์˜์—์„œ ๋‹จ์ˆœํ™”ํ•˜์—ฌ ์„ค๋ช…ํ•˜์˜€๋‹ค.
  1. 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 ์˜ต์…˜์„ ์„ค์ •ํ•ด์ค€๋‹ค.

  1. Run ์˜†์—์„œ Edit Configuration ํด๋ฆญ
  2. ์‹คํ–‰ํ•  ํด๋ž˜์Šค ์„ ํƒ (ํ˜„ Test)
  3. 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() ๊ฐ’๊ณผ, ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋ฅผ ์ •์ˆ˜ํ˜•์œผ๋กœ ์บ์ŠคํŒ…ํ•œ ๊ฐ’์ด ๋™์ผํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค!

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์ฆ‰, ์‹ค์ œ๋กœ ๊ฐ์ฒด์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋ฅผ ํ•ด์‹œ ์ฝ”๋“œ๋กœ ํ™œ์šฉํ•˜๋Š” ์˜ต์…˜์€ ์กด์žฌํ•œ๋‹ค.


๐Ÿ“Œ ๊ฒฐ๋ก 

  1. Object.hashCode()๋Š” JVM ๊ตฌํ˜„์— ๋”ฐ๋ผ ๋™์ž‘์ด ๋‹ฌ๋ผ์ง€๋ฉฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ์™€๋Š” ๋ฌด๊ด€ํ•œ ๊ฐ’(๋‚œ์ˆ˜, XOR-Shift ๋“ฑ)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  2. hashCode()์™€ toString()์˜ @๋’ค ๊ฐ’์€ ๊ฐ™์ง€๋งŒ, ์ด๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๊ฐ€ ์•„๋‹Œ ํ•ด์‹œ ์ฝ”๋“œ์˜ 16์ง„์ˆ˜ ํ‘œํ˜„์ผ ๋ฟ์ด๋‹ค.

  3. ํŠน์ • JVM ์˜ต์…˜(-XX:hashCode=4)์„ ์ ์šฉํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ๊ธฐ๋ฐ˜ hashCode๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. (๋‹ค๋งŒ ์ด ์˜ต์…˜์€ ์‹คํ—˜์ ์ด๋ฉฐ ๊ธฐ๋ณธ๊ฐ’์€ ์•„๋‹ˆ๋‹ค.)


์ฐธ๊ณ ํ•œ ๋ธ”๋กœ๊ทธ

profile
์•ˆ๋…•ํ•˜์„ธ์š”. ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž, ํ•œ์ƒํ˜ธ์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€