본 글은 김영한 님의 『김영한의 실전 자바 - 기본편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.
자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요
본 게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.
자바 메모리 구조
자바 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나눌 수 있다. 비유를 하자면 메서드 영역이 붕어빵 틀, 힙 영역이 생성된 붕어빵이 존재하는 공간이다.
메서드 영역
메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리하고, 프로그램의 모든 영역에서 공유한다.
메서드 영역에는 클래스 정보, static 영역, 상수 풀 구역이 존재한다.
- 클래스 정보
- 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드가 존재한다.
- static 영역
- static 변수들을 보관한다.
- 런타임 상수 풀
- 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다.
스택 영역
스택 영역은 자바 실행 시, 하나의 실행 스택이 생성되는데, 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함한다.
- 스택 프레임 : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거된다.
- 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
힙 영역
힙 영역은 객체(인스턴스)와 배열이 생성되는 영역이다. 가비지 컬렉션(GC)이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC 에 의해 제거된다.
메서드로 인스턴스 생성 과정
- 특정 클래스로 100 개의 인스터스 생성 → 힙 메모리에 100개의 인스턴스가 생김
- 각각의 인스턴스는 내부에 변수와 메서드를 가진다.
- 같은 클래스로 부터 생성된 객체라도, 내부의 변수 값은 다를 수 있지만, 메서드는 공통된 코드를 공유한다.
- 객체가 생성될 때, 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한 메모리 할당은 없다.
- 메서드는 메서드 영역에서 공통으로 관리되고 실행된다.
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) 을 사용해, 마치 클래스에 직접 접근하는 것처럼 느껴진다.
위 코드의 동작 방식을 알아보자.
- static 이 붙은 멤버 변수는 메서드 영역에서 관리한다.
- static 이 붙은 멤버 변수는 인스턴스 영역에 생성되지 않는다.
- Data(”A”) 인스턴스를 생성하면 생성자가 호출된다.
- 생성자에는 count++ 코드가 있는데, count 는 static 이 붙은 정적 변수이다. 정적 변수는 인스턴스 영역이 아니라 메서드 영역에서 관리하므로, 메서드 영역에 있는 count 의 값이 하나 증가한다.
- B,C 도 마찬가지로 동작한다.
static 이 붙은 정적 변수에 접근하려면 Data3.count 와 같이 클래스명 + .(dot) + 변수명으로 접근하면 된다. 또한 Data3 의 생성자와 같이 자신의 클래스에 있는 정적 변수라면 클래스명을 사용할 수 있다.
이렇게 static 변수를 사용한 덕분에 공용 변수를 사용해서 편리하게 문제를 해결할 수 있다.
변수 용어 정리
public class Data{
public String name;
public static int count;
}
멤버 변수(필드) 의 종류
- 인스턴스 변수 : 예 ) name
- static 이 붙지 않은 멤버 변수는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다.
- 인스턴스를 만들 때 마다 새로 만들어진다.
- 클래스 변수 : 예 ) count
- 클래스 변수, 정적 변수, static 변수 라고 부른다.
- 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다.
- 자바 프로그램을 시작할 때 딱 1개가 만들어진다.
변수와 생성주기
- 지역 변수(매개변수 포함)
- 지역 변수는 스택영역에 있는 스택 프레임 안에 보관된다.
- 메서드가 종료되면 스택 프레임이 제거 되는데, 이 때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다.
- 인스턴스 변수
- 인스턴스에 있는 멤버 변수를 인스턴스 변수라 한다.
- 인스턴스 변수는 힙 영역을 사용하는데, 힙 영역은 GC(가비지 컬렉션)가 발생하기 전까지는 생존한다.
- 클래스 변수
- 메서드 영역은 프로그램 전체에서 사용하는 공용 공간이다.
- 클래스 변수는 해당 클래스가 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 인스턴스 메서드
- 클래스 메서드
- 메서드 앞에 static 키워드를 붙이면, 정적 메서드, 클래스 메서드라고 한다.
- static 만 사용할 수 있다.
- 클래스 내부의 기능을 사용할 때, 정적 메서드는 static 이 붙은 정적 메서드나 정적 변수만을 사용할 수 있다.
- 인스턴스 메서드
- static 이 붙지 않은 메서드는 인스턴스를 생성해야 호출할 수 있는데, 이를 인스턴스 메서드라고 한다.
- 모든 곳에서 static 을 호출할 수 있다.
- 정적 메서드는 공용 기능이다. 따라서 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서 static 을 호출할 수 있다.
그럼 정적 메서드가 인스턴스 메서드를 사용할 수 없는 이유가 무엇일까?
정적 메서드는 클래스의 이름을 통해 바로 호출할 수 있다. 그래서 인스턴스처럼 참조값의 개념이 없다. 특정 인스턴스의 기능을 사용하려면 참조값을 알아야 하는데, 정적 메서드는 참조값 없이 호출한다. 따라서 정적 메서드 내부에서 인스턴스 변수나 인스턴스 메서드를 사용할 수 없다.
'인프런 > 김영한 자바' 카테고리의 다른 글
| [김영한의 실전 자바 기본편] - 9. 상속 (0) | 2025.09.15 |
|---|---|
| [김영한의 실전 자바 기본편] - 8. final (0) | 2025.09.12 |
| [김영한의 실전 자바 기본편] - 6. 접근 제어자 (0) | 2025.09.09 |
| [김영한의 실전 자바 기본편] - 5. 패키지 (0) | 2025.09.09 |
| [김영한의 실전 자바 기본편] - 4. 생성자 (0) | 2025.09.09 |