인프런/김영한 자바

[김영한의 실전 자바 기본편] - 7. 자바 메모리와 static

sson-coding 2025. 9. 11. 17:02
본 글은 김영한 님의 『김영한의 실전 자바 - 기본편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.

자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요 

『김영한의 실전 자바 - 기본편』 보러 가기

본 게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.


자바 메모리 구조

자바 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나눌 수 있다. 비유를 하자면 메서드 영역이 붕어빵 틀, 힙 영역이 생성된 붕어빵이 존재하는 공간이다.

메서드 영역

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

메서드 영역에는 클래스 정보, static 영역, 상수 풀 구역이 존재한다.

  1. 클래스 정보
    • 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드가 존재한다.
  2. static 영역
    • static 변수들을 보관한다.
  3. 런타임 상수 풀
    • 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다.

스택 영역

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

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

힙 영역

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

메서드로 인스턴스 생성 과정

  1. 특정 클래스로 100 개의 인스터스 생성 → 힙 메모리에 100개의 인스턴스가 생김
  2. 각각의 인스턴스는 내부에 변수와 메서드를 가진다.
    • 같은 클래스로 부터 생성된 객체라도, 내부의 변수 값은 다를 수 있지만, 메서드는 공통된 코드를 공유한다.
  3. 객체가 생성될 때, 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한 메모리 할당은 없다.
  4. 메서드는 메서드 영역에서 공통으로 관리되고 실행된다.

static

static 키워드는 주로 멤버 변수와 메서드에 사용된다. 먼저 멤버 변수에 왜 필요한지 알아보자.

static 변수

인스턴스 내부 변수에 카운트 저장

# Data1
package static1;
public class Data1 {
	 public String name;
	 public int count;
	 public Data1(String name) {
		 this.name = name;
		 count++;
	 }
}

# DataCountMain1
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);
	 }
}

# 실행 결과
A count=1
B count=1
C count=1

이 코드는 객체가 생성될 때 마다 생성자를 통해 인스턴스의 멤버 변수인 count 값을 증가시키기 위한 코드이다.

하지만 이 코드는 기대한 대로 작동하지 않는다. 그 이유는 무엇일까? 바로, 객체를 생성할 때 마다 인스턴스는 새로 만들어지고, 인스턴스에 포함된 count 변수도 새로 만들어지기 때문이다.

그럼 특정 클래스에서 공용으로 함께 사용할 수 있는 변수를 어떻게 사용해야 될까?

static 변수 사용

위에서의 문제점을 해결하고 코드를 작성하기 위해 static 키워드를 사용하면 공용으로 함께 사용하는 변수를 만들 수 있다.

# Data3
package static1;
public class Data3 {
	 public String name;
	 public static int count; //static
	 public Data3(String name) {
		 this.name = name;
		 count++;
	 }
}

# DataCountMain3
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);
	}
}

# 실행결과
A count=1
B count=2
C count=3

코드를 보면 static int count 가 있다. 기존 변수 타입(int ) 앞에 static 키워드가 붙어있다. 이렇게 멤버 변수에 static 을 붙이게 되면 static 변수, 정적변수, 클래스 변수라고 한다.

DataCountMain 코드를 보면 count 정적 변수에 접근하는 방법이 특이한걸 볼 수 있다. 클래스 명에 .(dot) 을 사용해, 마치 클래스에 직접 접근하는 것처럼 느껴진다.

위 코드의 동작 방식을 알아보자.

  1. static 이 붙은 멤버 변수는 메서드 영역에서 관리한다.
    • static 이 붙은 멤버 변수는 인스턴스 영역에 생성되지 않는다.
  2. Data(”A”) 인스턴스를 생성하면 생성자가 호출된다.
  3. 생성자에는 count++ 코드가 있는데, count 는 static 이 붙은 정적 변수이다. 정적 변수는 인스턴스 영역이 아니라 메서드 영역에서 관리하므로, 메서드 영역에 있는 count 의 값이 하나 증가한다.
  4. B,C 도 마찬가지로 동작한다.

static 이 붙은 정적 변수에 접근하려면 Data3.count 와 같이 클래스명 + .(dot) + 변수명으로 접근하면 된다. 또한 Data3 의 생성자와 같이 자신의 클래스에 있는 정적 변수라면 클래스명을 사용할 수 있다.

이렇게 static 변수를 사용한 덕분에 공용 변수를 사용해서 편리하게 문제를 해결할 수 있다.


변수 용어 정리

public class Data{
	public String name;
	public static int count;
}

멤버 변수(필드) 의 종류

  1. 인스턴스 변수 : 예 ) name
    • static 이 붙지 않은 멤버 변수는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다.
    • 인스턴스를 만들 때 마다 새로 만들어진다.
  2. 클래스 변수 : 예 ) count
    • 클래스 변수, 정적 변수, static 변수 라고 부른다.
    • 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다.
    • 자바 프로그램을 시작할 때 딱 1개가 만들어진다.

변수와 생성주기

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

static 메서드

예제를 통해 살펴보자.

# DecoUtil
package static2;
public class DecoUtil {
	 public String deco(String str) {
		 String result = "*" + str + "*";
		 return result;
 }
}

# DecoMain
package static2;
public class DecoMain1 {
 public static void main(String[] args) {
	 String s = "hello java";
	 DecoUtil utils = new DecoUtil();
	 String deco = utils.deco(s);
	 System.out.println("before: " + s);
	 System.out.println("after: " + deco);
 }
}

# 실행 결과
before: hello java
after: *hello java*

위 코드는 deco() 메서드를 호출하기 위해 DecoUtil 의 인스턴스를 생성해야 한다. 그런데 deco() 라는 기능은 멤버변수도 없고, 단순히 기능만 제공할 뿐이다. 그러면 인스턴스를 만들 필요가 있을까?

그럴 때 static 메서드를 사용하면 된다.

# DecoUtil
package static2;
public class DecoUtil {
 public static String deco(String str) {
	 String result = "*" + str + "*";
	 return result;
 }
}

# DecoMain
package static2;
public class DecoMain {
 public static void main(String[] args) {
	 String s = "hello java";
	 String deco = DecoUtil.deco(s);
	 System.out.println("before: " + s);
	 System.out.println("after: " + deco);
 }
}

# 실행 결과
before: hello java
after: *hello java*

DecoUtil2.deco(s) 코드를 보자. static 이 붙은 정적 메서드는 객체 생성 없이 .(dot) 을 사용해 바로 호출할 수 있다. 정적 메서드 덕분에 불 필요한 객체 생성 없이 편하게 메서드를 사용할 수 있다.

클래스 메서드 vs 인스턴스 메서드

  1. 클래스 메서드
    • 메서드 앞에 static 키워드를 붙이면, 정적 메서드, 클래스 메서드라고 한다.
    • static 만 사용할 수 있다.
      • 클래스 내부의 기능을 사용할 때, 정적 메서드는 static 이 붙은 정적 메서드나 정적 변수만을 사용할 수 있다.
  2. 인스턴스 메서드
    • static 이 붙지 않은 메서드는 인스턴스를 생성해야 호출할 수 있는데, 이를 인스턴스 메서드라고 한다.
    • 모든 곳에서 static 을 호출할 수 있다.
      • 정적 메서드는 공용 기능이다. 따라서 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서 static 을 호출할 수 있다.

그럼 정적 메서드가 인스턴스 메서드를 사용할 수 없는 이유가 무엇일까?

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