[java] static & JVM 메모리 모델

송어·2023년 11월 13일

static과 메모리의 관계

main class는 왜 객체생성없이 실행이 될까??

지금까지 만들어진 클래스를 사용하려면 객체를 생성하는 과정을 거쳐야만 했다. 그런데 main class는 왜 객체를 생성하는 과정이 없어도 실행이 될까? 그것은 static키워드에 해답이 있다.

main class가 동작되는 방식

  1. JVM에서 실행할 클래스를 찾는다.
  2. static 키워드가 붙어있는 멤버들을 정해진 메모리(static-zone) 위치에 한번 자동으로 로딩한다.
  • static 멤버들은 클래스를 사용하는 시점에 딱 한번 메모리에 할당된다는 점이 중요하다.
  • 여기서는 main() 메서드가 static이기 때문에 메모리에 자동으로 로딩한다.
  1. JVM이 static-zone에서 main()메서드를 호출한다.(Method Call)
  2. 호출한 메서드를 Call Stack Frame Area(Stack Area)에 push한 뒤 동작을 시작한다.

Call Stack Frame Area
메서드가 호출되면(Method Call) 호출된 기계어 코드가 push되고 실행되는 메모리 공간
현재 프로그램이 실행되고 있는 상태를 파악할 수 있다.(LIFO구조)

public class StaticTest {
	public static void main(String[] args) { // call 1
    	int a = 10;
        int b = 20;
        int result = StaticTest.sum(a,b);
        System.out.println(result);
    }
    public static int sum(int a, int b) { // call 2
    	int v = a + b;
        return v;
    }
}

해당 클래스의 동작 방식 및 생명주기
1. main Method가 실행시 Method Area(static-zone)에 올라감
2. JVM이 static-zone에서 Method Call
3. 호출된 main()이 Stack Area에 push되어 동작이 실행됨(PC는 main()을 가리킴)
4. int sum의 반환값인 StaticTest.hap(a,b) 호출을 위해 static-zone에 할당
5. Method Call로 인해 hap()메서드가 Stack Area에 push되어 동작이 실행됨(PC가 sum()을 가리킴-LIFO구조의 특성)
6. 동작이 종료된 sum()은 Stack에서 사라짐(PC는 main()을 가리킴)
7. 동작이 종료된 main()은 Stack에서 사라짐

static과 none static 멤버들의 접근방법

none static메서드는 static-zone에 할당될 수 없기 때문에 따로 메모리로 로딩해야한다. 즉, 객체 생성을 통해 메서드를 메모리에 할당시켜야 한다.

public class NoneStaticTest {
    public static void main(String[] args) { // call 1
        int a = 10;
        int b = 20;

        NoneStaticTest test = new NoneStaticTest(); // NoneStaticTest객체생성
        int result = test.sum(a,b);
        System.out.println(result);
    }
    public int sum(int a, int b) { // call 2
        int v = a + b;
        return v;
    }
}
  1. main Method가 실행시 Method Area(static-zone)에 올라감
  2. JVM이 static-zone에서 Method Call
  3. 호출된 main()이 Stack Area에 push되어 동작이 실행됨(PC는 main()을 가리킴)
  4. sum()메서드를 사용하기 위해 NoneStaticTest객체 생성(sum()을 메모리에 올리기 위함)
  5. NoneStaticTest객체는 Heap Memory에 생성되고, sum()은 Method Area의 none static-zone에 할당됨
  6. Method Call에 의해 sum()이 push되어 Stack Area에 올라감
  7. sum()메서드가 종료되면 stack에서 지워짐
  8. 실행이 종료된 main()메서드도 stack에서 지워짐

stack Area에 올라간 인스턴스변수 test는 heap메모리에 할당된 NoneStaticTest객체 주소를 가리킨다.
NoneStaticTest객체의 hap()메서드는 Method Area의 hap()을 가리킨다.
-> 메서드의 기계어 코드가 따로 Method Area에 할당된다는 의미

요약

  • static 메서드클래스를 사용하는 시점자동으로 static-zone에 로딩된다.
  • none static 메서드객체 생성을 통해 none static-zone에 로딩된다.

두 개의 클래스가 존재하는 경우의 static 메서드 접근

public class StaticAccess {
	public static void main(String[] args) {
    	int a = 10;
        int b = 20;
        int result = MyUtil.sum(a,b); // static메서드인 sum이 클래스 사용 시점에
        //자동으로 static-zone에 올라가있기 때문에 객체를 생성할 필요가 없다.
        
        System.out.println(result);
    }
}
public class MyUtil {
	public static int sum(int a, int b) {
    	int v = a + b;
        return v;
    }
}

static 메서드 접근 방법 : 클래스이름.호출메서드
빈번하게 사용하는 메서드의 경우 객체 생성을 통해 사용하기 보단 static메서드로 관리해 호출한다.

JVM이 사용하는 메모리 영역

JVM의 Memory Model(Runtime Data Area)

1. Method Area

  • 메서드의 바이트코드(기계어 코드)가 할당되는 공간
  • static-zone & none static-zone으로 나누어짐
  • static 멤버들은 static-zone에 할당됨(클래스 사용 시점)

2. Heap Area Generation

  • 객체가 생성되는 메모리 공간
  • GC(Garbage Collector)에 의해서 메모리가 수집됨(사용을 마친 객체 소멸)

3. Stack Area(Call Stack Frame Area) & PCregister(Native method Area)

  • 메서드 호출시 메서드의 기계어 코드를 할당 받고(Native Method Area), 메서드가 실행 되는 메모리 공간(Call Stack Frame Area)
    (지역변수, 매개변수들이 만들어지는 공간)
  • PC(Program Counter)에 의해 현재 실행 중인 프로그램의 위치가 관리됨
  • LIFO 구조로 운영됨

4. Runtime Constant Pool(Literal Pool)

  • 상수 값이 할당되는 메모리 공간
  • 문자열 중 문자열 상수(Literal-리터럴)가 할당 되는 메모리 공간

객체 생성과 static의 관계

어떤 클래스의 모든 멤버가 static인 경우?

어떤 클래스의 모든 멤버 메서드가 static인 경우, 객체 생성 후 메서드를 사용할 수 있고, static-zone에 할당된 메서드를 그대로 호출해서 사용할 수도 있다.
일반적으로 static 메서드는 객체 생성과는 관련이 없기 때문에 객체 생성을 통해 인스턴스 메서드로 사용하기 보단 즉시 호출해서 사용하는 방법이 더 바람직한 방법이다.

public class AllStaticTest {
    public static void main(String[] args) {

        // static 메서드를 사용하기엔 바람직하지 않은 방법임(인텔리제이에서도 경고를 띄움)
        AllStatic st = new AllStatic();
        System.out.println(st.sum(10,20));
        System.out.println(st.max(10,20));
        System.out.println(st.min(10,20));

        // static 메서드를 사용하는 바람직한 방법
        System.out.println(AllStatic.sum(10,20));
        System.out.println(AllStatic.max(10,20));
        System.out.println(AllStatic.min(10,20));
    }
}
public class AllStatic {

	private AllStatic() {} // 생성자의 접근 제어자를 private로 설정해 객체 생성을 막을 수 있음
    
    public static int sum(int a, int b) {
        int v = a + b;
        return v;
    }

    public static int max(int a, int b) {
        //return a > b ? a : b;
        return Math.max(a, b);
    }

    public static int min(int a, int b) {
        //return a < b ? a : b;
        return Math.min(a, b);
    }
}

생성자의 접근 제어자를 private로 사용해 객체 생성을 못하도록 막을 수 있다.
(생성자는 반드시 public이다 -> 잘못된 개념)
-> System, Math 등의 자바 api는 실제로 private생성자를 가지고 있음

Class, Object, Instance 구분

Class : 객체를 모델링하는 도구(설계도, 틀)

public class CarDTO {
    public int carSn;
    public String carName;
    public int carPrice;
    public String carOwner;
    public int carYear;
    public String carType;
    
    public CarDTO() {}
    
    ...이하 생략
}

Object : 클래스를 통해 선언되는 변수

CarDTO = car; // 객체변수 선언

객체가 구체적인 실체(대상)을 가리키지 않는 상태(객체 변수)
객체가 서로 구분이 되지 않는 시점

Instance : 객체 생성에 의해 메모리(Heap Memory)에 만들어진 객체

car = new CarDTO(); // 객체 생성 및 초기화

객체가 구체적인 실체(대상)을 가리키는 상태(인스턴스 변수)
객체가 서로 구분이 되는 시점

서로 비슷한 개념으로 모두 객체를 나타내는 용어이다.


평소에 자바를 사용할 때 메모리 관련 개념을 크게 생각하지 않았었는데, 이번에 JVM의 메모리 모델과 자바의 동작방식을 익히면서 JVM의 기본적인 메모리 구조나 할당 방식은 자바를 사용함에 있어 매우 필수적인 개념이라는 것을 체험했다. 특히 static메서드를 제대로 배우지 않아서 잘 사용하지 않았었는데 이번에 static키워드와 메모리의 관계에 대해 어느정도 짚고 넘어간 만큼 앞으로 프로그래밍을 할 때에도 static키워드를 사용해보면서 조금 더 메모리 적으로 생각하고 신경 쓸 수 있도록 클린한 애플리케이션을 만들어보고 싶다.

관련 내용에 대해서 조금 더 깊게 알고싶은 부분이나 더블체크 해야할 사항을 파악해서 시일 내에 글을 수정 & 보충 해야겠다.

0개의 댓글