자바에서 사용하는 메모리 모델의 구조를 이해하면 자바 프로그래밍에 큰 도움이 됩니다. 그러나 모든 것을 자세히 알 필요는 없습니다. 우리가 공부한 것과 연관해서 필요한 개념만 조금 이해하면 됩니다. 자, 그럼 자바에서 사용하는 메모리 모델의 구조를 알아봅니다.
10.1 자바의 메모리 모델
java.exe가 실행되면서 자바 가상 머신이 만들어집니다. 이때 자바는 메모리 공간의 효율성을 높이기 위해 메모리 공간을 여러 영역으로 나누게 됩니다. 자세한 내용은 자바 기술 문서에서 확인할 수 있습니다.01 너무 자세한 범위까지 알 필요는 없으므로 우리에게 꼭 필요한 개념까지만 알아보겠습니다.02
02 JAVA 11 기준
자바 프로그램은 실행되면서 확보한 메모리 영역을 크게 메서드 영역, 스택 영역, 힙 영역으로 구분하여 사용합니다.
메서드 영역
프로그램 실행에 대한 코드, 스태틱static 변수 및 메서드, 런타임 상수 풀runtime constant pool이 메서드 영역method area에 생성됩니다. 이 영역에 저장된 내용은 프로그램 시작 전에 로드되고 프로그램 종료 시 소멸됩니다. 런타임 상수 풀에는 컴파일 타임에 알려진 숫자 리터럴03부터 런타임에 확인되어야 하는 메서드 및 필드 참조에 이르기까지 여러 종류의 상수가 포함됩니다.
03 편집자 주_ 리터럴(literal)은 컴퓨터 과학 분야에서 고정된 값을 의미한다. 상수는 변하지 않는 변수를, 리터럴은 변하지 않는 값(데이터)를 말한다.
스택 영역
메서드가 호출되면 지역 변수, 매개변수가 프레임 형태로 생성되어 스택 영역stack area으로 쌓였다가 사라집니다. 이때 프레임 형태는 불투명한 빈 박스box 형태로 이해하면 됩니다. 하나의 박스 안에서는 다른 박스 안의 내용을 알 수 없습니다. 그러므로 프레임 영역을 벗어난 다른 메서드의 변수들은 서로 참조할 수 없습니다. 앞서 변수의 사용 가능 범위에서 배운 내용이 프레임 형태로 적용됩니다.
힙 영역
클래스의 객체(인스턴스), 배열이 new 연산자에 의해 힙 영역heap area에 동적으로 생성됩니다.
생성된 객체는 자동 저장소 관리 시스템인 가비지 컬렉터garbage collector에 의해 사용이 없음이 확인되면 자동으로 제거됩니다. C/C++와 달리 자바는 이렇게 자동으로 메모리를 관리합니다.
10.2 디버깅하며 배우는 스택 영역 원리
매개변수와 지역 변수가 스택 영역에서 어떻게 처리되는지 예제로 알아보겠습니다.
To Do 01 먼저 프로젝트를 다음과 같이 만들고 클래스 파일도 만들어줍니다.
To Do 02 코드를 다음과 같이 Ex01_MemoryInStack.java에 작성합니다.
public class Ex01_MemoryInStack { public static void main(String[] args) { int num1 = 10; int num2 = 20; adder(num1, num2); System.out.println("-------------------"); } public static int adder(int n1, int n2) { int result = n1 + n2; return result; } }
스택 영역에서 변수가 어떻게 처리되는지 확인하려면 디버깅 모드로 들어가야 합니다. 디버깅 모드에서 정지를 하려면 브레이크포인트가 필요합니다. 브레이크포인트를 설정해봅시다.
To Do 03 이클립스에서 ❶ 코드의 3번 라인 왼쪽 부분을 우클릭합니다. 그리고 ❷ 팝업 메뉴가 뜨면 를 선택합니다.
TIP Ex01_MemoryInStack.java 편집기 창에서 main() 메서드가 아니고 adder() 메서드에 커서를 미리 위치시켜놓으면 라인 번호 앞의 파란색 부분이 아래쪽에 표시되므로 이 위치에 브레이크포인트 표시가 추가될 때 좀 더 쉽게 확인이 됩니다.
제대로 선택이 되었다면 다음 그림과 같이 표식(파란색 점)이 보이게 됩니다.
브레이크포인트가 잘 지정되었으니 디버그 모드로 실행을 해서 스택 영역에서 변수가 어떻게 처리되는지를 확인하겠습니다.
To Do 04 디버그 모드로 프로그램을 실행하기 위해 다음 순서로 진행합니다. ❶ Ex01_MemoryInStack.java 선택 → 메뉴에서 ❷ → ❸ → ❹ 선택.
To Do 05 다음과 같은 보안 경고 창이 뜨면 을 클릭해줍니다(안 뜰 수도 있습니다).
To Do 06 현재 이클립스의 퍼스펙티브가 자바 모드인데 디버그 모드로 바꾸겠냐고 물어봅니다. 버튼을 클릭해 퍼스펙티브를 디버그 모드로 변경해줍니다.
이클립스의 창 구성이 다음과 같이 변하면서 디버그 모드로 변환됩니다.
프로그램은 우리가 브레이크포인트를 설정한 3번 라인까지 실행되고 5번 라인에서 멈춰 있습니다. 아직 5번 라인이 실행된 것이 아닙니다.
왼쪽 ❶ Ex01_MemoryInStack.main() 메서드가 호출되면서 main() 메서드에 관한 프레임이 스택 영역에 생성되었습니다. 우측 창의 ❷ 탭은 이 프레임만의 스택 영역 상황을 표시해줍니다(여기서 하나의 프레임은 그냥 하나의 빈 박스 정도로 이해하면 됩니다).
탭을 보면 정리하면 main() 메서드가 실행되면서 main() 메서드에서 사용될 매개변수와 지역 변수를 처리할 프레임이 스택 영역에 생성되고 ❸ 매개변수 args가 프레임 안에 생성된 겁니다.
프로그램의 진행은 다음 메뉴 아이콘이나 기능키 F5, F6을 눌러서 할 수 있습니다.
F5, F6 명령은 일반적인 코드 진행은 똑같고 메서드를 만났을 때 처리가 다릅니다.
• F5 Step Into : 메서드가 있으면 그 메서드 내부 코드로 진입합니다.
• F6 Step Over : 메서드를 만나면 메서드 내부 코드로 진입하지 않고 결과만 반환받아 실행한 뒤 다음 줄로 넘어갑니다.
• F6 Step Over : 메서드를 만나면 메서드 내부 코드로 진입하지 않고 결과만 반환받아 실행한 뒤 다음 줄로 넘어갑니다.
To Do 07 자, 그러면 F6 키를 한 번 눌러봅시다. 5번 라인이 실행되면서 num1 변수가 int형으로 만들어지고 10을 대입받습니다. 오른쪽 탭을 보면 num1 변수가 스택 영역에 생성되고 정수 10이 대입되어 있습니다.
To Do 08 F6 키를 또 한 번 누릅니다. 6번 라인이 실행되면서 num2 변수가 int형으로 만들어지고 20을 대입받습니다. 오른쪽 탭을 보면 num2 변수가 스택 영역에 생성되고 정수 20이 대입되어 있습니다.
To Do 09 여기서는 F5 키를 누릅니다. 메서드 안으로 들어가 실행해봐야 하기 때문입니다. 11번 라인이 실행되면서 좌측 탭을 보면 ❶ adder() 메서드에 대한 프레임이 스택에 쌓이게 됩니다. 그리고 ❷ 13번 라인을 실행하고자 대기합니다. 11번 라인이 실행되면서 ❸ 매개변수 n1과 n2가 스택 영역에 생성됩니다(오른쪽 스택을 보여주는 [Variables] 탭에서 확인할 수 있습니다).
이전 스택의 내용이 스택에서 없어진 것은 아닙니다. 이클립스의 디버그 모드는 현재 프레임의 스택 내용만을 보여주기 때문에 앞서 스택에 쌓여 있는 프레임 내용은 보이지 않는 것뿐입니다.
탭에서 선택해서 볼 수 있습니다.To Do 10 계속 진행을 위해 F6 키를 한 번 누릅니다. 13번 라인이 실행되면서 result 변수가 int형으로 만들어지고 더하기 연산의 결과 30을 대입받습니다. 오른쪽 탭을 보면 result 변수가 스택 영역에 생성되고 정수 30이 대입되어 있습니다.
To Do 11 F6 키를 한 번 누릅니다. return에 의해 adder() 메서드의 진행이 끝났기 때문에 ❶ adder() 메서드를 호출한 곳으로 실행 포인트가 다시 돌아와 있습니다. 그리고 ❷ 좌측 탭을 보면 Ex01_MemoryInStack.adder 프레임이 스택 영역에서 사라진 것을 볼 수 있습니다. 그리고 ❸ 우측 탭에서는 Ex01_MemoryInStack.main 프레임의 여태까지의 스택 상황을 다시 보여주고 있습니다.
To Do 12 F6 키를 한 번 누릅니다. 실행 포인트가 다음 줄로 이동합니다.
To Do 13 F6 키를 한 번 누릅니다. 내용을 출력하고 더 실행시킬 내용이 없기 때문에 실행 포인트가 main() 메서드의 마지막 중괄호까지 이동합니다.
To Do 14 F6 키를 한 번 누릅니다. main의 중괄호까지 실행이 끝났기 때문에 Ex01_Memory InStack.main 프레임이 스택 영역에서 제거됩니다. 이렇게 스택 영역에 아무것도 남아 있지 않게 되면 프로그램은 종료됩니다.
지금까지의 과정을 그림으로 그려 보면 다음과 같습니다.
앞에서 메서드 영역을 설명할 때 스태틱static 변수 및 스태틱 메서드가 메서드 영역에 저장되며, 프로그램 시작 전에 로드되고 프로그램 종료 시 소멸된다고 했습니다. 이 말을 상기하면서 그림을 살펴봐주세요. 코드가 메서드 영역에 다 로딩이 되면 그중에서 스태틱으로 지정된 변수와 메서드를 찾아 메서드 영역 내 스태틱 영역으로 옮겨줍니다.
그래서 main() 메서드는 static 지시자가 붙어 있기 때문에 스태틱 영역에 위치하게 됩니다. 이때 스태틱 변수가 있다면 값이 여기서 대입됩니다. 아직 프로그램이 시작하기 전입니다.
그리고 JVM은 무조건 메서드 영역 내 스태틱 영역에서 main() 메서드를 첫 메서드로 실행시킵니다. 만약 스태틱 영역에 옮겨진 main()이 없다면 프로그램은 실행되지 않습니다. 그래서 여러분이 실행시키려고 ❶ JVM에 전달한 클래스에는 main() 메서드가 반드시 있어야 하고 public으로 접근 가능해야 합니다. 그래야 자바 프로그램이 실행됩니다.
왜 모든 프로그램에서 저 부분에 ❷ static 지시자가 쓰였는지, 왜 메서드 이름이 ❸ main인지, 앞에 ❶ public은 왜 붙어 있는지 이제 이해되시겠죠?
스태틱은 별것 없습니다. 자바의 메모리 모델 구조가 이렇게 만들어졌기에 단지 먼저 추려내져야 하는 변수와 메서드가 있다면 static 지시자 표시를 하여 메모리의 특정 영역에 따로, 그리고 미리 로딩시켜놓은 것뿐입니다. 그리고 이 영역의 변수 및 메서드는 어떤 객체에서도 접근해서 사용할 수 있습니다. 그렇기에 스태틱 변수를 전역 변수라고 부르기도 합니다. 스태틱의 동작 원리에 대해서는 이후에 예제를 살펴보며 더 자세히 알아보겠습니다.
여기서는 프로그램이 진행됨에 따라 스택 영역에 메서드의 프레임이 쌓이고 그 프레임 안에서 지역 변수들이 생겨났다 사라졌다 하다가 모든 메서드의 프레임이 스택 영역에서 사라지면 프로그램이 종료된다는 것만 이해하면 됩니다.
To Do 15 퍼스펙티브 창에서 자바를 선택하거나 다음 그림처럼 자바 퍼스펙티브 아이콘을 선택해서 이클립스를 디버그 모드에서 다시 자바 모드로 바꾸어줍니다.
10.3 디버깅하며 배우는 힙 영역 원리
이번에는 객체 변수가 스택 영역과 힙 영역에서 어떻게 처리되는지를 알아보겠습니다.
To Do 01 먼저 프로젝트에 다음과 같이 Book.java와 Ex02_MemoryInHeap1.java 클래스 파일을 추가해줍니다.
Book 클래스를 만들 때는 다음 그림과 같이 main()을 만들어주는 옵션의 체크를 해지하고, Ex02_MemoryInHeap1 클래스는 main을 만들어주는 옵션을 체크(선택)합니다.
To Do 02 두 클래스 코드는 각각 다음과 같이 작성해주세요.
public class Book { int num; }
public class Ex02_MemoryInHeap1 { public static void main(String[] args) { Book book1 = new Book(); Book book2 = new Book(); book1.num = 10; book2.num = 20; System.out.println(book1.num); System.out.println(book2.num); book1 = null; book2 = null; } }
이전에는 .java 파일 하나에 여러 클래스를 만들었는데, 이번 예제에서는 각각의 .java 파일로 클래스를 만들었습니다.
To Do 03 이제 디버그 모드로 진입해볼 것입니다. 이클립스에서 Ex02_MemoryInHeap1.java 코드의 ❶ 3번 라인 왼쪽 부분을 우클릭합니다. ❷ 팝업 메뉴가 뜨면 를 선택합니다. 그러면 ❸ 3번 라인에 표식(파란색 점)이 나타납니다. 실행을 하다 이 표시를 만나면 여기서 멈추고 기다리란 의미입니다.
To Do 04 디버그 모드 진입은 ❶ Ex02_MemoryInHeap1.java 선택 → 메뉴에서 ❷ → ❸ → ❹ 을 순서대로 눌러 진입합니다.
To Do 05 현재 이클립스의 퍼스펙티브가 자바 모드인데 디버그 모드로 바꾸겠냐고 물어봅니다. 앞선 실습과 같이 버튼을 클릭해 퍼스펙티브 모드를 디버그 모드로 변경해줍니다.
이클립스의 창 구성이 디버그 모드로 변환됩니다. 프로그램은 우리가 브레이크포인트를 설정한 3번 라인까지 실행되고 5번 라인에서 멈춰 있습니다. 아직 5번 라인이 실행된 것이 아닙니다.
왼쪽 ❶ Ex02_MemoryInHeap1.main() 메서드가 호출되면서 main() 메서드의 프레임이 스택 영역에 생성되었습니다. 우측 창의 ❷ 탭은 이 프레임만의 스택 영역 상황을 보여줍니다. 그래서 지금 보면 main() 메서드의 ❸ 매개변수 args가 프레임 안에 생성된 것을 볼 수 있습니다.
탭을 보면 To Do 06 여기서 F6 키를 한 번 누릅니다. 코드에서는 객체를 만들어 Book 클래스형 book1 변수에 대입해주고 있습니다. 실제로는 어떻게 되는지 확인해보겠습니다.
5번 라인이 실행되면서 수행되는 동작들이 꽤 많습니다. 먼저 new 연산자와 Book() 생성자를 이용하여 객체를 힙 영역에 만듭니다(이클립스는 힙 영역을 스택 영역처럼 보여주지 않습니다). 그리고 객체를 참조할 수 있게 객체를 관리하는 내부적인 표에 자기 위치를 등록시켜줍니다.
예를 들면 다음과 같습니다.
생성되는 객체 크기는 클래스마다 천차만별이라 힙 영역에 동적으로 만들어 저장합니다. 스택 영역에는 기본 자료형처럼 정해진 크기의 값들만 저장하게 됩니다.
객체는 만들어졌고 book1 변수는 스택 영역에 만들어집니다. 그리고 값으로는 크기가 천차만별인 객체를 직접 대입받지 않고 그 객체를 참조할 수 있는 id값을 대입받습니다(그래서 참조 변수라고도 부릅니다). id값은 정해진 크기의 값이기 때문에 스택 영역에 변수의 값으로 대입할 수 있습니다.
To Do 07 탭에서 book1을 클릭하여 펼치면 객체 안의 클래스 멤버 변수인 num은 따로 값을 넣지 않았지만 0으로 초기화되어 있습니다.
To Do 08 F6 키를 다시 한 번 누릅니다. 같은 방식으로 Book 클래스형의 객체가 힙 영역에 만들어지고 스택 영역에 만들어진 변수 book2가 참조할 수 있도록 참조 값을 대입시켜줍니다.
To Do 09 탭에서 book2를 클릭하여 펼치면 객체 안의 클래스 멤버 변수인 num은 따로 값을 넣지 않았지만 0으로 초기화되어 있습니다.
여기서 스택 영역의 main 프레임 안에 생성된 변수들을 확인하면 book1과 book2가 참조하는 id값이 다릅니다. 이를 통해 힙 영역의 서로 다른 객체를 참조하고 있다는 것을 확인할 수 있습니다.
To Do 10 F6 키를 다시 한 번 누릅니다. 8라인이 실행되면서 book1의 멤버 변수에 값이 대입됩니다.
To Do 11 F6 키를 다시 한 번 누릅니다. 9라인이 실행되면서 book2의 멤버 변수에 값이 대입되고 있습니다.
To Do 12 F6 키를 다시 한 번 누릅니다. 11라인이 실행되면서 book1의 멤버 변수의 값을 출력합니다. 힙 영역에 만들어져 있는 객체의 멤버 변수에 값을 잘 대입하고 잘 가져오네요.
To Do 13 F6 키를 다시 한 번 누릅니다. 12라인이 실행되면서 book2의 멤버 변수의 값을 출력합니다. 힙 영역에 만들어져 있는 객체의 멤버 변수에 값을 잘 대입하고 잘 가져옵니다.
To Do 14 F6 키를 다시 한 번 누릅니다. 14라인이 실행되면서 book1에 null을 대입합니다. 이렇게 null을 대입하는 것을 참조를 끊는다고 표현합니다. 스택 영역의 변수와 힙 영역의 객체 간에 참조 관계를 끊는 겁니다.
To Do 15 F6 키를 다시 한 번 누릅니다. 15라인이 실행되면서 book2에 null을 대입하고 참조 관계를 끊습니다.
이렇게 참조를 끊을 때 힙 영역의 객체가 바로 사라지는 것은 아닙니다. 스택 영역에 있는 변수에 더는 힙 영역의 객체를 참조하지 않는다고 표시만 한 겁니다.
가비지 컬렉터가 메모리 관리를 위해 수행될 때 힙의 객체를 발견하고 이 객체를 사용하는 스택 영역의 변수를 찾아보고 아무것도 참조하는 것이 없다고 결론이 나면 그때 제거를 하기 위해 자동으로 제거 표시를 해줍니다(제거가 아니고 제거 표시입니다).04
04 개념상 동작입니다. 실제로는 반대로 Mark and Sweep 동작을 합니다.
가비지 컬렉션
가비지 컬렉션garbage collection이 수행되는 동안에는 모든 스레드가 멈추게 됩니다. 그래서 가비지 컬렉션은 시스템의 성능에 영향을 미치지 않도록 별도의 알고리즘으로 계산되어 실행됩니다. 그러므로 가비지 컬렉션은 한 번도 발생하지 않을 수 있습니다. 가비지 컬렉션을 강제로 발생시키는 데 다음 코드를 사용합니다.
System.gc()
가비지 컬렉션이 발생하면, 소멸 대상이 되는 인스턴스는 결정되지만 곧바로 실제 소멸로 이어지지는 않습니다. 그리고 인스턴스의 실제 소멸로 이어지지 않은 상태에서도 프로그램 이 종료될 수 있습니다. 종료가 되면 어차피 객체는 운영체제에 의해 소멸됩니다.
따라서 반드시 객체의 소멸을 이끄는 finalize() 메서드가 반드시 호출되기 원한다면 아래 코드를 추가로 삽입해야 합니다.
System.runFinalization();
가비지 컬렉션의 단점으로는 프로그래머가 객체가 필요 없어지는 시점을 알더라도 메모리 에서 직접 해제할 수 없고, 가비지 컬렉션의 알고리즘이 메모리 해제 시점을 계속 추적하고 판단하게 되므로 추가적인 시스템 비용이 발생하게 된다는 겁니다.
가비지 컬렉션이 실행되는 시각이나 수행 시간을 알 수 없습니다. 이런 점은 실시간 시스템 에서 프로그램이 예측 불가능하게 잠시라도 정지할 수 있어 매우 위험합니다.
그래서 위의 메서드들은 가급적 호출하지 않는 프로그램을 작성해야 합니다.
To Do 16 F6 키를 다시 한 번 누릅니다. main() 메서드의 중괄호까지 다 실행하고 스택 영역의 모든 프레임이 사라지면서 프로그램이 종료됩니다. 그러면 이 프로그램에서 만든 힙 영역의 모든 객체는 가비지 컬렉터가 제거하지 않아도 자동으로 제거됩니다.
To Do 17 퍼스펙티브 창에서 자바를 선택하거나 다음 그림처럼 자바 퍼스펙티브 아이콘을 선택해서 이클립스를 디버그 모드에서 다시 자바 모드로 바꾸어줍니다.
10.4 디버깅하며 배우는 힙 영역 객체 참조
이번에는 힙 영역에 생성된 객체가 어떻게 참조되는지를 알아보겠습니다.
To Do 01 프로젝트에 Ex03_MemoryInHeap2.java 클래스 파일을 추가해줍니다.
public class Ex03_MemoryInHeap2 { public static void main(String[] args) { Book book1 = new Book(); // ❶ book1.num = 10; Book book2 = book1; // ❷ System.out.println("book1.num : " + book1.num); System.out.println("book2.num : " + book2.num); System.out.println("------------------------------"); book2.num = 20; System.out.println("book1.num : " + book1.num); System.out.println("book2.num : " + book2.num); } }
앞서 만들어놓은 Book 클래스는 이번에도 사용합니다.
❶ 객체를 생성해서 book1 변수에 대입해주었습니다. ❷ 그리고 그 변수를 이용해서 새로운 참조 변수 book2에 대입해주었습니다. 이럴 경우 객체가 새로 만들어지는지, 기존 객체를 다시 사용하는지 디버그 모드를 실행해서 살펴보겠습니다.
To Do 03 이번에도 main() 메서드가 시작하는 위치에 브레이크포인트를 만들어줍니다.
To Do 04 디버그 모드를 실행합니다. ❶ Ex03_MemoryInHeap2.java 선택 → 메뉴에서 ❷ → ❸ → ❹ 선택합니다.
To Do 05 버튼을 클릭해 퍼스펙티브를 디버그 모드로 변경해줍니다.
이클립스의 창 구성이 디버그 모드로 변환됩니다.
프로그램은 우리가 브레이크포인트를 설정한 3번 라인까지 실행되고 5번 라인에서 멈춰 있습니다. 아직 5번 라인이 실행된 것이 아닙니다.
왼쪽 ❶ Ex03_MemoryInHeap2.main() 메서드가 호출되면서 main() 메서드의 프레임이 스택 영역에 생성되었습니다. 우측 창의 ❷ 탭에는 main() 메서드의 ❸ 매개변수 args가 프레임 안에 생성된 것을 볼 수 있습니다.
탭을 보면 To Do 06 여기서 F6 키를 한 번 누릅니다. 5번 라인이 실행되면 new 연산자와 Book() 생성자를 이용하여 객체를 힙 영역에 만들고, 객체를 참조할 수 있게 객체를 관리하는 id값을 Book 클래스형의 book1 변수에 대입해줍니다.
To Do 07 F6 키를 다시 한 번 누릅니다. 6라인이 실행되면서 book1의 멤버 변수에 값이 대입되고 있습니다.
To Do 08 F6 키를 다시 한 번 누릅니다. 8라인이 실행되면서 Book 클래스형의 book2 변수를 만들고 기존 book1의 값을 대입합니다. book1의 id값은 힙 영역에 만들어진 객체가 메모리의 어느 위치에 있다는 것을 가리키고 있는 참조 값입니다.
book2에 book1을 대입한 결과를 스택에서 살펴보면 book2에도 book1이 참조하고 있던 기존 객체의 id값이 똑같이 들어와 있습니다.
즉 대입 연산은 연산의 결과, 상수 또는 기존 변수의 값을 새로운 변수의 값으로 넣어주는 것이므로, 지금도 book2 변수에 book1 변수의 값이 대입이 되고 그 값은 기존 객체를 참조할 수 있는 id값입니다.
결과적으로 힙 영역에 객체를 새로 만드는 것이 아니고, 기존 객체를 가리키는 변수만 하나 더 늘어난 겁니다.
TIP 이렇게 하나의 객체를 여러 변수가 참조할 수 있습니다. 가비지 컬렉터는 이런 참조가 하나도 없는 힙 영역의 객체를 제거 대상으로 삼습니다.
To Do 09 F6 키를 눌러 10라인과 11라인을 진행하면 변수명은 다르지만 실제 같은 객체를 가리키고 있으므로 객체 안의 num 멤버 변수의 값을 출력한 결과는 같습니다.
To Do 10 F6 키를 다시 한 번 누릅니다. 14라인이 실행되면서 book2의 멤버 변수 num의 값을 새로운 값으로 대입합니다.
스택 창에서 확인할 수 있듯이 book1, book2 두 변수가 가리키는 객체의 멤버 변수 num의 값이 동시에 변경되고 있습니다. 같은 객체를 가리키고 있기 때문입니다.
To Do 11 F6 키를 계속 눌러 출력해 보면 변경된 값이 같게 출력되는 것을 확인할 수 있습니다. 중괄호 끝까지 진행해서 프로그램을 종료시킵니다.
To Do 12 퍼스펙티브 창에서 자바를 선택하거나 다음 그림처럼 자바 퍼스펙티브 아이콘을 선택해서 이클립스를 디버그 모드에서 다시 자바 모드로 바꾸어줍니다.
학습 마무리
여기까지 자바 프로그래밍에서 사용하는 메모리 모델의 구조를 알아보았습니다.
핵심 요약
1 자바 프로그램이 실행되면서 확보한 메모리 영역을 크게 메서드 영역, 스택 영역, 힙 영역으로 구분하여 사용합니다.
2 메서드 영역에는 프로그램 실행에 대한 코드, 스태틱 변수 및 메서드, 런타임 상수 풀이 생성됩니다.
3 런타임 상수 풀에는 컴파일 타임에 알려진 숫자 리터럴부터 런타임에 확인되어야 하는 메서드 및 필드 참조에 이르기까지 여러 종류의 상수가 포함됩니다.
4 메서드가 호출되면 지역 변수, 매개변수가 프레임 형태로 생성되어 스택 영역으로 쌓였다가 사라집니다.
5 클래스의 객체(인스턴스), 배열이 new 연산자에 의해 힙 영역에 동적으로 생성됩니다.
6 생성된 객체는 자동 저장소 관리 시스템인 가비지 컬렉터에 의해 사용이 없음이 확인되면 자동으로 제거됩니다.
이재환
27년차 개발자로 최근 12여 년간 여러 교육 기관에서 자바와 모바일 앱 개발, 빅데이터 강의를 겸하고 있습니다. 인프런에서 〈예제로 배우는 스프링 부트 입문〉, 〈디자인 패턴 with JAVA(GoF)〉, 〈자바 : 클래스의 이해와 객체지향 프로그래밍〉 등의 온라인 강의도 진행합니다. 저서로는 《예제로 배우는 스프링 부트 입문》(비제이퍼블릭, 2020), 《핵심강좌! 유니티》(유페이퍼, 2018), 《핵심강좌! Cocos2d-X》(유페이퍼, 2017)가 있습니다.