
자바의 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나눌 수 있다.
메서드 영역: 프로그램을 실행하는데 필요한 공통 데이터를 관리한다. 이러한 데이터에는 클래스, static, 상수등이 있으며, 메서드 영역은 모든 영역에서 공유한다.
클래스 변수 = 정적 변수 = static 변수
스택 영역: 자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보등을 포함한다.
힙 영역: 인스턴스와 배열이 생성되는 영역이다. 가비지 컬렉션이 이루어지는 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.
스택 영역은 스택이라는 자료구조를 떠올리면 이해하기가 쉽다. 스택은 가장 나중에 들어간 데이터가 가장 빨리 나오는 LIFO 구조로 아래 코드를 통해 쉽게 이해할 수 있다.
public class JavaMemoryMain1 {
public static void main(String[] args) {
System.out.println("main start");
method1();
System.out.println("main end");
}
static void method1() {
System.out.println("method1 start");
method2();
System.out.println("method1 end");
}
static void method2() {
System.out.println("method2 start");
System.out.println("method2 end");
}
}
실행결과는 아래와 같다.
main -> method1 -> method2순서대로 호출을 하였지만method2 -> method1 -> main순서대로 코드가 종료된다. 이를 통해 스택 영역은 LIFO의 형태로 동작됨을 확인할 수 있다.main start method1 start method2 start method2 end method1 end main end
메소드 영역은 클래스나 static 등 공통으로 사용하는 정보가 저장되는 영역이고, 힙영역은 주로 인스턴스나 배열등 new 키워드를 써서 만들어진 정보가 저장되는 영역이다.
왜 이러한 영역들을 구분해놨을까? 클래스는 붕어빵 틀과 같은 설계도인데 이 설계도가 여러개 있을 필요가 전혀 없다. 붕어빵 장사할 때 붕어빵 기계 10개 있을 필요가 있을까? 장사 망한다 이러한 이유로 공통적으로 쓰이는 영역인 메소드 영역과 개별 객체 혹은 배열이 저장되는 힙영역으로 나눔으로써 메모리 최적화를 수행할 수 있다.
사실 이번 장의 하이라이트는 static 이 친구이다. 크게 static 변수와 static 메서드로 구분된다. 본격적으로 시작하기 전에 아래의 질문을 던져보자
크게 정적 변수와 정적 메서드로 나뉘는데 이들은 클래스에서 공통으로 사용할 변수나 유틸 메서드가 필요할 때 사용한다. 먼저 static 변수부터 어떻게 활용되는지 살펴보자. 내가 만약에 자동차 판매 횟수를 카운팅 해보고 싶다고 해보자. 이때 아래와 같이 코드를 짰다.
public Car {
public int count;
public String name;
public Car(String name){
this.name = name;
count++;
}
}
public static void main(String[] args){
Car car1 = new Car("포르쉐")
Car car2 = new Car("G80")
Car car3 = new Car("테슬라")
sout("count=" + car3.count) //잉? 왜 1인데?
}

단순히 각 인스턴스 변수인 count 값이 올라가니, 당연히 카운트가 안될 수 밖에 없다.
그럼 어떻게 해결할까? static 변수 쓰면 된다.
public Car {
public static int count;
public String name;
public Car(String name){
this.name = name;
count++;
}
}
public static void main(String[] args){
Car car1 = new Car("포르쉐")
Car car2 = new Car("G80")
Car car3 = new Car("테슬라")
sout("count=" + car3.count) //이제는 3이다.
}

car1 car2 car3 모두 메서드 영역의 count 변수의 값을 증가시킨다.다음으로 static 메서드는 언제 사용하는지 살펴보자. 이는 수학 합산 계산처럼 간단한 util 메서드로써 활용된다. 클래스를 만들었는데 인스턴스 변수는 필요 없고 단순히 메서드 기능만 제공할 때 사용된다.
static 메서드는 사용할 때 주의사항이 필요하다. 바로 static 메서드는 다른 static 메서드나 static 변수에는 접근할 수 있지만, 인스턴스 메서드와 변수에는 접근할 수 없다는 것이다. 그런데 이것은 너무나도 당연한 얘기이다. 힙영역에 인스턴스가 생성됐는지도 모르고, 어떠한 인스턴스를 가르키는지도 모르는데 어떻게 static 메서드가 인스턴스를 접근할 수 있을까..? 당연히 못한다.
public class DecoData {
private int instanceValue;
private static int staticValue;
public static void staticCall() {
//instanceValue; //인스턴스 변수 접근, compile error
//instanceMethod; //인스턴스 메서드 접근, compile error
staticValue++;
staticMethod();
}
//인스턴스 메서드는 정적, 인스턴스 변수에 모두 접근 가능
public void instanceCall() {
instanceValue++;
instanceMethod();
staticValue++;
staticMethod();
}
private void instanceMethod() {
System.out.println("instanceValue=" + instanceValue);
}
private static void staticMethod() {
System.out.println("staticValue=" + staticValue);
}
}
staticCall 인 정적 메서드는 인스턴스 변수와 메서드에 접근 못한다.
반면 instanceCall 인 인스턴스 메서드는 인스턴스 변수와 정적 변수 모두 접근 가능하다.
참고로 클래스 변수를 호출할 때 아래와 같이 2가지 방식이 있다.
Car car1 = new Car("포르쉐")
car1.count #방법1
Car.count #방법2
이제 static 에 대해서 이해했으므로 드디어 main 함수에 대해서 이해할 수 있다. main 함수는 프로그램 호출시 가장 먼저 시작되는 곳으로 메서드 영역에 올라온다.(static 이므로)
public class JavaMemoryMain1 {
public static void main(String[] args) {
method1();
}
static void method1() {
System.out.println("method1 start");
}
}
main 문 밖의 함수를 호출하기 위해서 항상 static 을 붙여준 것이 이제 보일까?
main은 정적 메서드이니까 다른 정적 메서드만을 참조할 수 있기 때문이다.