JVM의 바이트코드 실행 Optimizing Java #3

BaekGwa·2024년 8월 9일

Optimizing Java

목록 보기
3/5
post-thumbnail

이글은, 오라일리 사의 Optimizing Java 책을 참고하여, 정리한 내용이다.

컴파일 과정/.class 구조 살펴보기

  • 자바 소스 코드는 실행되기까지 많은 변환 가정을 거칩니다.
  • 첫번째로는 작성한 코드를 자바 컴파일러(javac)를 통해 컴파일 하여, 바이트코드로 만드는 것입니다.
  • .java -> .class
//.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
//.class
CAFEBABE 00000034 00110003 00000002 00000001
00000000 07000005 07000006 07000007 07000008
00000009 0000000A 00000000 00000001 00000000
00000003 0000000D 00000002 0000000E 0000000F
00000010 00000011 00000012 00000000 00000001
00000003 00000013 00000003 00000014 00000015
00000016 00000017 00000018 00000019 0000001A
0000001B 00000000 00000001 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
  • 바이트 코드는 특정 컴퓨터 아키텍처에 특정하지 않은, 중간 표현형입니다.
  • 따라서, 이식성이 좋아 컴파일된 소프트 웨어는 JVM 지원 플랫폼 어디서든 실행 할 수 있습니다.
    • Scala 컴파일러 scalac로 컴파일한 바이트코드도, JVM에서 문제없이 작동
  • .class 파일은 아래의 구조를 갖추고 있고, JVM은 클래스를 로드할 때, 올바른 형식을 준수 하고 있는지 빠짐없이 검사합니다.

속성(컴포넌트)별 어떤 역할인지에대해서는 다루지 않습니다.


컴파일 파일 살펴보기 (.class)

  • 다음과 같은 간단한 예제 코드를 컴파일 해보겠습니다.
package test;

public class mainTest {
    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            System.out.println("Hello World");
        }
    }
}

//명령어 실행
javac mainTest.java
javap -v mainTest.class

public test.mainTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: bipush        10
         5: if_icmpge     22
         8: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #13                 // String Hello World
        13: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: iinc          1, 1
        19: goto          2
        22: return
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 5: 16
        line 8: 22
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 2
          locals = [ int ]
        frame_type = 250 /* chop */
          offset_delta = 19
}
  • 먼저 mainTest class에는 메서드가 main 하나지만, javac가 클래스 파일에 디폴트 생성자를 자동 추가하므로, 2개가 생성 됩니다.

  • 위의 javap 역어셈블러를 통해 변환된 코드를 간단하게 해석해보자면 이런 순서로 해석할 수 있습니다.

    1. 생성자에서 this 레퍼런스를 스택 상단에 올려 놓는 aload_0 명령을 실행
    2. invokespecial 명령을 호출. 슈퍼 생성자들을 호출하고 객체를 생성하는 등 특정 작업을 담당하는 인스턴스 메서드를 실행.
    3. main() 메서드 실행. iconst_0으로 정수형 상수 0을 평가 스택에 푸쉬, istore_1로 상수값을 오프셋 1에 위치한 지역 변수(루프의 i)에 저장.
    4. 지역 변수 오프셋은 0부터 시작하며, 0번째 엔트리는 무조건 this
    5. 오프셋 1의 변수를 스택으로 다시 로드(iload_1)한 뒤, 상수 10을 푸쉬(bipush10)한 다음 if_icmpage로 둘을 비교 합니다. (정수값이 10보다 같거나 큰가?)
    6. 처음부터 몇번은 비교테스트 실패할 테니, 8번 명령으로 넘어갑니다. (성공할 경우에는 22번 명령어로 넘어가 종료)
    7. System.out 정적 메서드를 해석(getstatic #2)하고 상수 풀에서 "Hello World"라는 스트링(문자열)을 로드(ldc #3) 합니다.
    8. invokevirtual 명령어로 이 클래스에 속한 인스턴스 메서드를 실행.
    9. iinc를 만나, 정수값은 하나 증가, goto를 만나서 다시 2번 명령어로 되돌아갑니다.

학습한 순서 정리 해보기.

  • 그렇다면, 이전에 알아본 클래스로더와 컴파일을 연결시키면 어떤 과정이 될까?

  • 아마 이러한 과정이 될 것이다. (코드는 위와 동일)

    1. 개발자가 .java 코드를 컴파일 진행. -> .class 파일 생성.
    2. 개발자가, java 명령어로, 애플리케이션 실행
    3. JVM이 main() 메서드의 시작점을 설정하고 클래스 로더에 로딩 요청을 보냄.
    4. 클래스 로더가 Main 클래스의 .class 파일을 찾아서 메모리에 로드함.
    5. 클래스 로더.class 파일을 읽어 바이트코드를 해석하고 클래스 정의 및 초기화를 수행함.
    6. main() 메서드 실행 중 필요에 따라 추가적으로 다른 클래스들을 로드함.
    7. 바이트 코드를 읽어보며 동작을 실행. 먼저 생성자 메서드를 읽고, iconst_0를 실행... 등등

여담


역시, IDE의 딸깍 실행 버튼과 커맨드는 편하다. 이래서 IDE 쓰는 것 같다...

참고 문서
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1

profile
현재 블로그 이전 중입니다. https://blog.baekgwa.site/

0개의 댓글