학습 자료

자바 코드는 대체 누가 실행하는가


Article

자바를 처음 배울 때 대부분 이렇게 시작한다. 파일을 하나 만들고 Hello.java 라고 이름 붙인 다음, 코드를 적는다.

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

그리고 터미널에 이렇게 친다.

java Hello
터미널
  • 오류: 기본 클래스 Hello을(를) 찾거나 로드할 수 없습니다.
  • 원인: java.lang.ClassNotFoundException: Hello

파일은 분명히 거기 있다. 이름도 틀리지 않았다. 그런데 못 찾겠다고 한다.

이 에러는 자바가 코드를 어떻게 실행하는지 모르면 계속 만난다. 그리고 그 구조를 한 번 이해하고 나면, 앞으로 만날 수많은 "왜 안 되지?"가 미리 풀린다.

이번 강의는 문법을 하나도 다루지 않는다. 대신 Hello.java 라는 텍스트 파일이 화면에 "안녕"을 찍기까지, 그 사이에 무슨 일이 벌어지는지 본다.

자바 코드는 스스로 실행되지 않는다

.java 파일은 그냥 글자가 적힌 텍스트 파일이다. 메모장으로 열어도 읽힌다. 확장자를 .txt 로 바꿔도 내용은 그대로다.

컴퓨터는 이 글자들을 그 자체로는 이해하지 못한다. public 이 무슨 뜻인지, System.out.println 이 뭘 하라는 건지 모른다.

그러니 누군가 이 텍스트를 읽고, 무슨 뜻인지 해석하고, 실제 작업으로 옮겨줘야 한다. 자바는 그 일을 두 사람에게 나눠 맡긴다. 이게 자바의 가장 큰 특징이고, 아까 그 에러의 원인이기도 하다.

사람의 글자를 기계의 글자로

첫 번째 담당자는 컴파일러다. 이름은 javac 다. java compiler 의 준말이다.

javac.java 파일을 읽어서 .class 파일을 만들어낸다.

javac Hello.java

이 명령을 치면 화면에는 아무것도 안 뜬다. 대신 같은 폴더에 Hello.class 라는 파일이 새로 생긴다. 아무 말이 없다는 건 잘 됐다는 뜻이다. 자바 컴파일러는 칭찬에 인색하다.

코드가 실행되기까지
  1. Hello.java사람이 읽는 텍스트
  2. javac컴파일러가 번역한다
  3. Hello.class바이트코드
  4. JVM읽고 실행한다
  5. 화면에 "안녕"

두 번째 담당자가 마지막 칸의 JVM 이다. Java Virtual Machine, 자바 가상 머신이다. java 명령을 치면 JVM 이 뜬다.

.class 안에는 무엇이 들어 있나

Hello.class 를 메모장으로 열면 깨진 글자만 잔뜩 나온다. 사람이 읽으라고 만든 파일이 아니기 때문이다.

그래도 안을 들여다보는 도구가 있다. javap 다.

javap -c Hello.class
터미널
  • Compiled from "Hello.java"
  • public class Hello {
  • public Hello();
  • Code:
  • 0: aload_0
  • 1: invokespecial #1
  • 4: return
  • public static void main(java.lang.String[]);
  • Code:
  • 0: getstatic #7
  • 3: ldc #13
  • 5: invokevirtual #15
  • 8: return
  • }

getstatic, ldc, invokevirtual, return. 우리가 쓴 적 없는 단어들이다.

public Hello(); 라는 덩어리도 눈에 걸린다. 우리는 그런 걸 쓴 적이 없다. javac 가 몰래 하나 만들어 넣었다. 이름이 클래스 이름과 똑같은 이 덩어리를 생성자라고 부르는데, 11강에서 만난다. 지금은 "안 쓴 것도 생길 수 있다"는 것만 봐두자.

실제로 돌려보면 각 줄 뒤에 // Field java/lang/System.out:... 같은 주석이 더 붙는다. 여기서는 지웠다.

이건 자바가 아니다. 사람이 읽으라고 만든 글자가 아니라, JVM 이 읽으라고 만든 글자다. 이걸 바이트코드라고 부른다.

우리가 System.out.println("안녕") 이라고 쓴 한 줄이, JVM 이 알아듣는 네 개의 명령으로 잘게 쪼개진 것이다.

그래서 아까 왜 안 됐나

이제 맨 처음의 에러를 다시 보자.

java Hello

java 는 JVM 을 띄우는 명령이다. JVM 은 바이트코드를 읽는다. 그러니 Hello.class 를 찾는다.

그런데 우리는 javac 를 부른 적이 없다. Hello.class 가 아직 세상에 없다. JVM 은 Hello.java 를 거들떠보지도 않는다. 자기가 읽을 줄 아는 글자가 아니기 때문이다.

제대로 된 순서
  1. 01

    코드를 쓴다

    파일 이름은 Hello.java. 클래스 이름과 같아야 한다.

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

    컴파일한다

    Hello.class 가 생긴다. 화면에는 아무것도 안 뜬다.

    javac Hello.java
  3. 03

    실행한다

    확장자를 빼고 클래스 이름만 적는다. Hello.class 라고 쓰지 않는다.

    java Hello

3단계에서 java Hello.class 라고 쓰면 또 에러가 난다. java 명령에게 넘기는 것은 파일 이름이 아니라 클래스 이름이기 때문이다. 21강에서 패키지를 배우고 나면 이 이름이 shop.Main 처럼 길어진다.

JVM 은 자바로 만들어지지 않았다

여기서 헷갈리기 쉬운 지점이 하나 있다.

JVM 은 자바 코드를 실행해주는 프로그램이다. 그렇다면 JVM 자신은 무엇으로 만들어졌을까? 자바로 만들었다면, 그 JVM 은 또 누가 실행하나?

가장 널리 쓰이는 JVM 인 HotSpot 은 주로 C++ 로 작성됐다. 운영체제가 직접 실행할 수 있는 보통의 프로그램이다.

자바를 실행해주는 프로그램은 자바가 아니다. 당연해 보이지만, 이걸 짚고 넘어가면 다음 이야기가 쉬워진다.

"한 번 쓰면 어디서든 돈다"

자바를 소개할 때 늘 따라붙는 문구다. Write Once, Run Anywhere. 이게 무슨 뜻인지 이제 설명할 수 있다.

Hello.class 는 어느 운영체제에서 만들었든 내용이 똑같다. 윈도우에서 컴파일한 .class 를 그대로 맥에 복사해도 돌아간다.

운영체제마다 다른 것은 .class 가 아니라 JVM 이다.

같은 바이트코드, 다른 JVM

윈도우

Hello.class + 윈도우용 JVM

  • .class 파일은 완전히 동일하다
  • 윈도우용 JVM 이 바이트코드를 읽는다
  • 화면 출력은 윈도우 방식으로 처리한다
  • JVM 자체는 .exe

맥 · 리눅스

Hello.class + 그쪽 JVM

  • .class 파일은 완전히 동일하다
  • 맥용 JVM 이 같은 바이트코드를 읽는다
  • 화면 출력은 맥 방식으로 처리한다
  • JVM 자체는 .exe 가 아니다

운영체제 차이를 JVM 이 혼자 뒤집어쓴다. 그 덕분에 우리가 쓴 코드는 어디서든 똑같이 동작한다.

정확히 말하면 "한 번 쓰면 어디서든 돈다"가 아니라 "한 번 쓰면 JVM 이 있는 곳이면 어디서든 돈다" 이다.

JDK, JRE, JVM

자바를 설치하려고 검색하면 이 세 단어가 한꺼번에 쏟아진다. 관계만 잡아두자.

설치하면 딸려오는 것들
  • JDKJava Development Kit — 개발자용 전체 꾸러미
    • 개발 도구코드를 만들 때 쓴다
      • javac.java → .class 컴파일러
      • javap바이트코드를 들여다본다
    • JREJava Runtime Environment — 실행에 필요한 것만
      • JVM바이트코드를 읽고 실행한다
      • 표준 라이브러리String, System, List …

만든 프로그램을 실행만 하려면 JRE 로 충분하다. 코드를 작성하려면 javac 가 필요하니 JDK 를 깔아야 한다.

우리는 코드를 쓸 거니까 JDK 를 설치한다. 그러면 JRE 도 JVM 도 그 안에 다 들어 있다.

정리하면

한 줄로 줄이면 이렇다.

자바 코드는 javac 가 바이트코드로 번역하고, JVM 이 그 바이트코드를 읽어 실행한다.

다시 짚어보기
  1. 01

    .java 는 텍스트다

    그 자체로는 아무 일도 하지 않는다. 사람이 읽으라고 만든 글자다.

  2. 02

    javac.class 로 번역한다

    결과물이 바이트코드다. JVM 이 읽으라고 만든 글자다.

    javac Hello.java
  3. 03

    JVM 이 바이트코드를 실행한다

    파일 이름이 아니라 클래스 이름을 넘긴다.

    java Hello
  4. 04

    JVM 은 운영체제마다 다르다

    바이트코드는 어디서나 같다. 그래서 코드가 옮겨 다닐 수 있다.

  5. 05

    JDK 를 설치한다

    javac 와 JVM 이 함께 들어 있다.

이 시리즈에서 앞으로 배울 문법은 대부분 javac 가 검사하는 영역이다. 타입을 틀리게 쓰면 실행되기 전에 컴파일러가 먼저 잡아낸다. 자바를 배우는 일의 절반은 컴파일러와 대화하는 법을 익히는 것이다.

다음 강의에서는 아까 그냥 지나친 한 줄을 뜯어본다.

public static void main(String[] args)

단어가 다섯 개나 붙어 있다. JVM 은 왜 하필 이 이름을 찾는지, 그리고 저 단어들이 각각 무슨 뜻인지 본다. 솔직히 말하면 2강에서는 전부 설명하지 않는다. publicstatic 의 진짜 의미는 한참 뒤에 가서야 답할 수 있다.