ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JAVA - 자바 메모리 구조와 static
    JAVA 2024. 1. 6. 12:07
    728x90

    1. 자바 메모리 구조

    • 메서드 영역(Method Area) : 메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리한다. 이 영역은 프로그램의 모든 영역에서 공유한다.
      • 클래스 정보 : 클래스 실행코드(바이트 코드), 필드, 메서드와 생성자 코드등 모든 실행 코드가 존재한다.
      • static 영역 : static 변수들을 보관한다. 뒤에서 자세히 설명하는 걸로.
      • 런타임 상수 풀 : 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다. 예를 들어서 프로그램에 "hello"라는 리터럴 문자가 있으면 이런 문자를 공통으로 묶어서 관리한다. 이 외에도 프로그램을 효율적으로 관리하기 위한 상수들을 관리한다.
    • 스택 영역(Stack Area) : 자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레미은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함한다.
      • 스택 프레임  : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임은 제거된다.
    • 힙 영역(Heap Area) : 객체(인스턴스)와 배열이 생성되는 영역이다. 가비지 컬렉션이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.

    2. 스택과 큐 자료 구조

    후입 선출(LIFO, Last In First Out)

     - 여기서 가장 마지막에 넣은 3번이 가장 먼저 나온다. 이렇게 나중에 넣은 것이 가장 먼저 나오는 것을 후입 선출이라 하고, 이런 자료 구조를 스택이라 한다.

     

    선입 선출(FIFO, First In First Out)

     - 후입 선출과 바대로 가장 먼저 넣은 것이 가장 먼저 나오는 것을 선입 선출이라 한다. 이런 자료 구조를 큐(Queue)라 한다.

     

    이번 시간에서 중요한 것은 스택이다. 프로그램 실행과 메서드 호출에는 스택 구조가 적합하다. 스택 구조를 학습했으니, 자바에서 스택 영역이 어떤 방식으로 작동하는지 알아보자.

    3. 스택 영역

    다음 코드를 실행하면 스택 영역에서 어떤 변화가 있는지 확인해보자.

    public class JavaMemory1 {
        public static void main(String[] args) {
            System.out.println("main start");
            method1(10);
            System.out.println("main end");
        }
    
        static void method1(int m1) {
            System.out.println("method1 start");
            int cal = m1 * 2;
            method2(cal);
            System.out.println("method1 end");
        }
    
        static void method2(int m2) {
            System.out.println("method2 start");
            System.out.println("method2 end");
    
        }
    }

    출력 결과는 

     

    main start
    method1 start
    method2 start
    method2 end
    method1 end
    main end

     

    이 순이다. 그 말은 실행 순서가

    main시작 - method1시작 - method2시작 - method2종료 - method1종료 - main종료  

    이런 식이다.

     

    정리

    • 자바는 스택 영역을 사용해서 메서드 호출과 지역 변수(매개변수 포함)를 관리한다.
    • 메서드를 계속 호출하면 스택 프레임이 계속 쌓인다.
    • 지역 변수(매개변수 포함)는 스택 영역에서 관리된다.
    • 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
    • 스택 프레임이 모두 제거되면 프로그램도 종료된다.

    4. 스택 영역과 힙 영역

    스택 영역과 힙 영역이 함께 사용되는 경우를 알아보자.

    public class Data {
        private int value;
    
        public Data(int value) {
            this.value = value;
        }
    
        public int getValue() {
            return value;
        }
    }
    public class JavaMemory2 {
        public static void main(String[] args) {
            System.out.println("main start");
            method1();
            System.out.println("main end");
        }
    
        static void method1() {
            System.out.println("method1 start");
            Data data1 = new Data(10);
            method2(data1);
            System.out.println("method1 end");
        }
    
        static void method2(Data data2) {
            System.out.println("method2 start");
            System.out.println("data2.getValue() = " + data2.getValue());
            System.out.println("method2 end");
        }
    }

    실행 결과

     

    main start
    method1 start
    method2 start
    data2.getValue() = 10
    method2 end
    method1 end
    main end

     

    좀 헷갈릴수도 있기에 자세히 코드 진행구조를 설명해보겠다.

     

    1. 처음 main() 메서드를 실행하면 main() 스택 프레임이 생성된다.

    2. main() 메서드에서 method1() 을 실행한다. method1() 스택 프레임이 생성된다.

    3. method1()은 지역변수로 Data data1 을 가지고 있다. 이 지역변수도 스택 프레임이 포함된다.

    4. method1()은 new Data(10)을 사용해서 힙 영역에 Data 인스턴스를 생성한다. 그리고 참조값을 data1에 보관한다.

    5. method1() 은 method2()를 호출하면서 Data data2 매개변수에 해당 참조값을 넘긴다.

    6. 이제 method1()에 있는 data1과 method2()에 있는 data2 지역변수는 둘다 같은 참조값을 가진 인스턴스를 참조한다.

    7. method2()가 종료된다. method2()의 스택 프레임이 제거되면서 매개변수 data2도 함께 제거된다.

    8. method1()이 종료된다. method1() 스택 프레임이 제거되면서 지역 변수 data1도 함께 제거된다.

    9. method1()이 종료된 직후의 상태를 보면 method1()의 스택 프레임이 제거되고 지역 변수 data1도 함께 제거되었다.

    10. 이제 해당 참조값을 가진 Data 인스턴스를 참조하는 곳은 없다.

    11. 참조하는 곳이 없으므로 사용되는 곳도 없다. 결과적으로 프로그램이 더는 사용하지 않는 객체인 것이다. 이런 경우에는 메모리만 차지하게 되므로, 가비지 컬렉션이 참조가 사라진 인스턴스를 찾아 메모리에서 제거한다.

    5. static 변수 1

    새로운 키워드인 static 에 대해 알아보자.

    static는 주로 멤버 변수와 메서드에 사용된다.

    먼저 멤버 변수에 static 키워드가 왜 필요한지 예제를 통해 알아보자.

     

    특정 클래스를 통해 생성된 객체의 수를 세는 단순한 코드이다.

    public class Data1 {
        public String name;
        public int count;
    
        public Data1(String name) {
            this.name = name;
            count++;
        }
    }
    public class DataCountMain1 {
        public static void main(String[] args) {
            Data1 data1 = new Data1("A");
            System.out.println("data1.count = " + data1.count);
    
            Data1 data2 = new Data1("B");
            System.out.println("data1.count = " + data2.count);
    
            Data1 data3 = new Data1("C");
            System.out.println("data1.count = " + data3.count);
        }
    }

    실행시켜 보면 count는 올라가지 않고 계속 1이다. 왜냐하면 객체를 생성하면 Data1의 인스턴스는 새로 만들어지기 때문에 count도 새로 만들어진다. 각자 별개의 객체이기 때문에 숫자가 늘어나지 않는다. 

     인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않는다. 원하는 답을 구하기 위해선 변수를 서로 공유해야 한다.

    public class Counter {
        public int count;
    }
    
    public class Data2 {
        public String name;
        public Data2(String name, Counter counter) {
            this.name = name;
            counter.count++;
        }
    }
    public class DataCountMain2 {
        public static void main(String[] args) {
            Counter counter = new Counter();
            Data2 data1 = new Data2("A", counter);
            System.out.println("A counter = " + counter.count);
    
            Data2 data2 = new Data2("B", counter);
            System.out.println("B counter = " + counter.count);
    
            Data2 data3 = new Data2("C", counter);
            System.out.println("C counter = " + counter.count);
        }
    }

    물론 이렇게 해서 count를 공용으로 사용한 덕분에 객체를 생성할 때 마다 값을 정확하게 증가시킬 수 있다.

     

    하지만 불편한 점이 있다.

    • Data2 클래스와 관련된 일인데 Counter라는 별도 클래스를 추가로 사용해야 한다.
    • 생성자의 매개변수도 추가되고 생성자가 복잡해진다. 생성자를 호출하는 부분도 복잡해진다.

    6. static 변수 2

    public class Data3 {
        public String name;
        public static int count; // static 변수 or 정적 변수 or 클래스 변수
    
        public Data3(String name) {
            this.name = name;
            count++;
        }
    }
    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);
    
            // 추가
            // 인스턴스를 통한 접근(가능하지만 권장하지 않음)
            Data3 data4 = new Data3("D");
            System.out.println(data4.count);
    
            // 클래스를 통한 접근(보통 이 방법을 권장함)
            System.out.println(Data3.count);
        }
    }

    맴버 변수 앞에 static을 붙이게 되면 static 변수, 정적 변수, 클래스 변수라 한다.

    객체가 생성되면 생성자에서 정적 변수 count의 값을 하나 증가시킨다.

    7. static 변수 3

    static 변수의 용어정리를 해보자.

    public class Data3 {
        public String name;
        public static int count; // static 변수 or 정적 변수 or 클래스 변수
    
        public Data3(String name) {
            this.name = name;
            count++;
        }
    }
     

    위 코드에서 name, count 둘다 멤버 변수이다.

    멤버 변수(필드)는 static이 붙은 것과 아닌 것에 따라 다음과 같이 분류할 수 있다.

    멤버 변수(필드)의 종류

    • 인스턴스 변수 : static이 붙지 않은 멤버 변수, 예)name
      • static이 붙지 않은 멤버 변수는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다. 따라서 인스턴스 변수라 한다.
      • 인스턴스 변수는 인스턴스를 만들 때 마다 새로 만들어진다.
    • 클래스 변수 : static이 붙은 멤버 변수, 예)count
      • 클래스 변수, 정적 변수, static 변수등으로 부른다. 용어를 모두 사용하니 주의하자.
      • static이 붙은 멤버 변수는 인스턴스에 무관하게 클래스 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다. 따라서 클래스 변수라 한다.
      • 클래스 변수는 자바 플그램을 시작할 때 딱 1개가 만들어진다. 인스턴스와는 다르게 보통 여러곳에서 공유하는 목적으로 사용된다.

    변수와 생명주기

    • 지역 변수(매개변수 포함) : 지역 변수는 스택 영역에 있는 스택 프레임 안에 보관된다. 메서드가 종료되면 스택 프레임도 제거되는데 이때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다. 따라서 지역 변수는 생존 주기가 짧다.
    • 인스턴스 변수 : 인스턴스에 있는 멤버 변수를 인스턴스 변수라 한다. 인스턴스 변수는 힙 영역을 사용한다. 힙 영역은 가비지 컬렉션이 발새하기 전까지는 생존하기 때문에 보통 지역 변수보다 생존 주기가 길다.
    • 클래스 변수 : 클래스 변수는 메서드 영역의 static 영역에 보관되는 변수이다. 메서드 영역은 프로그램 전체에서 사용하는 공용 공간이다. 클래스 벼수는 해당 클래스가 JVM에 로딩 되는 순간 생성된다. 그리고 JVM이 종료될 때 까지 생명주기가 이어진다. 긴 생명주기를 가진다.

    static이 정적이라는 이유는 여기 있다. 힙 영여겡서 생성되는 인스턴스 변수는 동적으로 생성되고, 제거된다. 반면에 static인 정적 변수는 거의 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다. 정적 변수는 이름 그대로 정적이다.

    8. static 메서드 1

    이번엔 static이 붙은 메서드에 대해 알아보자.

    예를 들어 "hello"라는 문자열 앞뒤에 *를 붙여서 "*hello*"와 같이 꾸며주는 기능이다.

     

    일단 먼저 지금까지 학습한 것을 토대로 코드를 만들어보자.

    public class DecoUtil1 {
        public String deco(String str) {
            return "*" + str + "*";
        }
    }
    public class DecoMain1 {
        public static void main(String[] args) {
            String s = "hello java";
            DecoUtil1 decoUtil1 = new DecoUtil1();
            String deco = decoUtil1.deco(s);
    
            System.out.println("before : " + s);
            System.out.println("after : " + deco);
        }
    }
    

    앞서 개발한 deco()메서드를 호출하기 위해선 DecoUtil1의 인스턴스를 먼저 생성해야 한다. 근데 deco()의 기능은 멤버 변수도 없고, 단지 기능만 제공할 뿐이다. 인스턴스가 필요없는 이유는 멤버변수들을 사용하는 목적이 큰데, 이 메서드는 사용하는 인스턴스 변수도 없고, 단순히 기능만 제공된다.

    Static메서드

    public class DecoUtil2 {
        public static String deco(String str) {
            return "*" + str + "*";
        }
    }
    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이 붙었다. 이렇게 하면 정적 메서드를 만들 수 있다. 이 정적 메서드는 정적 변수처럼 인스턴스 생성 없이 클래스 명을 통해서 바로 호출할 수 있다.

    위 코드는 객체 생성 없이 바로 클래스명+.(dot)+메서드명 으로 바로 호출할 수 있다.

    정적 메서드 덕분에 불필요한 객체 생성없이 편리하게 메서드를 사용할 수 있었다.

    클래스 메서드

    메서드 앞에도 static을 붙일 수 있다. 이것을 정적 메서드 또는 클래스 메서드라 한다. 정적 메서드라는 용어는 static이 정적이라는 뜻이기 때문이고, 클래스 메서드라는 용어는 인스턴스 생성 없이 마치 클래스에 있는 메서드를 바로 호출하는 것 처럼 느껴지기 때문이다.

     

    인스턴스 메서드

    static이 붙지 앟은 메서드는 인스턴스를 생성해야 호출할 수 있다.

    9. static 메서드 2

    정적 메서드는 객체 생성없이 클래스에 있는 메서드를 바로 호출할 수 있다는 장점이 있다.

    하지만 정적 메서드는 언제나 사용할 수 있는 것이 아니다.

     

    정적 메서드 사용법

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

    예제를 통해 정적 메서드 사용법을 익혀 보자.

    public class DecoData {
        private int instanceValue;
        private static int staticValue;
    
        public static void staticCall() {
    //        instanceValue++;//인스턴스 변수 접근, complie error
    //        instanceMethod();//인스턴스 메서드 접근, complie error
    
            staticValue++; // 정적 변수 접근
            staticMethod(); // 정적 메서드 접근
        }
    
        public static void staticCall(DecoData data) {
            data.instanceValue++;
            data.instanceCall();
        }
    
        public void instanceCall() {
            instanceValue++;// 인스턴스 변수 접근
            instanceMethod();// 인스턴스 메서드 접근
    
            staticValue++; // 정적 변수 접근
            staticMethod(); // 정적 메서드 접근
        }
    
        private void instanceMethod() {
            System.out.println("instanceValue = " + instanceValue);
        }
    
        private static void staticMethod() {
            System.out.println("staticValue = " + staticValue);
        }
    }

    이번 예제에선느 접근 제어자를 적극 활용해서 필드를 포함한 외부에서 직접 필요하지 않는 기능은 모두 막아 두었다.

    • instanceValue는 인스턴스 변수이다.
    • staticValue는 정적 변수(클래스 변수)이다.
    • instanceMethod()는 인스턴스 메서드이다.
    • staticMethod()는 정적 메서드(클래스 메서드)이다.

    staticCall()메서드를 보자.

    이 메서드는 정적 메서드이다. 따라서 static만 사용할 수 있다. 정적 변수, 정적 메서드에는 접근할 수 있지만, static이 없는 인스턴스 변수나 인스턴스 메서드에 접근하면 컴파일 오류가 발생한다.

     

    instanceCall() 메서드를 보자.

    이 메서드는 인스턴스 메서드이다. 모든 곳에서 공용인 static을 호출할 수 있다. 따라서 정적 변수, 정적 메서드에 접근할 수 있다. 물론 인스턴스 변수, 인스턴스 메서드에도 접근 가능하다.

    10. static 메서드 3

    용어 정리

    멤버 메서드의 종류

    • 인스턴스 메서드 : static이 붙지 않는 멤버 메서드
    • 클래스 메서드 : static이 붙은 멤버 메서드
      • 클래스 메서드, 정적 메서드, static 메서드 등으로 불린다.

    static이 붙지 않은 멤버 메서드는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다. 따라서 인스턴스 메서드라 한다. static이 붙은 맴버 메서드는 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다. 따라서 클래스 메서드라 한다.

     

    정적 메서드 활용

    정적 메서드는 객체 생성이 필요 없이 메서드의 호출만으로 필요한 기능을 수행할 때 주로 사용한다. 예를 들어 간단한 메서드 하나로 끝나는 유틸리티성 메서드에 자주 사용한다. 수학의 여러가지 기능을 담은 클래스를 만 들 수 있는데, 이 경우 인스턴스 변수 없이 입력한 값을 계산하고 반환하는 것이 대부분이다. 이럴 때 정적 메서드를 사용해서 유틸리티성 메서드를 만들면 좋다.

    정적 메서드 접근 법

    static 메서드는 static 변수와 마찬가지로 클래스를 통해 바로 접근할 수 있고, 인스턴스를 통해서도 접근할 수 있다.

    public class DecoDataMain {
        public static void main(String[] args) {
            ...
    
            //추가
            //인스턴스를 통한 접근
            DecoData data3 = new DecoData();
            DecoData.staticCall();// 권장하지 않는 방법
    
            //클래스를 통한 접근
            DecoData.staticCall();
        }
    }

    둘의 차이는 없지만, 클래스를 통해서 접근하도록 하자.

    왜냐하면 인스턴스를 통해서 접근하면 저 메서드가 클래스 메서드인지 인스턴스 메서드인지 혼동이 올 수도 있기 때문이다.

     

    static import

    정적 메서드를 사용할 때 해당 메서드를 자주 호출해야 한다면 static import를 고려하자.

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

    main() 메서드는 정적 메서드

    인스턴스 생성 없이 실행하는 가장 대표적인 메서드가 바로 main() 메서드이다.

    main() 메서드는 프로그램을 시작하는 시작점이 되는데, 생각해보면 객체를 생성하지 않아도 main() 메서드가 작동했다. 이것은 main() 메서드가 static이기 때문이다. 물론 더 정확히 말하자면 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 수 있다. 따라서 정적 메서드인 'main()' 메서드가 같은 클래스에서 호출하는 메서드도 정적 메서드로 선언해서 사용했다.

    main() 메서드와 static 메서드 호출 예시

    public class ValueDataMain {
        public static void main(String[] args) {
            ValueData valueData = new ValueData();
            add(valueData);
        }
    
        static void add(ValueData valueData) {
            valueData.value++;
            System.out.println("숫자 증가 value=" + valueData.value);
        }
    }

    'JAVA' 카테고리의 다른 글

    JAVA - final  (1) 2024.01.08
    JAVA - 자바 메모리 구조와 static - 연습문제  (0) 2024.01.06
    JAVA - 접근제어자 연습문제  (1) 2024.01.04
    JAVA - 접근 제어자  (1) 2024.01.04
    JAVA - 패키지  (0) 2024.01.04
Designed by Tistory.