-
[Java] 자료구조(스택, 힙), static언어/Java 2024. 11. 9. 09:54
김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런
김영한 | 실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 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 - 메서드 영역: 프로그램을 실행하는 데 필요한 공통 데이터. 이 영역은 프로그램의 모든 영역에서 공유한다.