▩ 목 차 ▩
1. JVM이 자바 소스코드를 어떻게 OS에서 실행하는지의 과정 및 구조
1-1. JVM 구조
1-1-1. Class Loader
1-1-2. Excution Engine
1-1-3. Garbage Collector
1-1-4. Runtime Data Areas
1-2. JVM 정리
2. C언어) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 [ Code, Data, Heap, Stack ]
2-1. Code 영역
2-2. Data 영역
2-3. Stack 영역
2-4. Heap 영역
2-5. Stack과 Heap의 Overflow
3. 자바) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 Runtime Data Areas [ Method Area, Runtime Constant Pool, Heap Area, Statck Area, PC Register, Native Method Stack Area ]
3-1. Method Area(Class Area, Static Area)
3-2. Runtime Constant Pool
3-3. Heap Area(모든 Thread가 공유)
3-3-1. Heap Area 메모리 관리
3-4. Stack Area(모든 Thread가 공유)
3-5. PC Register
3-6. Native Method Stack Area
4. 자바) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 [ Stack, Heap 중점 ]
4-1. Stack(기본자료형 저장 및 참조자료형의 값 참조 가능)
4-2. Heap(참조자료형 값 저장->heap에 저장되어 있는 참조자료형 값을 stack에 참조할 수 있도록 해줌)
4-3. Stack과 Heap영역의 메모리 할당과 해제 예시
5. 자바) Garbage Collection
이번에 기본자료형과 참조자료형을 배우면서 값을 비교하는 "==" 연산자와 equals()메소드에 대해 배웠다.
기본자료형은 "=="비교를 할때 값 자체를 비교를 하고 참조자료형의 경우 equals()비교는 주소값을 비교한다고 배웠다.
어떻게 메모리에 어떻게 저장되어 값을 가져오는지 정확하게 알고 싶었고,
- 프로그램이 운영체제로부터 할당받는 메모리 공간을 알아보고,
- 메모리 영역을 공부하기 전 JVM이 자바 소스코드를 어떻게 OS에서 실행하는 과정을 알아보자.
- 그 후, 기본자료형과 참조자료형이 메모리 공간에 어떻게 저장되는지(Stack과 Heap 영역이 사용되는지) 알아보자.
[ 자바에서 JVM이 OS로부터의 할당만을 알고 싶은 사람은 2번 챕터[ C) 운영체제로부터의 할당받는 메모리 공간 ]는 건너뛰셔도 좋습니다. ]
■ 1. JVM이 자바 소스코드를 어떻게 OS에서 실행하는지의 과정 및 구조 ■
JVM은 자바 바이트코드를 실행시키는 가상머신으로서 자바가 플랫폼에 독립적으로 실행될 수 있게 한다.
- 자바 소스코드인 .java 파일을 컴파일러가 자바 바이트 코드인 .class로 변환한다.
- .class 코드를 JVM 클래스 로더에게 보낸다.
- 클래스 로더는 JVM Runtime Data Area으로 로딩하여 JVM의 메모리에 올린다. [ 즉, 메모리 공간을 할당 받는다. ]
■ 1-1. JVM 구조
JVM 구조에는 Class Loader, Excution Engine, Garbage Collector, Runtime Data Areas가 있다.
■ 1-1-1. Class Loader
컴파일러에 의해 바이트코드로 변환된 코드를 Runtime Data Areas에 클래스 단위로 로드 후 Link를 통해 적절히 배치시키는 작업
Class Loader로 인해 동적으로 클래스를 로드할 수 있다.
■ 1-1-2. Excution Engine
Runtime Data Areas에 배치된 바이트 코드를 실행 시키는 역할
메모리에 올라온 코드를 명령어 단위로 실행한다.
■ 1-1-3. Garbage Collector
어플리케이션이 생성한 객체의 생존여부를 판단하여 더 이상 사용되지 않는 객체의 메모리를 반환함으로써 메모리를 자동적으로 관리하는 역할
■ 1-1-4. Runtime Data Areas
운영체제로부터 할당받은 메모리를 관리하는 영역.
JVM에서 관리하는 메모리 영역은
- Method(Static or Class) Area
- Runtime Constant Pool
- Heap Area
- Stack Area
- PC Register
- Native Method Stack Area
■ 1-2. JVM 정리
간단하고 중요한 말을 하자면,
모든 자바 프로그램은 JVM을 통해 실행된다.
프로그램이 실행되면 JVM은 OS로부터 프로그램을 수행하는데 필요한 메모리를 할당 받는다.
==> 이 할당 받은 메모리를 가지고 JVM은 용도에 따라 구분해서 사용한다.
■ 2. C언어) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 [ Code, Data, Heap, Stack ] ■
프로그램이 실행되기 위해서는 운영체제(OS)가 프로그램의 정보를 메모리에서 불러와야 하고,
프로그램이 실행되는 동안 CPU가 코드를 처리하기 위해서는, 메모리가 명령어와 데이터들을 저장해야 한다.
프로그램을 실행하게 되면 운영체제(OS)로부터 할당받는 대표적인 메모리 공간(RAM)4가지
- Code 영역
- Data 영역
- Stack 영역
- Heap 영역
지금부터 여기서는 메모리 공간(RAM)에서의 각각의 영역에서 하는일은 무엇인지 배워보도록 한다.
■ 2-1. Code 영역
- 실행할 프로그램의 코드가 저장되는 영역으로, text영역이라고도 한다.
- 프로그램이 시작하고 끝날 때까지 메모리에 계속 남아있다.
- 컴파일된 기계어가 들어간다.
- CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리한다.
■ 2-2. Data 영역
- 프로그램의 전역변수(인스턴스변수)와 정적변수(static,클래스변수), 문자열 상수(""),배열(array),구조체(structure)가 저장되는 영역.
- 데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸한다.
- BSS와 GVAR 영역을 합쳐 Data 영역이라고 한다.
- GVAR[data] : 초기화가 된 데이터를 저장하기 위한 영역 (ROM에 저장) [ 초기화된 데이터는 값을 저장해야 하기 때문에 비휘발성인 ROM에 저장한다. ]
- BSS : 초기화가 되지 않은 데이터를 저장하기 위한 영역 (RAM에 저장)
[ 사실 bss도 초기화를 한다. 단, 값을 초기화 하는 것이 아니라, 메모리 상의 공간을 초기화 시키는 것이다. 그렇기 때문에 bss를 사용하는것이 메모리 공간적으로 보면 더 효율적이라 할 수 있다.] - 즉, GVAR 영역이 프로그램이 가동되기 이전에 초기화 하는 것이라면, bss 영역은 프로그램이 가동된 후 초기화 하는것이라고 생각하면 편하다.
■ 2-3. Stack 영역
- 프로그램이 자동으로 사용하는 임시 메모리 영역
- 함수의 호출과 관계되는 지역변수와 매개변수가 저장되는 영역.
- 스택 영역의 크기는 컴파일 시에 결정된다.
- 스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸한다.
- 이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다.
- 스택 영역은 후입 선출(LIFO, Last-In-First-Out)의 방식으로, 가장 나중에 들어온 데이터가 가장 먼저 인출된다.
==> 스택 영역이 메모리 높은 주소에서 낮은 주소의 방향으로 할당 되기 때문이다. - 스택 영역에서 푸시(phsh)로 데이터를 저장하고, 팝(pop)로 데이터를 인출한다.
- 장점 : 데이터 액세스가 빠른편이며, 변수를 명시적으로 할당 해제 할 필요가 없다. 또한 하나의 명령으로 메모리 조작과 어드레드 조작이 가능하다.
- 단점 : 스택의 크기 제한이 있어(os마다 다름) 한계를 초과하도록 삽입할 수 없다. 또한 변수의 크기를 조정할 수 없다.
■ 2-4. Heap 영역
- 사용자가 직접 관리할 수 있고, 관리해야만 하는 영역.
- 힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다.
- 힙 영역은 선입선출(FIFO, First-In-First-Out)의 방식으로, 가장 먼저 들어온 데이터가 가장 먼저 인출된다.
==> 힙 영역이 메모리의 낮은 주소에서 높은 주소의 방향으로 할당되기 때문이다. - 런타임 시에 크기가 결정된다.
- 장점 : 메모리 크기에 제한이 없다. 또한 프로그램에 필요한 개체의 개수나 크리글 미리 알 수 없는 경우에 사용 가능하다.
- 단점 : 데이터 액세스가 상대적으로 느린편으로, 메모리를 관리해야한다. 또한 할당 및 해제 작업으로 인한 속도 저하가 발행한다.
■ 2-5. Stack과 Heap의 Overflow
stack영역에서의 지역변수는 사용되고 소멸하기 때문에 데이터 용량의 불확실성을 가진다.
==> 따라서 stack 영역에서의 주소값은 밑에서부터 채워지며, 주소는 선언된 순서대로 정해진다.
반면에 heap영역에서의 주소값은 위에서부터 채워 내려가기 때문에, 두 메모리 영역의 주소가 겹치게 되는 오버플로우(overflow)가 발생할 수 있다.
이때 스택이 힙을 침범하는 경우를 스택 오버플로우라고 하고 힙이 스택을 침범하는 경우를 힙 오버 플로우라고 한다.
■ 3. 자바) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 Runtime Data Areas [ Method Area, Runtime Constant Pool, Heap Area, Statck Area, PC Register, Native Method Stack Area ] ■
■ 3-1. Method Area(Class Area, Static Area)
호출한 클래스와 인터페이스에 대한 Runtime Contatnt Pool, 전역변수(인스턴스변수)와 정적변수(static), 메소드 바이트 코드 등을 저장한다.
이 영역에 등록된 class만이 Heap 생성될 수 있다.
사실 Method Area는 논리적으로 Heap에 포함된다.
논리적으로 Heap의 일부분이지만, 일반적으로 가비지 컬렉션의 대상이 아니다.
■ 3-2. Runtime Constant Pool
Method Area 영역에 포함되는 공간이다. 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 reference를 저장한다.
■ 3-3. Heap Area(모든 Thread가 공유)
참조형(Reference Type)의 데이터 타입을 갖는 객체(인스턴스)와 배열 등은 Heap 영역에 데이터가 저장되며, GC의 주 대상이 된다.
==> 즉, "new"를 사용하여 객체를 만들때 저장이 된다.
이때 변수(객체, 객체변수, 참조변수)는 Stack 영역의 공간에서 Heap영역의 참조값(reference value, 메모리와 저장된 주소를 연결해주는 값인 해시코드)을 new 연산자를 통해 Return 받는다.
==> 힙의 참조 주소는 "스택"이 갖고 있고 해당 객체를 통해서만 힙 영역에 있는 인스턴스를 핸들링 가능하다.
[ 문자열을 저장하는 String도 참조형 타입의 변수이다. new 연산자를 이용해서 생성하면 데이터가 Heap영역에 저장되며, 같은 값이라도 각각 다른 영역에 저장된다. ]
참고로 Heap에 저장된 데이터가 더 이상 사용이 불필요하다면 메모리 관리를 위해 JVM에 의해 알아서 해제가 된다.
==> 해제됨에 따라 공간을 사용할 수 있도록 변경되며, 이러한 기능을 가비지 컬렉션(GC)라고 한다.
■ 3-3-1. Heap Area 메모리 관리
- Young Generation : 객체가 생성되자마자 저장되는 공간이다. 시간이 지날수록 우선순위가 낮아지며, Old영역으로 내려가게 된다. 이곳에서 객체가 사라지면 Minor GC가 발생한다.
- Old Generation : 오래된 객체가 저장되는 공간이다. 이곳에서 객체가 사라지면 Major GC가 발생한다.
- Permanent Generation : Class Loader에 의해 로드되는 클래스나 메소드에 대한 Meta 정보가 저장되는 영역이다. Reflection을 이용하여 동적으로 클래스를 로드하는 경우 자주 사용된다.
[ 자바의 Refelection은 JVM에서 실행되는 어플리케이션의 런타임 동작을 검사하거나 수정할 수 잇는 기능이 필요한 프로그램에서 사용된다. 쉽게 말하자면, 클래스의 구조를 개발자가 확인할 수 있고, 값을 가져오거나 메소드를 호출하는데 사용된다.
ex) Object.getClass() ]
■ 3-4. Stack Area(모든 Thread가 공유)
Stack 구조의 저장공간이다.
메소드 내에서 정의하는 기본 자료형( int, double, byte, long, short, float, boolean )에 해당되는 지역 변수의 데이터 값이 저장되는 공간으로, 임시적으로 사용되는 영역이다.
함수를 호출하면 push를 통해 Stack에 저장(메모리 할당)하고 함수 호출이 종료되면(메모리 해제) 다음 실행할 함수를 pop하여 함수를 실행한다.
스레드 별로 저장공간을 따로 생성하여 관리한다.
하나의 쓰레드는 내부적으로 static, stack, heap 영역을 갖게 된다.
==> 그렇기에 A쓰레드는 다른 쓰레드에 접근 할 수 없지만, static과 heap 영역을 공유하여 사용할 수 있다.
[EX] - Stack영역 값 할당 및 해제 , 후입 선출
public class StackAreaEx {
public static void main(String[] args) {
int a = 5;
a = 4;
a = 3;
a = 2;
System.out.println(a);
for(int i=0; i<5; i++){ }// System.out.println(i); 컴파일 에러
}
}
위 코드를 보게 되면 a라는 변수(지역함수)는 main 메소드가 호출될 때 Stack 영역에 할당되고 종료시에 해제된다.
a 라는 변수는 값이 5, 4, 3, 2 순으로 값을 할당 하였고, 최종적으로 출력되는 값은 2이다.
즉, Stack 영역은 후입 선출(LIFO, Last-In-First-Out)의 방식으로 변수에 새로운 데이터가 할당되면 이전 데이터는 지워진다는 것이다.
for문을 보게 되면 int i를 정의하였는데 for문이 종료된 다음 i를 출력하지 못하는 이유는 지역변수이므로 for문의 종료와 함께 Stack 영역에서 해제되었기 때문이다.
■ 3-5. PC Register
[ C언어의 메모리 구조 중 Code(프로그램 코드 영역)영역와 동일하다고 생각하면 편하다. ]
현재 수행중이거나 다음에 실행할 인스트럭션 주소를 저장한다.
연산 수행 중 발생하는 데이터를 레지스터에 저장하였다가 CPU가 필요할 때 가져다 쓴다.
스레드 별로 공간을 만들어 관리한다.
■ 3-6. Native Method Stack Area
[ Java 외의 언어로 작성된 네이티브 코드들을 위한 stack ]
자바가 접근할 수 없는 영역은 C와 같은 Low Level 언어로 작성되어 있다.
==> Java 외의 언어로 작성된 Native 코드를 실행시키면서 발생하는 데이터를 Stack 구조로 저장하기 위한 공간이다.
스레드 별로 생성된다.
■ 4. 자바) 프로그램이 실행하게 되면 운영체제로부터 할당받는 메모리 공간 Runtime Data Areas [ Stack, Heap 중점 ] ■
Java에서 내가 짠 코드들은 어떻게 메모리에 저장되고 실제 어떤 데이터들이 garbage로 분류되는지 알고 싶었다.
[ Stack과 heap 영역의 사용에 초점을 맞추었다. ]
■ 4-1. Stack(기본자료형 저장 및 참조자료형의 값 참조 가능)
위에서 말했듯이 간단하게 Stack에 대해 다시 말해보자면
- 함수의 호출과 관계되는 지역변수와 매개변수가 저장되는 영역.
- Heap 영역에 생성된 Object타입의 데이터의 참조값이 할당된다.
- 기본자료형의 데이터가 값과 함께 할당된다.
- 지역변수들은 scope[ 변수를 사용할 수 있는 범위 ]에 따른 visibility를 가진다.
- 각 Thread는 자신만의 stack을 가진다.
Stack 에는 heap 영역에 생성된 Object 타입의 데이터들에 대한 참조를 위한 값들(쉽게 말해서, heap에 저장된 값들을 Stack영역이 참조해 사용한다는말)이 할당된다.
또한 기본자료형[byte, short, int, long, double, float, boolean, char]타입의 데이터들이 할당된다.
==> 기본자료형의 데이터들에 대해서는 참조값을 저장하는 것이 아니라 실제 값을 Stack에 직접 저장하게 된다.
Stack 영역에 있는 변수들은 visibility(가시성)를 가진다. 변수 scope(변수를 사용할 수 있는 범위)에 대한 개념이다. 전역변수가 아닌 지역함수가 foo()라는 메소드내에서 Stack에 할당 된 경우, 해당 지역변수는 다른 함수에 접근할 수 없다.
예를들어, foo() 라는 메소드에서 bar()메소드를 호출하고 bar()메소드의 종료되는 중괄호가 }가 실행되는 경우 bar() 함수 내부에서 선언한 모든 지역변수들은 stack에서 pop 되어 사라진다.
Stack 메모리는 Thread 하나당 하나씩 할당된다. 즉, 스레드 하나가 새롭게 생성되는 순간 해당 스레드를 위한 Stack도 함께 생성되며, 각 스레드의 Stack영역에는 접근할 수 없다.
Stack의 예시를 보며 이해를 해보자.
[EX] - Stack
public class Main {
public static void main(String[] args) {
int argument = 4;
argument = someOperation(argument);
}
private static int someOperation(int param){
int tmp = param * 3;
int result = tmp / 2;
return result;
}
}
위의 코드를 보면 이야기 해보자.
전체적인 코드의 움직임은 argument에 4라는 값을 최초로 할당했고, 이 argument 변수를 SomeOperation메소드의 매개변수로 넘겨주고 결과값을 다시 argument로 할당한다. 구체적으로 하나하나 살펴보자.
1. int argument= 4; 에 의해 스택에 argument라는 변수명으로 공간이 할당되고, argument 변수의 타입은 기본자료형이므로 이 공간에는 실제 4라는 값이 할당된다.
2. someOperation()메소드가 호출된다. 호출될 때 인자로 argument 변수를 넘겨주며 scope가 someOperation()메소드로 이동한다. [ scope가 바뀌면서 기존의 argument 라는 값은 scope에서 벗어나므로 사용할 수 없다. ]
이 때 매개변수로 넘겨받은 argument는 param이라는 변수로 복사되어 전달되고, param 또한 기본자료형이므로 Stack에 할당된 공간에 값이 할당된다.
3. 다음으로 메소드 안에서의 코드가 실행되는데 이 코드들은 int tmp = param *3; int result = tmp / 2; 이다
이 tmp 변수와 result 변수는 기본자료형이므로 Stack에 쌓이게 된다.
4. 그 다음, 닫는 중괄호 } 가 실행되어 someOperation() 메소드가 종료되면서 호출한 함수 scope에서 사용되었던 모든 지역변수들은 stack에서 pop된다.(위에서 부터 사라짐) 그 후 다시 main함수로 scope가 이동하게 되고 argument의 값은 someOperation()의 결과값을 리턴받고 값이 변하여 저장된다.
5. main() 함수가 종료되는 순간 stack에 있는 모든 데이터들은 pop 되면서 프로그램이 종료된다.
■ 4-2. Heap(참조자료형 값 저장->heap에 저장되어 있는 참조자료형 값을 stack에 참조할 수 있도록 해줌)
- Heap 영역에는 주로 긴 생명주기를 가지는 데이터들이 저장된다.
- 참조형(Reference Type)의 데이터 타입을 갖는 객체(인스턴스)와 배열 등은 Heap 영역에 데이터가 저장되며, GC의 주 대상이 된다. 즉, "new"를 사용하여 객체를 만들때 저장이 된다.
- 애플리케이션의 모든 메모리 중 stack에 있는 데이터를 제외한 부분이라고 보면 된다.
- 모든 Object 타입(Integer,String,ArrayList,객체)는 heap 영역에 생성된다.
- 몇 개의 스레드가 존재하든 상관없이 단 하나의 heap 영역만 존재한다.
- Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수가 stack에 올라가게 된다.
Heap 예제를 보며 이애하자.
[EX] - Heap
public class Main {
public static void main(String[] args) {
int port = 4000;
String host = "localhost";
}
}
1. int port = 4000;에 의해서 기존처럼 4000 이라는 값이 port라는 변수명으로 할당되어 stack에 쌓인다.
2. String은 Object를 상속받아 구현되었으므로 String은 heap 영역에 할당되었고, stack에 host라는 이름으로 생성된 변수는 heap 에 있는 "localhost"라는 스트링을 레퍼런스(참조)하게 된다.
■ 4-3. Object(참조자료형)의 stack과 heap영역의 메모리 할당과 해제 예시(scope의 개념 중요성)
[EX] - Object(참조자료형)의 stack,heap
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> listArgument = new ArrayList<>();
listArgument.add("yaboong");
listArgument.add("github");
print(listArgument);
}
private static void print(List<String> listParam) {
String value = listParam.get(0);
listParam.add("io");
System.out.println(value);
}
}
위의 코드를 보며 stack과 heap 영역의 메모리 할당에 대해 이해해보자.
1. main() 함수의 시작부분은 List<String> listArgument = new ArrayList<>(); 로 시작한다.
new 예약어를 사용하여 생성하려는 오브젝트를 저장할 수 있는 충분한 공간이 heap에 있는지 먼저 찾은 다음, 빈 List를 참조하는 listArgument라는 지역변수를 스택에 할당한다.
2. 다음으로 listArgument.add("yaboong"); 구문이 실행되고 이 구문은 listArgument.add(new String("yaboong")); 과 같은 역할을 한다.
즉, new예약어에 의해 heap 영역에 충분한 공간이 있는지 확인한 후 "yaboong"이라는 문자열을 할당하게 된다. 이때 새롭게 생성된 문자열인 "yaoong"을 위한 변수는 stack에 할당되지 않는다.
List내부의 인덱스에 의해 하나씩 add()된 데이터에 대한 레퍼런스 값을 갖게 된다.
3. 다음으로 listArgument.add("github"); 구문이 실행되고 List에서 레퍼런스 하는 문자열이 하나 더 추가된다.
4. 다음으로 print(listArgument); 구문에 의해 함수 호출이 일어난다. 이때 listArgument라는 참조자료형을 매개변수로 넘겨준다.
이 메소드에서 listArgument를 listParam이라는 변수로 바꾸어 사용한다.
또한 함수 호출시 인자가 가지고 있는 값이 그대로 파라미터에 복사된다.
print()메소드 내부에서 listArgument는 scope 밖에 있게 되므로 접근할 수 없는 영역이 된다.
5. print()메소드 내부에서는 String value = listParam.get(0); 구문을 통해 List에 있는 데이터에 접근하여 값을 value라는 변수에 저장한다. 이 때 print()메소드의 scope에서 stack에 value가 추가되고, 이 value는 listParam을 통해 List의 0번째 요소[ List[0] ]에 접근하여 그 참조값을 가지게 된다. 그리고나서 listArgument.add("io"); 구문을 통해 데이터를 추가하고 출력함으로서 print()메소드는 종료된다.
6. 메소드가 닫는 중괄호에 의해 종료되면 print()메소드의 지역변수는 모두 stack에서 pop되어 사라진다.
이때, List는 Object 타입이므로 지역변수가 모두 stack에서 pop되더라도 heap 영역에 그대로 존재한다.
즉, 함수호출시 레퍼런스 값을 복사하여 가지고 있던 listParam과 메소드 내부의 지역변수인 value만 스택에서 사라지고 나머지는 모두 그래도인 상태로 함수 호출이 종료된다.
[EX] - Integer[Immutable Object 타입] Stack, Heap 메모리 영역 저장
public class Main {
public static void main(String[] args) {
Integer a = 10;
System.out.println("Before: " + a);
changeInteger(a);
System.out.println("After: " + a);
}
public static void changeInteger(Integer param) {
param += 10;
}
}
위의 코드를 보자.
- Integer는 Object 타입이므로, Integer a = 10;에서 10은 heap 영역에 할당되고, 10을 가리키는 레퍼런스 변수 a가 스택에 할당된다.
- 다음 구문인 ChangeInteger(a); 구문에 의해, a는 매개변수로 쓰이고, param이라는 레퍼런스 변수가 스택에 할당되고, 이 param은 main() 함수에서 a를 가리키던 곳을 똑깥이 가르키고 있다. [ ChangeInteger()메소드에서 a는 scope 밖에 있게 되므로 접근할 수 없는 영역이 된다. ]
- main() 함수에서 레퍼런스하던 a와 같은 곳을 param 이 가리키고 있으므로 param 에 10을 더하면 , changeInteger()함수가 종료되고 a의 값을 출력했을 때 바뀐 값이 출력될 것이다.
==> 결과는 20이다. 즉 값이 안바뀐다 왜 그럴까? [ 밑에서 배우겠지만, 불변객체일 경우에는 어떤 연산을 수행할때마다 기존 오브젝트를 변경하는 것이 아니라 새로운 오브젝트를 생성하는 것이라고 알고 있을 것이다. 그렇기에 값을 변경이 아닌, heap에 새로운 오브젝트를 생성한 것이다. ]
아래 예시를 보며 위에서 값이 안바뀐 이유를 알아보자.
[EX] - 불변객체(immutable) 연산 수행하기
public class Main {
public static void main(String[] args) {
String s = "hello";
changeString(s);
System.out.println(s);
}
public static void changeString(String param) {
param += " world";
}
}
- main() 함수의 s변수가 레퍼런스하는 "hello" 오브젝트를 param에 복사하면서 changeString() 메소드가 시작된다.
[ ChangeString()메소드에서 s는 scope 밖에 있게 되므로 접근할 수 없는 영역이 된다. ] - param += "world"; 를 실행하는 것은 heap에 "hello world" 라는 String 오브젝트가 새롭게 할당되는 작업이다
[ 즉, heap에서 기존 오브젝트를 hello->hello world 라고 바꾸는게 아닌 새로운 hello world 오브젝트를 생성한 것이다. 왜? 불변객체를 연산했기 때문에~ ] - 기존에 "hello"오브젝트를 레퍼런스하고 있던 param으로 새롭게 생성된 String 오브젝트인 "hello world"를 레퍼런스 하도록 만드는 것이다.
- changeString()함수가 종료되면 새롭게 생성된 "hello world" 오브젝트를 레퍼런스 하는 param이라는 변수는 스택에서 pop 되므로 어느것도 레퍼런스 하지 않는 상태가 된다. [ changeString()함수가 종료되면 param이라는 변수는 스택에서 pop되고 scope가 main()함수로 옮겨지기 때문이다. changeString()함수 안에 있는 변수들을 사용할 수 없게 되는 것이다. ]
- 이러한 경우 "hello world" 오브젝트는 garbage로 분류된다.
결과값으로 changeString()메소드를 수행하고 돌아가도 기존에 "hello"를 레퍼런스 하고 있던 변수의 값은 그대로 이다.
==> 왜냐하면 Immutable Object는 불변객체로써, 값이 변하지 않는다. 변경하는 연산이 수행되면 변경하는 것처럼 보이더라도 실제 메모리에는 새로운 객체가 할당되기 때문이다.[ 즉, 쉽게 말하면 불변객체를 연산하면서 변경되는 것처럼 보이지만 새로운 객체가 할당된 것이고, 연산하는 메소드가 종료되면 새로운 객체가 할당된 것은 pop 되기 때문에 새로운 객체를 참조하지 않는다. 그렇기에 메소드가 종류 후 불변객체가 heap영역에서 참조하는 것은 변하지 않는다. ]
자바에서 Immutable Object는 Integer, Character, Byte, Boolean, Long, Double, Float, Short, String 가 있다.
위에 있는 Immutable Object는 heap에 있는 같은 오브젝트를 레퍼런스 하고 있는 경우라도, 새로운 연산이 적용되는 순간 새로운 오브젝트가 heap에 새롭게 할당된다.
■ 5. 자바) Garbage Collection ■
예제를 보며 Garbage Collection에 대해 이해해보자.
[EX] - Garbage Collection
public class Main {
public static void main(String[] args) {
String url = "https://";
url += "yaboong.github.io";
System.out.println(url);
}
}
1. 위 코드에서 String ulr = "https://"; 구문이 실행된 뒤 스택과 힙은 아래와 같다.
2. 다음 구문인 url += "yaboong.github.io"; 구문을 실행한다. 불변객체(String)의 연산이므로 "https://"String에 "yaboong.github.io"를 덧 붙인 "https://yaboong.github.io"로 바뀌는것이 아니라
새로운 String "https://yaboong.github.io"이 할당되는 것이다.
위의 사진을 보게되면 Stack에는 새로운 변수가 할당되지 않는다.
즉, 문자열 더하기 연산의 결과인 "https://yaboong.github.io"가 새롭게 heap 영역에 생성되고, 기존에 "https://"를 레퍼런스 하고 있던 url 변수는 새롭게 생성된 문자열을 레퍼런스(참조)하게 되는것이다!!!!
==> 여기서 기존의 "https://"라는 문자열을 레퍼런스 하고 있는 변수는 아무것도 없으므로 Unreachable 오브젝트가 된다.
JVM의 Garbage Collector 는 Unreachable Object를 우선적으로 메모리에서 제거하여 메모리 공간을 확보한다.
Unreachable Object란 Stack에서 도달할 수 없는 Heap 영역의 객체를 말하는데, 위에 있는 예제에서 "https://" 문자열과 같은 경우가 되겠다.(불변객체에서의 연산작업을 할 때 남은 껍데기라고 생각하자.)
즉, Garbage Collection이 일어나면 Unreachable 오브젝트들은 메모리에서 제거된다.
Garbage Collection 과정은 Mark and Sweep 이라고도 한다.
JVM의 Garbage Collector가 스택의 모든 변수를 스캔하면서 각각 어떤 오브젝트를 레퍼런스 하고 있는지 찾는 과정이 Mark 이다.
그리고 나서 mark 되어있지 않은 모든 오브젝트들을 힙에서 제거하는 과정이 Sweep 이다.
Garbage Collection 이라고 하면 garbage들을 수집할 것 같지만,
실제로는 gabage를 수집하여 제거하는것이 아니라, gabage가 아닌 것을 따로 mark 하고 그 외의 것은 모두 지우는 것이다.
위의 예제(불변객체에서의 연산작업)에서 Garbage Collection 이 일어난 후의 메모리 상태는 아래 사진과 같다.
[EX] - 참조자료형 List 변수에 두번의 할당 작업(Garbage Collection을 이용하여 정리)
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> listArgument = new ArrayList<>();
listArgument.add("yaboong");
listArgument.add("github");
print(listArgument);
listArgument = new ArrayList<>();
}
private static void print(List<String> listParam) {
listParam.add("io");
System.out.println(listParam);
}
}
위의 코드를 보면 listArgument 라는 변수에 두번의 할당 작업이 일어난다. 위와 같이 실행한 결과의 stack과 heap 영역은 아래와 같이 될 것이다.
기존에 사용했던 listArgument 참조변수는 새롭게 생성된 빈 List를 레퍼런스 한다. 기존에 있던 세 개의 String 오브젝트는 List 내부의 인덱스에 의해 레퍼런스 되고 있지만 stack 에서는 Unreachable 한 영역에 있다.(즉, 누구도 heap영역에서 그 값(기존 사용했던 listArgument를 참조하지 않음)
기존에 listArgument가 참조했던 "yaboong", "github", "io"를 가진 ArrayList를 참고하는 있는 변수는 어느 stack에서도 찾아 볼 수 없다.
즉, 기존의 List 오브젝트와 기존의 List 오브젝트가 힙 내부에서 레퍼런스하고 있는 String 오브젝트 모두 garbage로 분류된다.
Garbage Collection이 일어난 후의 stack과 heap 영역은 아래와 같은 것이다.
출 처
'JAVA > 자바의 신 의문 해결' 카테고리의 다른 글
자바의 자료구조 정리 [ List, Set, Queue, Map ] (0) | 2022.09.19 |
---|---|
Garbage Colletion의 개념 및 동작원리 (1) | 2022.09.16 |
메모리 관점) 기본자료형, 참조자료형(모든 클래스, String 클래스) "==", eqauls() 비교 (0) | 2022.09.14 |
기본자료형, 참조자료형(모든 클래스, String 클래스) "==", eqauls() 비교 의문 해결 (0) | 2022.09.11 |