내일배움캠프/TIL

[Spring_4기 본캠프] 자바 메모리 구조와 static | Day 28

austindynasty 2024. 11. 26. 20:31

1. JVM - 자바 메모리 구조

[출처] 나의 손 (피그잼 활용)

 

▶ 자바 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역으로 나눌 수 있다.

  • 메서드 영역 : 클래스 정보를 보관한다. 이 클래스 정보가 붕어빵 틀이다.
    • 클래스 정보 : 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드 존재
    • static 영역 : static 변수들을 보관
    • 런타임 상수 풀 : 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다. 예를 들어 프로그램에 “안녕” 이라는 문자가 있으면 이런 문자를 공통으로 묶어서 관리한다.
  • 스택 영역 : 실제 프로그램이 실행되는 영역. 메서드를 실행할 때 하나씩 쌓인다. (후입 선출)
    • 스택 프레임 : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거된다.
    • 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함한다.
  • 힙 영역 : 객체(인스턴스)가 생성되는 영역이다. new 명령어를 사용하면 이 영역을 사용한다. 쉽게 이야기해서 붕어빵 틀로부터 생성된 붕어빵이 존재하는 공간이다. 참고로 배열도 이 영역에 생성된다.
    • 가비지 컬렉션이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 가비지 컬렉션 (GC)에 의해 제거된다.

2. static 변수 (클래스 변수)

    • 객체가 생성되면 생성자에서 정적 변수 count의 값을 하나 증가시킨다.
    package static1;
    
    public class Data3 {
        public String name;
        public static int count; //static 이 붙은 멤버 변수는 메서드 영역에서 관리한다. count는 static이 붙은 정적 변수이다. 정적 변수는 인스턴스 영역이 아님
    
        public Data3(String name) {
            this.name = name;
            count++;
        }
    }
    // static 변수는 쉽게 이야기 해서 클래스인 붕어빵틀이 특별히 관리하는 변수이다. 붕어빵 틀은 1개이므로 클래스 변수도 하나만 존재한다. 반면에 인스턴스인 붕어빵은 인스턴스의 수만큼 변수가 존재한다.
    
    package static1;
    
    public class DataCountMain3 {
    
        public static void main(String[] args) {
            Data3 data1 = new Data3("A");
            System.out.println("A count=" + Data3.count);
    
            Data3 data2 = new Data3("B");
            System.out.println("B count=" + Data3.count);
    
            Data3 data3 = new Data3("C");
            System.out.println("C count=" + Data3.count);
    
            //추가
            //인스턴스를 통한 접근 (권장 안함) -> count가 인스턴스 변수에 접근하는 것처럼 오해
            Data3 data4 = new Data3("D");
            System.out.println(data4.count);
    
            //클래스를 통해 바로 접근 가능
            System.out.println(Data3.count);
        }
    }
    
    → Data3.count와 같이 마치 클래스에 직접 접근하는 것처럼 느껴진다.
  1. → static이 붙은 멤버 변수 count는 인스턴스 영역이 아니라 메서드 영역에서 생성되어 관리된다.
  2. → 생성자에는 count++ 코드가 있다. count는 정적 변수이기 때문에 메서드 영역에서 관리하고, 이 경우 메서드 영역에 있는 count의 값이 하나 증가된다. (공용으로 사용)
  3.  
  4. 3. 멤버 변수(필드)의 종류
    • 멤버 변수의 종류
      • 인스턴스 변수 : static이 붙지 않은 멤버 변수 - name
      • 클래스 변수 : static이 붙은 변수 - count
        • 클래스 변수, 정적 변수, static 변수 등으로 부른다.
        • static이 붙은 멤버 변수는 인스턴스와 무관하게 클ㅋ래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다.
        • 클래스 변수는 자바 프로그램을 시작할 때 딱 1개가 만들어진다. 인스턴스와는 다르게 보통 여러 곳에서 공유하는 목적으로 사용된다.
      • 변수와 생명 주기
        • 지역 변수(매개변수 포함) : 지역 변수는 스택 영역에 있는 스택 프레임 안에 보관된다. 메서드가 종료되면 스택 프레임도 제거되는데 이 때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다. 따라서 지역 변수는 생존 주기가 짧다.
        • 인스턴스 변수 : 인스턴스에 있는 멤버 변수를 인스턴스 변수라 한다. 인스턴스 변수는 힙 영역을 사용한다. 힙 영역은 가비지 컬렉션이 발생하기 전까지는 생존하기 때문에 보통 지역 변수보다 생존 주기가 길다.
        • 클래스 변수 : 클래스 변수는 메서드 영역의 static 영역에 보관되는 변수이다. 메서드 영역은 프로그램 전체에서 사용하는 공용 공간이다. 클래습 변수는 해당 클래스가 JVM에 로딩되는 순간 생성된다. 그리고 JVM이 종료될 때까지 생명주기가 이어져 가장 긴 생명주기를 가진다.
    → static이 정적이라는 이유는 바로 여기에 있다. 힙 영역에 생성되는 인스턴스 변수는 동적으로 생성되고, 제거된다. 반면에 static인 정적 변수는 거의 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다. 정적 변수는 말 그대로 정적이다.
public class Data3 {
	public String name;
	public static int count;
}

→ Data3(”A”) 인스턴스를 생성하면 생성자가 호출된다.

static이 붙은 멤버 변수는 메서드 영역에서 관리한다.

 

4. static 메서드 

package static2;

public class DecoUtil2 {

    public static String deco(String str) { 
        return "*" + str + "*";
    }
}
// 메서드 앞에 static이 붙어있음 -> 정적 메서드 생성, 인스턴스 생성 없이 클래스 명을 통해 바로 호출 가능
package static2;

public class DecoMain2 {

    public static void main(String[] args) {
        String s = "hello java";
        String deco = DecoUtil2.deco(s);

        System.out.println("before: " + s);
        System.out.println("after: " + deco);
    }
}
  • 클래스 메서드 : 메서드 앞에 static을 붙인 것으로 정적 메서드라고도 한다. 정적 메서드를 이용하면 불필요한 객체 생성 없이 편리하게 메서드를 사용할 수 있다.
  • 인스턴스 메서드 : static이 붙지 않은 메서드는 인스턴스를 생성해야 호출할 수 있다.

하지만 정적 메서드를 항상 사용할 수 있는 것은 아니다. 

  • static 메서드는 static만 사용할 수 있다. 
    • 클래스 내부의 기능을 사용할 때, static이 붙은 메서드는 static 메서드와 static 변수만 사용 가능 
    • 클래스 내부의 기능을 사용할 때, static 메서드는 인스턴스 변수나 인스턴스 메서드를 사용할 수 없다.
  • 반대로 모든 곳에서 static을 호출할 수 있다.
    • 정적 메서드는 공용기능이다. 따라서 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서 static을 호출할 수 있다.

[예시]

package static2;

public class DecoData {

    private int instanceValue;
    private static int staticValue;

    public static void staticCall() {
        //instanceValue++; //인스턴스 변수 접근, compile error
        //instanceMethod(); //인스턴스 메서드 접근, compile error

        staticValue++; //정적 변수 접근
        staticMethod(); //정적 메서드 접근
    }

    public void instanceCall() {
        instanceValue++; //인스턴스 변수 접근
        instanceMethod(); //인스턴스 메서드 접근

        staticValue++; //정적 변수 접근
        staticMethod(); //정적 메서드 접근
    }


    private void instanceMethod() {
        System.out.println("instanceValue=" + instanceValue);
    }

    private static void staticMethod() {
        System.out.println("staticValue=" + staticValue);
    }
}
package static2;

//import static static2.DecoData.staticCall;
//import static static2.DecoData.*;

public class DecoDataMain {

    public static void main(String[] args) {
        System.out.println("1. 정적 호출");
        DecoData.staticCall();

        System.out.println("2. 인스턴스 호출1");
        DecoData data1 = new DecoData();
        data1.instanceCall();

        System.out.println("3. 인스턴스 호출2");
        DecoData data2 = new DecoData();
        data2.instanceCall();

        //추가
        //인스턴스를 통한 접근
        DecoData data3 = new DecoData();
        data3.staticCall();

        //클래스를 통한 접근
        DecoData.staticCall();
    }
}

 

 

★새배개★

1. 클래스 다이어그램 : Unified Modeling Language(UML) 중 하나로, 클래스 내부 구성요소 및 클래스 간의 관계를 도식화하여 시스템의 특정 모듈이나 일부 및 전체를 구조화 한다. 

2. 포맷 지정자 : 자바에서 형식화된 출력을 제공하는 printf 메서드를 통해 사용할 수 있다( System.out.printf() ) . 다양한 데이터를 특정 형식으로 정렬하거나 변환하여 출력하는 데 유용하다. 

// 형식 : System.out.printf("포맷 문자열", 값1, 값2, ...);

public class PrintfExample {
    public static void main(String[] args) {
        int number = 42;
        double pi = 3.14159;
        String text = "Java";

        // 정수 출력
        System.out.printf("정수: %d%n", number); // "정수: 42"

        // 실수 출력
        System.out.printf("실수: %.2f%n", pi); // "실수: 3.14"

        // 문자열 출력
        System.out.printf("문자열: %s%n", text); // "문자열: Java"
    }
}
포맷 지정자 설명
%d 10진수 정수
%f 소수점이 있는 실수
%s 문자열
%c 문자
%b 불리언(boolean)값
%n 줄바꿈
%x 16진수 정수