[java]primitive에 대한 System.identityHashCode의 동작

java는 call by value만이 존재한다.

혼란이 생기는이유는 c/c++을 경험했기 때문이다.
하지만 JAVA에서는 call by value만이 있다.

  • call by value : 값만 전달하는 방식
  • 메소드에 값을 전달하고, 호출된 메소드에서, 그 값에 대한 변화가 생기더라도, 호출한 메소드 측에는 아무런 영향도 주지 않는다.

여기서 혼란이 생기는 경우는 다음과 같은 경우다

void foo(User u){
	u.name = "abc";
}
User a = new User("g");
foo(a);
// a.name = "abc"가 된다. 
  • 엥?? 호출한 쪽의 User a의 name이 바뀌었는데요?? call by reference가 아닌가요??

하지만 아래의 경우를 보자.

void foo(User u){
	u = new User("abc");
}
User a = new User("g");
foo(a);
// a.name = "g"이다

JAVA에서 객체를 생성하면, 실제 object는 Heap memory에 생성이 되고, 참조 변수는 이를 참조하고 있을 뿐이다.
따라서 메소드에 값을 넘겨준다면, "값의 복사본"이 function으로 넘어간다. 그 메소드내에서 local variable로 사용이 된다. 그런데 이 변수 또한, 같은 객체를 참조하는 참조변수일 뿐이다.
즉, C/C++에서 할 수 있는 *user = new User("newman"); 을 통해서, 원래의 객체가 할당되어있던 그 메모리 주소에! 새로운 객체를 덮어 씌우는 개념이 아니게 되는 것이다.
여기서 u = new User("newman"); 을 한다면, 그저 그 메소드의 local 참조변수 u가 참조하는 새로운 인스턴스가 Heap memory에 생긴 것이다.


그런데
코딩인터뷰 관련글에선 아래와 같은 예제를 들고 있었다.
그런데 아래와 같은 코드를 통해 상황을 이해하기에는 힘들다고 생각했다.

int a=1; 
int b=a;

 System.out.println(System.identityHashCode(a));    // out: 1627674070
 System.out.println(System.identityHashCode(b));    // out: 1627674070

Primitive type의 경우, Java stack에 값과 함께 저장된다. 즉, Reference type은 "참조값"이 저장되는 반면, Primitive type의 경우, 위의 경우 a에는 1이 저장되는 것이다.

  • 그런데 현재 System.idnetytiHashCode()로 비교를 하고 있었다.

그런데 위의 코드에서 사용한 System.identityHashCode(Object o); 는 말 그대로 Primitive 가 아닌, Object를 받는것이었다. 즉, int가 Wrapper 클래스인 Integer로 변환되어 넘어가게 된다.

  • 이런것이 일어나는 것은 Java에서는 Primitive type에 대해 자동적으로 autovixing과 unboxing을 해 주기 때문이다.

JAVA의 built in primitive type들에 상응하는 Immutable class인 Wrapper class 들 (Integer, Boolean, Short, Long, Float,Dobule,Byte )

  • autoboxing 개념 : Integer j =1; : Integer.valueOf(int)가 호출된다
  • unboxing개념 : int i= new Integer(1);
  • 예를들어, Integer class를 보도록 하자.
public final class Integer extends Number implements Comparable<Integer> {
    private final int value;
    ...
    }

참고로 Immutable 객체 (thread-safe)임을 볼수가 있다. --> Wrapper classs는 immutable하다. 즉 state를 바꿀 수 없다. 이러한 immutable class의 경우, thread-safe하다는 장점을 가진다. ( 더이상 변경할 수 없기 때문에 다수의 스레드에서는 read-only접근만을 할 것이고 일관성있는 데이터를 얻을 수 밖에 없을테니)

  • 원래 System.identityHashCode() 를 통해 같은 값을 갖게 되는 객체들은 , 그들이 같은 객체일 경우에 해당되는 것이다.
    그런데 위와 같은 경우 , 같은 값이 출력되었다. 이런일이 왜 일어났을까???

    JVM에서는 작은 값의 Integer 의 경우, 최적화를 목적으로 cache를 한다. 즉, 0~127의 값에 대한 identity hashcodes는 같은 값이 나오게 된다는 것이다.

따라서

        int a = 100;
        Integer c = Integer.valueOf(100);
        int b = a;
        System.out.println("a = " + System.identityHashCode(a));
        System.out.println("b = " + System.identityHashCode(b));
        System.out.println("c = " + System.identityHashCode(c));
//        a = 1531333864
//        b = 1531333864
//        c = 1531333864
        

이런 출력이 나오게 된다.

        int a = 1000;
        Integer c = Integer.valueOf(1000);
        int b = a;
        System.out.println("a = " + System.identityHashCode(a));
        System.out.println("b = " + System.identityHashCode(b));
        System.out.println("c = " + System.identityHashCode(c));
//        a = 1531333864
//        b = 785992331
//        c = 940060004

Primitives는 literal이다. memory에 있는 고정된 값이다. 따라서 이 값은 ==으로 값에 대한 "equality"에 대한 테스트가 가능하다. ( 참고로 float, double의 경우에는, == 으로는 동등 비교의 정확성이 떨어진다. memory에 저장되는 방식 때문이다. 이들은 정확한 값이라고 볼 수 없다)

  • 참조변수 또한, ==은 결국, "참조값"의 비교를 하는 것 과 같다.

처음으로 Java에서 Cache라는.... 클래스를 보게 되었다.
현재는 이것을 어떻게 사용해야할지는 잘 모르겠지만 Cache라는 이름 답게, 성능을 높이기 위한 역할을 하는 아이다.
성능을 높이기 위해, 자주 사용되는 값(-`127~128 이라고 by default로 되어있는..)에 대한 Integer타입의 인스턴스를 미리 생성해두고, valueOf() method를 이용해서 인스턴스를 리턴할 경우에는 heap에 새로운 instance를 생성하는게 아니라, 기존에 생성되어 있는 인스턴스를 리턴하는 식으로 동작하게끔 하는 것 같다.
또한, autoboxing이 일어날 때면, 자동으로 valueOf() method가 호출이 된다.

Internal cache of integers , IntegerCache

  • java.lang.Integer안에는 IntegerCache가 존재한다. 이곳에는 -128~127 에 대한 instances들을 저장하고 있다.
    따라서 Integer.valueOf(1000)과 달리 Integer.valueOf(100) 은 항상 같은 instance를 return하는 것이다.

    이런식으로, 자주사용되는 Integer값을 재사용할 경우, GC의 일을 줄여줄 수 있다.

Integer.valueOf(int i) 사용 권장

  • 생성자를 사용하기 보다, valueOf()를 사용할 것을 권장하고 있다. 왜냐하면, 이 메소드를 사용하는 것이 caching을 사용하게 되어, 공간 시간 성능이 더 좋게 나오기 때문이다.
  • 이 method에서는 항상 -128~127의 범위 값에 대해 캐쉬되어 있는 Integer 인스턴스를 리턴하게 된다.
  • autoBoxing은, 내부적으로 valueOf()가 호출되는 것이다.
    @HotSpotIntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
  • 이러한 Cache의 Size는 VM의 -XX:AutoBoxCacheMax라는 parameter값을 조정함으로서 사이즈를 늘릴 수 있다.
 /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * jdk.internal.misc.VM class.
     */
         private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

이 아래 코드에서는, cache array에 low부터 시작해서 high까지의 값을 갖는 Integer인스턴스를 미리 행성해 놓는 것을 볼 수 있다.
static class인 IntegerCache

  • VM initialization때 java.lang.Integer.IntegerCache.high프로퍼티가 127보다 큰 값이 세팅되어있다면, 아마 h는 왠만하면 그 값으로 세팅 될 것임. 물론 h= Math.min(i,Integer.MAX_VALUE-(-low)-1이라는 코드가 있긴 하지만, 그정도로 큰 값이 와있을 것 같지는 않다....

refer

https://stackoverflow.com/questions/47677305/system-identityhashcode-behavior-on-primitives

좋은 웹페이지 즐겨찾기