본문 바로가기

언어 기초/JAVA

자바 메모리구조와 static [JAVA | 학습을 위한 자료 | 김영한 자바 기본]

자바 메모리 구조와 static

  • 메서드 영역: 클래스 정보를 보관한다. 이 클래스 정보가 붕어빵 틀이다.
  • 스택 영역: 실제 프로그램이 실행되는 영역이다. 메서드를 실행할 때 마다 하나씩 쌓인다.
  • 힙 영역: 객체(인스턴스)가 생성되는 영역이다. new 명령어를 사용하면 이 영역을 사용한다. 쉽게 이야기해서 붕어빵 틀로부터 생성된 붕어빵이 존재하는 공간이다. 참고로 배열도 이 영역에 생성된다.

메서드 영역(Method Area)

메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리한다. 이 영역은 프로그램의 모든 영역에서 공유한다.

  • 클래스 정보: 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드등 모든 실행 코드가 존재한다.
  • static 영역: static 변수들을 보관한다. 뒤에서 자세히 설명한다.
  • 런타임 상수 풀: 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다. 예를 들어서 프로그램에 "hello"라는 리터럴 문자가 있으면 이런 문자를 공통으로 묶어서 관리한다. 이 외에도 프로그램을 효율적으로 관리하기 위한 상수들을 관리한다. (참고로 문자열을 다루는 문자열 풀은 자바 7부터 힙 영역으로 이동했다.)

스택 영역(Stack Area)

자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레임은 지역 변수, 중간 연산 결 과, 메서드 호출 정보 등을 포함한다.

  • 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거된다.

힙 영역(Heap Area)

객체(인스턴스)와 배열이 생성되는 영역이다. 가비지 컬렉션(GC)이 이루어지는 주요 영 역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.

객체가 생성될 때, 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한 새로운 메모리 할당은 없다. 메서드는 메서드 영역에서 공통으로 관리되고 실행된다.

static 변수

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

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

클래스 변수는 자바 프로그램을 시작할 때 딱 1개가 만들어진다. 인스턴스와는 다르게 보통 여러곳에서 공유하는 목적으로 사용된다.

변수와 생명주기

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

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

인스턴스를 통한 접근 data4.count 정적 변수의 경우 인스턴스를 통한 접근은 추천하지 않는다. 왜냐하면 코드를 읽을 때 마치 인스턴스 변수에 접근하는것 처럼 오해할 수 있기 때문이다.

클래스를 통한 접근 Data3.count 정적 변수는 클래스에서 공용으로 관리하기 때문에 클래스를 통해서 접근하는 것이 더 명확하다. 따라서 정적 변수에 접근할 때는 클래스를 통해서 접근하자.

static 메서드

정적 메서드는 정적 변수처럼 인스턴스 생성 없이 클래스 명을 통해서 바로 호출할 수 있다.

static 이붙은정적메서드는객체생성없이클래스명+ . (dot)+메서드명으로 바로 호출할 수 있다. 정적 메서드 덕분에 불필요한 객체 생성 없이 편리하게 메서드를 사용했다.

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

정적 메서드가 인스턴스의 기능을 사용할 수 없는 이유 정적 메서드는 클래스의 이름을 통해 바로 호출할 수 있다. 그래서 인스턴스처럼 참조값의 개념이 없다. 특정 인스턴스의 기능을 사용하려면 참조값을 알아야 하는데, 정적 메서드는 참조값 없이 호출한다. 따라서 정적 메서드 내부에서 인스턴스 변수나 인스턴스 메서드를 사용할 수 없다.

 

인스턴스를 통한 접근 data3.staticCall() 정적 메서드의 경우 인스턴스를 통한 접근은 추천하지 않는다. 왜냐하면 코드를 읽을 때 마치 인스턴스 메서드에 접근하는 것 처럼 오해할 수 있기 때문이다.

클래스를 통한 접근 DecoData.staticCall() 정적 메서드는 클래스에서 공용으로 관리하기 때문에 클래스를 통해서 접근하는 것이 더 명확하다. 따라서 정적 메서드에 접근할 때는 클래스를 통해서 접근하자.

 

main() 메서드

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

main() 메서드는 프로그램을 시작하는 시작점이 되는데, 생각해보면 객체를 생성하지 않아도 main() 메서드가 작동했다. 이것은 main() 메서드가 static 이기 때문이다.

정적 메서드는 정적 메서드만 호출할 수 있다. 따라서 정적 메서드인 main() 이 호출하는 메서드에는 정적 메서드를사용했다. 물론 더 정확히 말하자면 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 수 있다. 따라서 정적 메서드인 main()메서드가 같은 클래스에서 호출하는 메서드도 정적 메서드로 선언해서 사용했다.

 

문제와 풀이
문제1: 구매한 자동차 수

다음 코드를 참고해서 생성한 차량 수를 출력하는 프로그램을 작성하자. `Car` 클래스를 작성하자.

public class CarMain {
     public static void main(String[] args) {
         Car car1 = new Car("K3");
         Car car2 = new Car("G80");
         Car car3 = new Car("Model Y");

		Car.showTotalCars(); //구매한 차량 수를 출력하는 static 메서드
     }
}

 

문제2: 수학 유틸리티 클래스
다음 기능을 제공하는 배열용 수학 유틸리티 클래스( `MathArrayUtils` )를 만드세요.

`sum(int[] array)` : 배열의 모든 요소를 더하여 합계를 반환합니다.

average(int[] array)` : 배열의 모든 요소의 평균값을 계산합니다.

min(int[] array)` : 배열에서 최소값을 찾습니다.
`max(int[] array)` : 배열에서 최대값을 찾습니다.

**요구사항**
`
MathArrayUtils` 은 객체를 생성하지 않고 사용해야 합니다. 누군가 실수로 `MathArrayUtils` 의 인스턴스

를 생성하지 못하게 막으세요.
실행 코드에 `static import` 를 사용해도 됩니다.

public class MathArrayUtilsMain {
     public static void main(String[] args) {
         int[] values = {1, 2, 3, 4, 5};
         System.out.println("sum=" + MathArrayUtils.sum(values));
         System.out.println("average=" + MathArrayUtils.average(values));
         System.out.println("min=" + MathArrayUtils.min(values));
         System.out.println("max=" + MathArrayUtils.max(values));
      }
}