ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 자료구조(스택, 힙), static
    언어/Java 2024. 11. 9. 09:54

    https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

     

    김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런

    김영한 | 실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문

    www.inflearn.com

    아래 내용은 위 링크에서 더 자세히 볼 수 있습니다.

    자바메모리 구조 - 실제

    • 메서드 영역: 프로그램을 실행하는 데 필요한 공통 데이터. 이 영역은 프로그램의 모든 영역에서 공유한다.
      • 클래스 정보(Method Area): 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드가 존재
    • 스택 영역(Stack Area): 자바 실행 시 하나의 실행 스택이 생성. 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함.
      • 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거된다.
    • 힙 영역(Heap Area): 객체(인스턴스)와 배열이 생성되는 영역이다. 가비지 컬렉션이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.

    참고: 각 스레드는 본인의 스택 영역이 존재한다.

    • 같은 클래스로 부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 수 있지만, 메서드는 공통된 코드를 공유한다.
    • 따라서 객체가 생성될 때 변수에는 메모리가 할당되지만, 메서드에 대한 새로운 메모리 할당은 없다.
    • 쉽게 말해 붕어빵틀은 한번만 사면 되지, 붕어빵 1개 만들 때 마다 붕어빵 틀 1개 사서 만드는 것이다. 돈 많아?

    스택과 큐 자료구조

    스택

    자료구조: 데이터가 어떻게 보관되고 관리하는 지에 대한 구조, 나는 자료구조란 데이터를 어떻게 효율적으로 보관하는 지에 대한 방법

    package memory;
    
    public class JavaMemoryMain1 {
        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() 을 실행.
      • main() 스택 프레임은 내부에 args 라는 매개변수를 갖는다. args 는 나중에
    • main → method1() 호출, method1() 스택 프레임 생성….
      • method1() 는 m1, cal 지역 변수(매개변수 포함)를 가지므로 해당 지역 변수들이 스택 프레임이 포함된다.
    • method1 → method2 를 호출. method2() 스택 프레임 생성.
      • m2도 지역변수이므로 스택 프레임.
    • 종료될 때, 스택 peak 의 스택 프레임과, 매개변수가 제거된다.
    • method2() 제거 후 method1() 의 시작이 아닌 method2() 를 호출한 시점으로 간다.
    • 더 이상 호출할 메서드가 없고, 스택 프레임도 완전히 비워졌을 때, 자바는 프로그램을 정리하고 종료한다.

    package memory;
    
    public class JavaMemoryMain2 {
        public static void main(String[] args) {
            System.out.println("main start");
            method1();
            System.out.println("main end");
        }
    
        static void method1() {
            System.out.println("mehod1 start");
            Data data1 = new Data(10); // 0x001이 들어간다. 힙 영역에
            method2(data1);
            System.out.println("mehod1 start");
        }
    
        static void method2(Data data2) { // 
            System.out.println("method2 start");
            System.out.println("method2 data.value: " + data2.getValue());
            System.out.println("method2 end");
        }
    }
    
    
    • method1() 이 종료된 직후 heap 영역에서 더 이상 Data 인스턴스를 참조하는 곳이 없다.
    • 따라서 가비지 컬렉션이 참조가 사라진 인스턴스를 찾아 제거한다.

    참고: 힙 영역 외부(메서드)가 아닌, 힙 영역 안에서만 인스턴스끼리 서로 참조하는 경우에도 GC의 대상이 된다. 예를 들어 힙 영역 안에서 객체끼리 참조하는 경우.


    static

    package static1;
    
    public class DataCountMain1 {
        public static void main(String[] args) {
            Data1 data1 = new Data1("A");
            System.out.println("A count = " + data1.count);
            Data1 data2 = new Data1("B");
            System.out.println("B count = " + data2.count);
            Data1 data3 = new Data1("C");
            System.out.println("C count = " + data3.count);
        }
    }
    
    package static1;
    
    public class Data1 {
        public String name;
        public int count;
    		
    		// 의도: 객체 생성된 수 count
        public Data1(String name) {
            this.name = name;
            count ++;
        }
    }
    
    • 왜 증가하지 않을까?
      • 각 인스턴스들은 힙영역에서 각자의 메모리를 할당 받기 때문이다. 그리고 count 변수도 새로 만들어 진다.
    • 인스턴스에 사용되는 멤버변수는 count 값은 인스턴스끼리 공유되지 않는다. 따라서 원하는 답을 구할 수 없다.
    • 그러면 증가시키려면 어떻게 해야 될까?
      • 없지 않나? 그 이유는 생성자로 생성할 때, 처음에 넣어준거니깐. 다시 들어가서 만들면 다른 놈이 만들어질 거 같아서.
      → 변수를 서로 공유하면 가능하다.
    • → count 를 static 으로 선언하면 가능하다.
    package static1;
    
    public class DataCountMain2 {
        public static void main(String[] args) {
            Counter counter = new Counter();  //
            Data2 data1 = new Data2("A", counter);
            System.out.println("A count = " + counter.count);
    
            Data2 data2 = new Data2("B", counter);
            System.out.println("A count = " + counter.count);
        }
    }
    
    
    package static1;
    
    public class Data2 {
        public String name;
    
        public Data2(String name, Counter counter) {
            this.name = name;
            counter.count++;
        }
    }
    
    
    package static1;
    
    public class Counter {
        public int count;
    
    }
    
    
    • Data2 클래스와 관련된 일인데 Counter 라는 별도의 클래스를 추가로 사용해야 한다.
    • 생성자의 매개변수도 추가되고, 생성자가 복잡해 진다.

    static 변수 2 (+2024.11.12 추가)

    package static2;
    
    public class Data3 {
        public String name;
        public static int count;
    
        public Data3(String name) {
            this.name = name;
            count++;
        }
    }
    • static 이 붙은 멤버 변수는 메서드 영역에서 관리한다.
    • Data3("A"); 인스턴스를 생성하면 생성자가 호출된다.
    • 생성자에는 count++ 이 있다. count는 static이 붙은 정적변수이다. 정적 변수는 인스턴스가 아니라 메서드 영역에서 관리하기 떄문에 메서드드 영역에 있는 count 값이 하나 증가.
    package static2;
    
    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("A");
            System.out.println("A count = " + Data3.count);
    
            Data3 data3 = new Data3("A");
            System.out.println("A count = " + Data3.count);
        }
    }
    • static 이 붙은 정적 변수에 접근하라면 Data3.count와 같이 클래스명 + . (dot) 변수명으로 접근하면 된다.
    • 참고로 Data3의 생성자와 같이 자신의 클래스에 있는 정적 변수라면 클래스명 생략할 수 있다.
    • 붕어빵 틀은 1개 이므로 클래스 변수도 하나만 존재한다. 반면에 인스턴스인 붕어빵은 인스턴스의 수 만큼 변수가 존재한다.

    static 변수3

    public class Data3 {
        public String name;
        public static int count;
    }
    • 멤버 변수(필드) : name , count
    • 멤버 변수의 종류
      • 인스턴스 변수:
        • static 이 안 붙은거
        • 따라서 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다.
      • 클래스 변수(정적 변수, static 변수):
        • 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속
        • 클래스 변수는 자바를 실행할 때 딱 1개만 만들어져 공유된다.
    • 변수와 생명주기
      • 지역 변수(매개변수 포함)
        • 스택 영역에 있는 스택 프레임에 보관
        • 메서드가 종료되면 스택 프레임도 제거
      • 인스턴스 변수
        • 힙 영역 을 사용.
        • GC가 발생하기 전까지 생존
      • 클래스 변수
        • 메서드 영역의 static 영역에 보관

    static 이 정적이라는 이유다. 힙 영역에 생성되는 인스턴스 변수는 “동적”으로 생성되고 제거. 반면에 static 인 정적 변수는 거의 프로그램 실행 시점에 딱 만들어지고 프로그램 종료 시점에 제거.

    Data3 data4 = new Data3("D");
    System.out.println(data4.count);
            
    System.out.println(Data3.count);
    • 인스턴스를 통한 접근
      • 정적 변수의 경우 인스턴스를 통한 접근은 추천하지 않음. 왜냐하면 코드를 읽을 때 마치 인스턴스 변수에 접근하는 것 처럼 오해할 수 있기 때문이다.
    • 클래스를 통한 접근
      • 정적 변수는 클래스에서 공용으로 관리하기 때문에 클래스를 통해서 접근하는 것이 더 명확하다.
    • static 메서드
      • 뭔가 멤버 변수에 접근하고 그런거 없이 기능만 수행할 때, main 에서 객체를 새로 생성하거나 그럴 필요 없이 static 을 사용해 공융으로 사한다.
    package static3;
    
    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 3

    //import static static3.DecoData.*;
    import static static3.DecoData.staticCall;
    • import 해주게 되면
    • 원래는 DecoData.staticCalll() 이렇게 호출해야 하지만
    • staticCall() 만으로도 호출이 가능하다.
    • 정적 메서드 뿐만 아니라 정적 변수에서도 사용 가능할 수 있다.

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

    • 인스턴스 생성 없이 실행하는 가장 대표적인 메서드가 바로 main() 메서드.
    • main() 메서드는 프로그램을 시작하는 시작점이 되는 데, 생각해보면 객체를 생성하지 않아도 main() 메서드가 작동했다. 이것은 main() 메서드가 static 이기 때문이다.
    • 정적 메서드는 정적 메서드만 호출할 수 있다. 따라서 정적 메서드인 main() 이 호출하는 메서드에는 정적 메서드만 사용 !!
    • 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 수 있다.

    '언어 > Java' 카테고리의 다른 글

    [Java] 접근제어자  (1) 2024.11.08
    [Java] 생성자, 패키지  (0) 2024.11.06
    [Java] 객체 지향 프로그래밍  (4) 2024.11.06
    [Java] 변수와 초기화, null, nullPointerException  (0) 2024.11.05
    [Java] 기본형 vs 참조형  (0) 2024.11.05
Designed by Tistory.