코딩 공부/Java

[Java] Chpater 2- 02) JVM 메모리 영역

sintory-04 2025. 2. 25. 14:01

    1️⃣ 메모리의 구조

    - JVM 이란 ?

    • VJM이란 ? 바이트코드를 해석하고 자바 프로그램을 실행시키는 역할

    - JAVA의 메모리 구조는 크게 3가지로 나뉨.

    1. Method Area

    • 프로그램 시작 시 한번만 클래스 정보가 저장
    • 클래스 정보(.class 파일) 가 올라가는 곳.
    • 클래스의 메서드 정보, static 변수 등이 저장됩니다.
    • 모든 객체가 공유하는 공용 공간

    2. Stack Area

    • 가장 위에 있는 접시를 먼저 꺼내듯 비슷한 구조로 생각하기
    • 선입후출(LIFO) 구조 입니다. 먼저 들어온 것이 가장 늦게 나간다는 뜻
    • 메서드가 호출될 때마다 새로운 스택프레임이 쌓인다.
    • 가장 위의 스택인 최근 호출된 메서드가 먼저 실행된다.
    • 메서드 실행이 끝나면 스택에서 제거된다.
    • 메서드 실행 시 생성되는 지역 변수(Primitive Type, Reference Type 포함)가 저장됨.
    • 메서드 호출이 끝나면 해당 스택 프레임이 제거됨.
    • 지역 변수(로컬 변수)나 메서드의 매개변수는 Stack에 저장됨.
    • -
    • 메서드가 호출될 때마다 Stack 영역에 메모리가 할당된다.
    • 메서드가 시작되면 추가되고 메서드가 종료되면 사라지는 구조이다.
    • 특정 메서드가 실행되면 해당 메서드의 정보와 변수가 Stack 에 저장되고 메서드 실행이 끝나면 그 메모리는 자동으로 제거된다.
    • 메서드 내에 선언된 지역변수들이 저장되는 공간이다. → 변수에 객체가 담기면 객체의 주소값이 저장됨.

    3. Heap Area

    • new 키워드로 생성된 객체(인스턴스)가 저장되는 공간입니다.
    • 객체의 실제 데이터가 저장되고 데이터의 주소는 stack 영역에 저장됩니다.
    • new 키워드를 사용하여 생성한 객체는 Heap에 할당된다.(참조 변수(Reference Variable)는 Stack에 저장됩니다.)
    • Heap 영역의 객체는 필요 없어지면 가비지 컬렉터(GC)가 정리 가능

    2️⃣ 코드의 흐름 살펴보기

    • 코드의 흐름을 살펴보기 위해서 아래의 사이트를 이용할 것이다.

    "https://cscircles.cemc.uwaterloo.ca/java_visualize/"

    Frames 는 Stack 영역,

    Object 는 Heap 영역이라고 보면 된다.

    • 아래의 코드의 흐름을 분석해보겠다.
    public class Main {
        static class Person {
    
            // 1. 속성
            String name;
            int age;
            String address;
    
            // 2. 생성자
            Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            // 3-1. 소개 기능(이름 나이 출력 기능)
            void introduce() {
                System.out.println("나의 이름은");
                System.out.println(this.name + "입니다.");
                System.out.println("나의 나이는");
                System.out.println(this.age + "입니다.");
            }
    
            // 3-2. 더하기 기능(소개를 하고 더하기 연산 수행)
            int sum(int value1, int value2) {
                introduce();
                int result = value1 + value2;
                return result;
            }
        }
    
        public static void main(String[] args) {
            String name = "Steve";
            int age = 20;
            Person personA = new Person(name, age);
            personA.introduce();
    
            int value1 = 1;
            int value2 = 2;
    
            int ret = personA.sum(value1, value2);
            System.out.println(ret);
        }
    }

    1. String name = "Steve";

    - 항상 Main 클래스가 먼저 실행된다 생각하면 된다.

    Main의 첫번째 줄인 String name = "Steve"; 가 가장 먼저 실행될 것이다.

    - 이렇게 보면 "Steve" 라는 값이 Stack[Frame] 영역에 쌓인 걸 볼 수 있다.

    - name 자체는 "Steve"를 참조하는 지역 변수이므로 Stack에 저장된 것이다.

    >> 이부분을 조금 더 이해하자면,

    • 우리가 String name = "Steve";라고 하면, "Steve"라는 문자열 값이 생기고, name 변수가 그것을 가리키게(참조하게) 된다.
    • 그래서 우리가 name 을 참조변수라고 얘기를 할 수 있는 것이고, 이 참조변수는 main 메서드가 실행될 시에 Stack 영역에 쌓이게 되는 것이다.

    2. int age = 20;

    - int age 도 Stack 영역에 쌓인것을 확인할 수 있다.

    - 기본형(int)은 값 자체가 Stack에 저장된다.

    3. Person personA = new Person(name, age);

    - 메서드를 호출 했기 때문에 코드 10줄로 가게된다.

    - 새로운 스택 프레임이 쌓이게 되며, 생성자에 따라 Heap 영역에 데이터를 저장하게 된다.

    Person(String name, int age) {
          this.name = name;
          this.age = age;
    }

    - 코드에서 보다시피, 이전에 할당했던 name과 age의 값을 personA 인스턴스가 참조하여 사용한다.

    4. personA.introduce();

    - 해당 메서드를 부름으로써 out put 으로 아래와 같이 나온 것을 확인 할 수 있다.

    - 이때 힙 영역에 있던 Person instance 를 가져오는 것을 알 수 있다.

    5.int value1 = 1; int value2 = 2;

    - 마찬가지로, int형 이기 때문에 stack 에 쌓인 걸 볼 수 있다.

    6. int ret = personA.sum(value1, value2);

    - 해당 sum 메서드를 호출 했기 때문에, 해당 메서드가 스택에 쌓인다.

    7. introduce();

    - 다시 introduce() 메서드를 호출했기 때문에, introduce() 로 돌아간다.

    int sum(int value1, int value2) {
        introduce();
        int result = value1 + value2;
        return result;
    }

    8. int sum(int value1, int value2) {

    - 메서드를 돌아오면서 Stack 영역에서 introduce 가 없어진 걸 확인할 수 있다.

    9. System.out.println(ret);

    - ret으로 저장해둔 값은 stack 에 쌓였고, sout를 마지막으로 코드가 끝난 걸 확인 할 수 있다.

    10. 출력문

     

    OUTPUT
    나의 이름은
    Steve입니다
    나의 나이는
    20입니다.
    나의 이름은
    Steve입니다.
    나의 나이는
    20입니다.

     

    11. Stack 과 Heap 정리

    코드 변수 저장 위치 설명
    String name = "Steve"; name (참조 변수) Stack "Steve"를 가리키는 참조값 저장
    "Steve" (문자열 객체) "Steve" Heap (String Constant Pool) "Steve" 문자열이 저장됨
    int age = 20; age Stack 기본형(int)은 값 자체가 Stack에 저장됨
    Person personA = new Person(name, age); personA (참조 변수) Stack Person 객체를 가리키는 참조값 저장
    (생성된 Person 객체) Person 객체 Heap new로 생성된 객체이므로 Heap에 저장
    int value1 = 1; value1 Stack 기본형(int)은 값 자체가 Stack에 저장됨
    int value2 = 2; value2 Stack 기본형(int)은 값 자체가 Stack에 저장됨
    int ret = personA.sum(value1, value2); ret Stack 기본형(int)은 값 자체가 Stack에 저장됨

    3️⃣ 기본형과 참조형의 차이

    ✅ 기본형(Primitive Type)

    • int, double, boolean, char, ...
    • 값 자체를 Stack에 저장
    • 빠른 접근 속도, 관리가 간단함 > 이러한 이유로 래퍼 변수보다 기본형 변수를 사용하는 방안으로 코드를 작성하는 것이 좋음.

    - 기본형은 왜 Stack에 저장할까?

    👉 기본형은 크기가 정해져 있고 작아서 빠르게 저장할 수 있기 때문
    👉 참조형은 크기가 커질 수 있어서 동적 메모리 관리가 가능한 Heap을 사용
    👉 자동 메모리 관리 때문 Stack 은 함수가 끝나면 자동으로 정리가 된다. (pop 연산)

    ✅ 참조형(Reference Type)

    • String, Array, Object, ...
    • 실제 데이터는 Heap에 저장되고, Stack에는 Heap을 가리키는 주소(참조값) 저장
    • 크기가 커질 수 있으므로 Heap에서 동적 메모리 할당