이전의 포스팅은 접근 방식 자체가 잘못되었다는 것을 깨달았다. 나의 질문이 모호했기 때문에 사람마다 답변이 달랐다고 생각한다.
이전포스팅
int a = 1;
에서 변수 a는 리터털 1의 값 그 자체를 가진다는 의견과 주소를 가리킨다는 의견으로 나누어졌었다.
그 이유는 의문에 대한 접근 방식이 잘못 되었기 때문이다. 내가 정말 원하는 답변을 얻으려면 질문을 변경해야 한다.
"변수 num은 1이라는 리터럴 '값'을 가지는가?"
에서
"변수 num이 가리키는 메모리는 1이라는 리터럴 '값'을 가지는가?"
으로
그래서 "num이 가리키는 메모리는 정수'값'을 가지는가?" 에 대한 내 결론은
"그렇다" 이다.
아래의 코드가 실행된다면 JVM에서는 이렇게 작동할 것이다.
int a = 1;
- 스택 메모리에 4Byte 짜리 공간이 생성된다.
- 변수 a는 이 메모리 공간을 가리킨다.
- 오퍼랜드 스택에 리터럴 1이 저장(push)된다. (-127 ~ 128 이라는 값은 이미 JVM 메모리에 할당되어 있기에 해당 값을 복사하여 저장한다.)
- 오퍼랜드 스택에서 1이라는 값을 pop하여 변수 a가 가리키는 메모리에 1이라는 값(value)을 저장한다.
즉, 변수 a라는 녀석은 1이라는 값(value)을 가지고 있는 메모리 주소를 가리키고 있다는 것.
나는 변수 a가 메모리 공간 그 자체라고 생각을 하고 해당 의문에 접근 했었다.
C/C++ 에서는 개발자가 이러한 메모리의 주소에 직접 접근하는 것을 허용한다. 하지만 자바는 이를 허용하지 않는다.
여러 전문가들의 답변을 종합해보면 아래와 같다. (정수형 연산 시)
Integer형식의 상수 풀은 '값'을 가지고 있다. - oracle 공식문서에 의하면 4byte의 공간에 빅 엔디안 방식으로 저장된다.
오퍼랜드 스택은 '값'을 가지고 연산을 수행한다.
오퍼랜드 스택에서 상수 풀을 참조할 때는 '값'을 복사해온다.
필요하다면 연산의 결과값을 메모리에 저장한다.
즉, int a = 1; 이라는 연산이 수행되면, JVM을 통해 a라는 변수는 물리적으로 1이라는 값을 가지는 메모리를 가리키지만, 논리적으로는 1이라는 값을 가지게 된다는 것이 내 결론이다.
아래는 내 질문에 달렸던 답변이다.
질문1
Oracle JVM 공식문서 - 오퍼랜드 스택에서는 iconst, bipush, sipush 명령어의 설명에서 reference라는 키워드가 있음에도 불구하고 정수값을 1, 2, byte, short 혹은 value 로 표현합니다. 하지만 정확하게 4byte짜리 2진수를 push한다는 말은 찾아볼 수 없었습니다.
그렇다면 사실 오퍼랜드 스택은 값을 가지고 연산을 수행하는 것이 아닌 정수 값을 가지고 있는 메모리의 주소를 참조해서 연산을 수행하는 것인가요?
답변
자바에서 정수형 상수는 주소 값을 가지지 않습니다. 오퍼랜드 스택에 값(value)으로 푸시됩니다. JVM에서는 정수형 상수를 처리하기 위해 iconst, bipush, sipush 등의 명령어를 사용합니다. 이 명령어들은 해당하는 값(value)을 오퍼랜드 스택에 직접 푸시하므로 주소 값을 참조하는 것이 아니라 값(value)을 참조합니다.
질문2
Oracle JVM 공식문서 - constant pool 에서 Integer 타입을 저장할 때 unsigned 32bit 크기의 bytes는 int 상수 값을 빅 엔디안(big endian)방식으로 저장한다고 합니다. 그렇다면 constant pool 에는 2진수로 표현된 정수 '값'이 저장되는 게 맞는 것인가요?
답변
constant pool에서 정수형 상수는 2진수로 표현된 값(value)이 저장됩니다. 저장된 값(value)은 JVM이 실행되는 플랫폼의 엔디안 방식에 맞추어 빅 엔디안(big endian) 또는 리틀 엔디안(little endian)으로 저장됩니다.
질문3
constant pool 에 2진수로 표현된 값이 저장된다고 한다면, 이를 참조하는 오퍼랜드 스택은 constant pool의 값을 복사해서 연산을 수행하는 걸까요? 주소를 참조해서 연산을 수행하는 걸까요?
답변
constant pool의 값(value)을 참조하는 오퍼랜드 스택은 해당하는 값(value)을 복사해서 연산을 수행합니다. 즉, 주소 값을 참조해서 연산을 수행하는 것이 아니라 값(value)을 복사해서 연산을 수행합니다. 이는 자바의 원시타입(primitive type)이 값(value)을 참조하고, 객체 타입(object type)은 주소 값을 참조하는 특성 때문입니다.
아래는 내 접근방식이 잘못되었다는 걸 깨닫게해주신 분의 답변이다.
아, 뭔가 굉장히 복잡하고 난해하네요. ㅎ 전 오래된 사람이 c/c++ 를 먼저하고 나중에 자바를 접했는데요, c++ 에서 보면,
int a = 1; // a 에는 값도 있고 주소도 있습니다. 특정주소에 1이라는 값이 저장되어 있는거죠.
struct abc abc = {1,2,3}; // abc 역시 마찬가지로 값도 있고 주소도 있습니다.
vector v; // 이 역시 마찬가지입니다.
그런데, vector *pv = new vector();
이렇게 하면 pv 역시 변수 이므로 주소도 있고 값이 있습니다.
여기에 있는 값은 new 로 할당한 주소가 되겠죠.
자바로 넘어와서 보면,
char, int, ... 이런 것들을 전부 primitive type 이라 하고, 전부 값 입니다.
물론 c++ 처럼 주소도 있겠지만 자바에서는 이 주소를 다루지 않습니다.
그리고, 클래스는 전부 new 할당을 합니다.
c++ 처럼 new 할당하지 않더라도 쓸 수 있는 방법을 제공하지 않습니다.
Map<String,Object> map = new Map<String,Object>();
map 역시 변수 이므로 주소와 값이 있는데, 여기서 값은 new 할당한 주소가 되는거죠.
그러니까, 자바에서는 프리미티브 변수에는 값이 저장되어 있고,
객체 변수는 new 할당한 주소가 저장되어 있습니다.
물론 모든 변수는 나름의 특정 주소를 가지고 있습니다만,
자바는 c++ 처럼 이 주소를 다루지 않는거죠.
그래서, 자바가 c++ 보다 좀 더 쉽게 접근할 수 있습니다.
저 주소를 다루게 되면 골치아프거든요. ㅎ
글이 길어졌네요. 더 헷갈리게 한 건 아닌지 모르겠습니다. :)
첨언)
원시타입의 변수 뿐만 아니라 모든 변수는 주소값을 저장하고 있는게 아니라 주소를 가르키고 있는 겁니다.
예를 들어 보겠습니다.
int a = 1; // 스택에 1이라는 값을 푸시합니다. 그리고, 1이 저장된 스택 내 주소가 있을 겁니다. 1000번지라 하겠습니다.
int b = 2; // 스택에 2라는 값을 푸시합니다. 1008번지라 하겠습니다.
// 여기서 1,2 를 어디에서 가져왔느냐? 이건 중요하지 않습니다. 그리고, 상수풀에 있는 값을 가져왔다 했으니, 나름 주소도 있을 겁니다.
// 그러나, 그 주소 역시 관심사가 아니고 알 필요도 다룰 이유도 없습니다. 이건 c++ 이든 자바든 다 마찬가지 입니다.
// 중요한건 a 는 1000번지를 가르키고 있고, 그 안에는 1이라는 값이 있다는 겁니다. b 역시 마찬가지겠죠.
// 이게 왜 중요하냐면, swap이라는 함수를 만든다 해보겠습니다. a와 b값을 교체하는거죠.
swap(a,b); print(a,b);
// 이렇게 하면 a와 b 값이 교체되어 2,1 이라는 값을 출력할까요? 안 됩니다.
// a에 있는 1이란 값과 b에 있는 2라는 값을 복사해서 함수의 인자로 넘기니 swap 함수안에서 별짓을 다해도
// 호출 한 쪽의 a, b 값은 변경되지 않습니다. call by value 라고 하죠.
여기서 c++ 과 자바의 차이점이 생깁니다.
c++ 에서는 swap(&a, &b) 이런식으로 a,b 의 값이 아니라 a,b 의 주소를 넘길 수 있습니다.
그럼 swap 함수는 저 주소 안의 값을 변경함으로서 a,b 의 값을 교체할 수 있습니다.
자바는 어떻습니까? c++ 처럼 주소를 다룰 수가 없습니다.
그래서, 자바에서 원시타입변수는 swap 함수를 만들 방법이 없습니다.
그럼, 이런 식으로 하면 어떨까요? 교체가 가능할까요?
Integer a=1;
Integer b=1;
swap(a,b) {tmp=a; a=b; b=tmp; }
이 역시 교체가 안 됩니다.
교체를 하려면, 이런 식으로 해야 합니다.
swap(a,b) { tmp = a.getValue(); a.setValue(b.getValue()); b.setValue(tmp); }
그니까, a 와 b 를 교체하는게 아니라 그 안에 있는 값을 변경해야하는거죠.
근데, 자바에서 이것도 안됩니다. 윈시타입의 래퍼 클래스는 불변 객체니 값을 변경할 수 없습니다.
이런 식으로 클래스를 별도로 정의하면 교체할 수 있겠죠.
MyInteger a=new MyInteger(1);
MyInteger b=new MyInteger(2);
'의문과 실험' 카테고리의 다른 글
[Java] 싱글톤과 Static은 뭐가 다를까? (0) | 2023.07.03 |
---|---|
[JAVA] Vector는 Thread-Safe 한가? (0) | 2023.04.21 |
[JAVA] 자바는 Call by Reference 지원 안해. 참조변수를 넘기는 경우는 뭘까? (0) | 2023.04.04 |
[JAVA] 오라클 공식 문서에도 없는 String pool은 도대체 무엇인가? (0) | 2023.04.03 |
[JAVA] int num = 1; num은 어떻게 '값'을 가지며, 비교될까 (0) | 2023.03.27 |