[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
Author And Source
이 문제에 관하여([java]primitive에 대한 System.identityHashCode의 동작), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ynoolee/javaprimitive에-대한-System.identityHashCode의-동작저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)