예제
JVM의 구성요소와 각각의 역할에 대해서는 이전의 글에서 설명했었다.
오늘은 실제로 소스코드가 어떻게 작동하는지 예시를 통해서 흐름을 알아보자.
예제 코드는 아래와 같다.
public class test {
public static void main(String[] args) {
StaticClass staticClass = new StaticClass();
int a = StaticClass.finalValue;
int b = StaticClass.staticValue;
int c = staticClass.value;
}
static class StaticClass {
static final int finalValue = 2;
static int staticValue = 3;
int value = 1;
}
}
변수를 저렇게 지정한 이유는 나중에 설명하도록 하겠다.
JVM에 구동시키기 위해서는 소스코드를 바이트코드로 컴파일하는 작업이 필요하다.
예시코드를 컴파일하면 test.class, test$StaticClass.class 두 개의 파일로 분할된다.
이제 컴파일된 바이트코드가 Class Loader를 통해서 Runtime Data Area로 이동한다.
Runtime Data Area의 Method Area에 클래스 파일이 들어오면 Execution Engine이 바이트코드를 하나씩 해석한다. 그렇게해서 public static void main(String[] args)메서드부터 하나씩 실행하게 된다.
메서드의 실행은 쓰레드별로 Stack Area에서 실행되며 Frame단위로 쌓인다.
frame은 다음과같이 구성된다.

- Local Variable Array : 지역변수 저장 Array
- Operand Stack : 연산을 위한 Stack
- Current Class, Constant pool, Reference : Class에 대한 정보
method에서 객체가 생성되면 Heap 영역에 인스턴스가 생성된다.
이렇게 작동하다가 다른 클래스의 정보가 필요하면 Class Loader를 통해서 Method Area에 필요한 클래스의 바이트 코드를 동적으로 가져온다.

이렇게 Stack Area에서 쓰레드별로 메서드를 실행하고 필요한 클래스 정보는 Method Area, 인스턴스의 정보는 Heap Area에서 가져온다. PC Register는 메서드의 실행 위치를 저장하는 역할을 한다.
클래스 로딩 시점
위와 같은 흐름으로 자바가 실행된다. 이전 포스트에서 static 변수나 클래스가 초기화되는 시점에 대해서 몰랐었는데 단순히 메모리에 올라가면 초기화한다 하고 결론을 내렸었다. 조금더 자세히 말하자면 Class Loader에 의해서 Method Area에 이동한 시점에 static 초기화가 이루어진다.
여러 블로그 글을 보다가 제어자별로 클래스 로딩 시점이 다르다는 글을 읽었다.
static이나 제어자가 없는 경우는 필자가 예상대로 클래스가 로딩되었으나 static final 제어자의 경우에는 그렇지 않았다.
외부 클래스에서 필드를 가져오는 경우 필연적으로 외부 클래스를 로딩해야한다.
그 클래스의 정보를 가져와야하니 어찌보면 당연한 말이다. 하지만 static fianl 제어자의 상수는 예외였다.
외부 클래스의 static fianl 필드 값을 호출해도 외부 클래스는 Method Area에 올라오지 않았다.
어떻게 된 일일까?
해결 과정
우선 구글링을 통해서 많은 블로그 글을 봤지만 그냥 상수는 미리 올라간다 라는 말만 있어서 명확한 답을 얻지는 못했다. 그러다 스택오버플로우에서 아래의 글을 발견했다.
요약하자면 필자와 같은 고민을 한 어느 외국인이 질문을 올렸고 static final로 지정된 상수는 컴파일 시 외부 클래스의 참조 형태가 아닌 상수로 변경한다는 것이다. static final이므로 값을 변경할 수 없기 때문에 굳이 참조형태를 유지해서 클래스를 로드할 필요가 없기 때문이다.
이 말이 맞는지 확인하기 위해서 예제코드를 컴파일 후 javap 명령어를 이용해서 바이트코드를 읽어봤다.
...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: new #12 // class test$StaticClass
3: dup
4: invokespecial #14 // Method test$StaticClass."<init>":()V
7: astore_1
8: iconst_2
9: istore_2
10: getstatic #15 // Field test$StaticClass.staticValue:I
13: istore_3
14: aload_1
15: getfield #18 // Field test$StaticClass.value:I
18: istore 4
20: return
...
현재 main 메서드에서 final static, static, 일반 field 순으로 데이터를 지역변수에 할당하고 있다.
우선 바이트코드의 명령어를 정리해보자.
- astore : Store reference into local variable
- aload : Load reference from local variable
- iconst : Push int constant
- istore : Store int into local variable
변수 저장 과정을 살펴보면 데이터를 불러온 후 istore를 이용해서 세 값을 저장한다고 추측할 수 있다.
이때, staticValue와 value는 test$StaticClass에서 데이터를 가져오지만 finalValue는 iconst로 가져오는 것을 알수있다.
예시를 통해서 static final은 외부 클래스가 아닌 내부에서 데이터를 가져오는 것을 확인하였고 바이트코드 변환시에 상수가 입력된다는 위의 글의 말이 맞는거같다. 확신하지는 않지만 현재 상태에서는 만족할만한 대답이라고 생각한다.
참고
☕ 클래스는 언제 메모리에 로딩 & 초기화 되는가 ❓
JVM의 클래스 로더 (Class Loader) 자바의 클래스들이 언제 어디서 메모리에 올라가고 클래스 멤버들이 초기화되는지, 원리를 알기위해선 우선 JVM(자바 가상 머신)의 클래스 로더(Class Loader)의 진행
inpa.tistory.com
Chapter 6. The Java Virtual Machine Instruction Set
The wide instruction modifies the behavior of another instruction. It takes one of two formats, depending on the instruction being modified. The first form of the wide instruction modifies one of the instructions iload, fload, aload, lload, dload, istore,
docs.oracle.com
'Java > 개념' 카테고리의 다른 글
| 자바의 자료구조 (Collection) (0) | 2023.05.11 |
|---|---|
| 선형 자료구조 (0) | 2023.05.03 |
| for vs iterator (0) | 2023.01.31 |
| 자바의 구동 방식 - 3 (0) | 2023.01.30 |
| 열겨형 (enum) (0) | 2023.01.22 |