자바에서 변수관리를 어떻게 관리할까? 라는 의문점으로 공부한 것을 정리한 글입니다.
스택은 원시타입(primitive type)의 키값을 저장하거나, 참조타입(Reference type)의 키를 담당하고 있습니다. 원시타입이란 boolean, char, byte, short, int, long, float, double 을 의미합니다. 예를 들어 다음과 같은 코드가 있다고 가정해봅시다.
int num = 1;
boolean isTrue = false;
float pie = 3.14;
이렇게 변수를 선언하면 스택에 차근차근 쌓이게 됩니다.
가시성이란 무언가를 보는 것입니다. 우리가 처음 프로그래밍을 배울때 지역 함수에 대해 배웁니다.
지역 함수로 선언된 변수는 다른 범위로 넘어가면 사용하지 못하는데 그 이유가 가시성입니다. 다음과 같은 코드가 있습니다.
public void visibility() {
int num = 1;
inside();
}
public void inside() {
System.out.println(num); //error
}
visibility 함수에 선언된 num은 inside 에서 위 그림과 같이 num 이 보이지 않게되어 inside 함수에서 사용할 수 없습니다.
위의 말대로 하나의 Thread 당 하나의 STACK 을 가집니다. 하나의 Thread 가 새롭게 생성되는 순간 해당 스레드의 위한 stack 도 함께 생성되며, 각 스레드에서 다른 스레드의 stack 영역에 접근할 수 없습니다.
primitive type 같은 경우는 값을 가지지만 new 를 통해 생성한 객체는 Heap에 값을 저장하고 Heap 에서 주는 참조값을 stack 에 저장합니다.
String s = "hello world";
우리가 데이터를 생성할 때 new ArrayList 같이 new 를 통해 생성된 것들은 전부다 Heap 에 저장을 합니다. 위의 STACK 의 4번을 참조해주세요.
Stack은 하나의 Thread 가 생성될때 마다 하나씩 생성하는 반면 Heap 은 하나만 존재하고 여러 Thread 에서 공유해서 사용합니다.
우리가 new 를 통해 데이터를 생성하면 Heap에서 저장할 공간이 있는지 먼저 확인합니다. 확인 한 후에 공간이 충분하면 Heap 에 데이터를 생성하고, 스택과 연결합니다.
Heap을 좀 더 이해하기 위해서 심화과정으로 들어가봅시다.
만약 우리가 ArrayList를 생성하고 싶다면 다음과 같이 new ArrayList
라고 선언을 할 것입니다. 그렇게 되면 heap 에서는 arrayList 만들 공간을 먼저 찾고 충분하다면 생성해줄 것입니다.
이후 우리는 List에 데이터를 하나 추가하게 된다면 stack 에 생성되지 않고 Heap 에 생성되게 됩니다.
그리고 나서 만약 우리가 다음과 같은 코드로 복사를 하게 된다면 다른 변수는 값을 받는게 아닌 참조값을 받게 됩니다.
public test() {
List<Integer> arr = new ArrayList<>();
arr.add("hello world");
List<Integer> list = arr;
}
결국 list.get(0)
을 하게 되면 "hello world" 라는 값을 얻게 됩니다.
또한 list 가 값을 변경하면 arr도 같이 변경되게 됩니다.
List<Integer> arr = new ArrayList<>();
arr.add("hello world");
List<Integer> list = arr;
list.add("java");
System.out.println(arr.get(1)); //"java"
여기까지 이해했다면 다음과 같은 코드를 보면서 어떻게 진행이 될지 먼저 생각해봅시다.
public print() {
Integer i = 1;
changeInt(i);
System.out.println(i); //?
}
public changeInt(Integer param) {
param += 10;
}
Integer는 Object 타입이므로 heap 에 저장되어 있고, 참조값을 넘겨줬으니
i += 10
을 했으니 11이 print 될거야
라고 누군가는 말할 수 있습니다. 하지만 확인해보면 print 된 값은 1입니다. 왜 이렇게 됐을까요?
비밀의 정답은 Integer, String, Double 등등은 Immutable Object 이기 때문입니다.
Immutable Object는 불변객체로써 값이 변하지 않는 특성을 가지고 있습니다.
String에 값을 더했을때 값이 변경되던데?
라고 하시는 분들도 계실겁니다. 하지만 값이 변경되는 것이 아니라 실제로는 메모리에 새로운 객체가 할당되는 것입니다.
자바에는 Wrapper class 에 해당 하는 Integer, String, Byte, Boolean, Long, Double, Float, Short, 클래스는 모두 Immutable(불변)입니다.
즉, 다음 그림과 같이 수행이 됩니다.
처음에는 같은 곳을 바라보다가 값을 더하게 되면서 다음과 같이 새롭게 데이터를 할당하게 됩니다.
Integer 는 다음과 같이 선언이 되어 있습니다.
private final int value;
public Integer(int value) {
this.value = value;
}
제가 공부한것에 따르면 final 은 값을 못바꾸게 하기위해 선언한 것이 아닌 상속을 제한하는 목적으로 사용했다고 합니다.
값을 못바꾸게 하기위한 것은 생성자에 의해서만 초기화되고 이후 값을 변경할수 없게 만들었기 때문에 불변객체라고 불립니다.
https://yaboong.github.io/java/2018/05/26/java-memory-management/