T 메모리 구조?

maketheworldwise·2022년 3월 25일
0


이 글의 목적?

이번 글에서는 코드가 실행될 때의 메모리 구조를 살펴보려고 한다.

T 메모리 구조의 동작 흐름

책에서는 데이터 저장 영역을 스태틱, 스택, 힙 3가지 영역으로 분리하여 T 메모리 구조라고 지칭했다. 사실은 조금 더 복잡하지만 책에서는 변수의 저장 방식에 대해서 설명하기 위해 간략하게 표현한거 같다.

main() 실행 전

메인 메소드가 실행되기 전에는 다음과 같은 과정이 진행된다.

  1. JRE는 메인 메소드를 찾는다.
  2. 메인 메소드의 존재가 확인되면 JVM을 구동시킨다.
  3. 구동된 JVM은 메인 메소드를 실행하기 전, 전처리 과정을 수행한다.
  4. JVM은 메인 메소드를 실행한다.

💡 JVM 전처리 과정?

가장 먼저 모든 자바 프로그램이 반드시 포함하는 java.lang 패키지, import 된 패키지, 프로그램상의 모든 클래스를 스태틱 영역에 배치하는 과정을 의미한다. 더 정확히는 패키지와 클래스는 처음으로 사용될 때 로딩되는 것이 맞다.

즉, 메인 메소드가 실행되기 전에는 스태틱 영역에 필요한 패키지와 클래스의 정보가 올라간다고 이해하면 된다. 😊

main() 실행

메인 메소드가 실행되면 스택 프레임이 스택 영역에 할당된다. 그리고 스택 프레임 내부에는 메인 메소드에 선언된 변수에 대한 공간을 할당해준다. 가장 기본적인 메인 메소드라면, 메인 메소드의 인자 - args를 저장할 변수 공간을 스택 프레임의 가장 밑에 확보된다.

메인 메소드의 모든 코드가 실행이 되었다면, 스택 프레임은 소멸된다. 메인 메소드가 끝나게 되면 JRE는 JVM을 종료하고 JRE자체도 운영체제 상의 메모리에서 사라진다.

💡 스택 프레임?

스택 프레임은 이름 그대로 위로 쌓이는 틀이라고 생각하면 된다. 그리고 이 틀(스택 프레임)은 여는 중괄호로 생성이 되고 닫는 중괄호로 소멸된다.

예시

간단한 예시로 위의 내용을 이해해보자.

public class Start { 
	public static void main(String[] args) { // 1번
	    int i = 10; 		// 2-1번
    	int k = 20; 		// 2-2번
        
        if(i == 10) { 		// 3번
        	int m = k + 5;	// 4번
            k = m;			// 5번
        } else {
        	int p = k + 10;
            k = p;
        }
        					// 6번
    }
}

스태틱 영역 (클래스의 놀이터)

java.lang, import된 패키지, 사용하는 클래스가 배치되는 영역이다. 클래스 내부에 static으로 선언되어있는 전역 변수들은 해당 클래스가 스태틱 영역의 공간에 배치될 때 그 안에 클래스의 멤버로 공간을 만들어 저장된다.

💡 가급적이면 전역 변수를 사용하지 않는 것이 좋다?

프로젝트 규모에 따라 코드가 커지면서 여러 메소드에서 전역 변수의 값을 변경하기 시작하면 T 메모리로 추적하지 않는 이상 전역 변수에 저장되어 있는 값을 파악하기 쉽지 않아진다. 즉, 유지 보수 과정에서 계속 추적을 해야하는 불편함이 생긴다.

그리고 동시성 이슈도 존재한다. 서로 다른 메소드가 전역 변수에 접근하게 되면 우리가 의도하지 않은 결과가 나올 수 있기 때문이다.

스택 영역 (메소드의 놀이터)

스택 프레임이 생성되는 영역이다. 스택 메모리 내의 스택 프레임 안의 변수는 지역 변수라고 하며, 해당 프레임에서만 사용할 수 없고 외부에서는 사용할 수 없다. 이는 스택 영역에 있는 각 스택 프레임에서도 접근이 불가능하다는 의미다.

왜 접근이 불가능할까? (책에서도 뚜렷한 이유는 모른다고 한다. 🥲)

책의 필자가 생각한 첫 번째 이유는, 메소드는 고유 공간이라 서로 침범할 수 없는게 이치에 맞기 때문이다.

두 번째는 포인터 문제다. 만약 A 스택 프레임에서 B 스택 프레임 내부의 지역 변수에 접근하려면, 해당 지역 변수의 위치를 명확히 알아야하는데, 자바에서는 포인터가 존재하지 않기 때문에 접근이 불가능하다.

세 번째는 메소드는 다양한 곳에서 호출되기 때문이다. 만약 호출한 메소드 내부의 지역 변수를 호출당하는 메소드에서 제어할 수 있게 하려면 포인터를 주고 받아야 하고, 이는 두 번째 문제와 연결된다. 또한 메소드를 호출하면서 만들어지는 스택 구조도 항시 변화할 것이다. 따라서 항시 변하기 때문에 어느 메소드 스택 프레임의 변수를 참조해야하는지에 대한 문제가 발생한다.

💡 Call by value?

Call by value는 메소드 호출 방식중 하나로, 전달되는 변수의 값을 복사하여 함수의 인자로 전달하는 것을 의미한다.

힙 영역 (객체의 놀이터)

힙 영역은 객체가 배치되는 공간이다. 앞에서 설명한 과정에서 힙 영역에 객체가 생성이 된다고 생각하면 된다. (힙은 대용량 자료를 저장할 수 있도록 메모리를 사용하는 자료구조다!)

public class Computer {
	public String name;
    public int mem;
    
    public void on() {
    	System.out.println("on");
    }
}
public class MyComputer { 
	public static void main(String[] args) {// 1번
    	Computer mac = new Computer();		// 2번 (mac)
        									// 3번 (new Computer)
                                            // 4번 (=)
        
        mac.name = "mac";					// 5-1번
        mac.mem = 16;						// 5-2번
        mac.on();							// 5-3번
        
        mac = null;							// 6번
    }
}

💡 Call by reference?

Call by reference는 메소드 호출 방식중 하나로, 전달되는 변수의 주소를 전달한다.

이 글의 레퍼런스

  • 스프링 입문을 위한 자바 객체 지향의 원리와 이해
profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글