1. JVM, 컴파일

박형진·2022년 7월 3일
0

자바 스터디

목록 보기
1/1

1. JVM(Java Virtual Machine) 이란?

JVM은 바이트코드를 OS가 해석할 수 있는 기계어로 변환시켜 주는 기능을 수행한다. 그렇기 때문에 코틀린이나 스칼라처럼 자바 언어가 아니더라도 바이트코드로 컴파일 될 수있는 언어라면 JVM에서 실행이 가능한 것이다.

내 컴퓨터 운영체제에 맞는 JVM을 설치한다면, JVM을 통해 자바 프로그램을 실행할 수 있다.

2. JVM의 구성요소

1. Class Loader:

런타임 시점에 .class(=바이트코드)를 JVM(=Runtime Data Area)에 에 할당한다

2. Execution Engine:

메모리에 할당된 바이트코드를 컴퓨터가 읽을 수 있는 기계어로 해석한다. 해석 방식에는 interpreter와 캐싱을 사용한 JIT컴파일러 방식이 존재한다.

interpreter: line by line 방식으로 한 줄 씩 해석한다.

JIT 컴파일러(Just-In-Time compiler): 동적 번역(dynamic translation)이라고도 불리는 이 기법은 프로그램의 실행 속도를 향상시킨다. 프로그램이 실행 중인 런타임에 실제 기계어로 변환해 주는 컴파일러를 의미합니다. 즉, JIT 컴파일러는 자바 컴파일러가 생성한 자바 바이트 코드를 런타임에 바로 기계어로 변환하는 데 사용합니다.

interpreter 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 그 다음부터는 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드는 캐싱되기 때문에 한 번 컴파일된 코드는 재사용이 가능하여 빠르게 실행할 수 있다. 하지만 무조건 JIT컴파일이 수행되는 것은 아니다. 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.

GC: 가비지 컬렉터, 참조되지 않는 인스턴스들을 제거한다.

3. Runtime Data Area:

JVM이 OS로부터 할당받는 메모리 공간이다.

  1. PC Register(Thread 마다 존재): 현재 수행 중인 JVM 명령어의 주소를 가진다.
  2. stack(Thread 마다 존재): Thread가 메서드를 호출할 때, 메서드의 정보(매개 변수, 지역 변수, 리턴 값)가 Frame 이라는 단위로 JVM stack에 저장된다. 그리고 메서드 호출이 종료될 때 스택에서 프레임이 제거된다.
  3. Native Method stack(Thread 마다 존재): Java 외의 언어로 작성된 메서드들을 위한 Stack
  4. Heap(모든 Thread 공유): 인스턴스들이 저장되며 GC의 관리 대상이된다.
  5. Method Area(모든 Thread 공유): Class Loader가 저장한 Class의 정보(클래스인지 인터페이스인지, 메서드 이름, 메서드 코드...)를 분류하여 저장한다. 상수를 저장하는 Runtime Constant Pool 도 존재한다. Method Area 는 클래스 데이터를 위한 공간이라면 Heap 은 인스턴스를 위한 공간이다. 마찬가지로 GC의 관리 대상이 된다.

3. 컴파일, CMD 명령어

컴파일(Compile)은 주어진 언어로 작성된 컴퓨터 프로그램을 다른 언어의 동등한 프로그램으로 변환하는 프로세스이다.

.java파일을 JVM이 사용할 수 있는 .class(=바이트코드)파일로 컴파일하는 과정이다.

1. Test.java

public class Test {
	public static void main(String[] args) {
		System.out.println("Hello Test");		
	}	
}

2. javac

C:\Users\..\Desktop\cmd>javac Test.java

JDK에 속한 자바 컴파일러인 javac를 사용하여 .java 파일을 바이트코드인 .class로 컴파일한다. 이제 JVM이 해석할 수 있는 언어로 변경된 것이다.

3. java

C:\Users\..\Desktop\cmd>java Test
Hello Test

실행과정 ->
1. JVM의 Class Loader를 통해 Test.class 파일을 JVM으로 로딩
2. JVM의 Execution engine이 인터프리터와 JIT를 사용하여 바이트코드를 기계어로 변환한다.
3. 변환된 기계어들은 Runtime Data Area에 배치되어 명령을 수행한다.

바이트 코드 출력: javap -c

C:\Users\박형진\Desktop\cmd>javap -c Test.class
Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String Hello Test
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

.class 파일을 역어셈블하여 인간이 이해할 수 있는 바이트코드로 출력하는 명령어이다. Opcode는 1byte의 크기를 가지기 때문에 바이트코드라고 불린다. 바이트코드는 2^8개의 명령어를 가질 수 있다.

기계어(=바이너리코드)와 바이트코드 둘 다 이진 코드로 작성된다. 하지만 기계어는 OS가 읽을 수 있는 언어이고 바이트코드는 JVM이 읽을 수 있는 언어이다. JVM은 바이트코드를 바이너리코드로 변환시켜주는 역할을 수행한다.

profile
안녕하세요!

0개의 댓글