티스토리 뷰

자바의 특징

자바는 Cross-platform language로 널리 알려져 있습니다. 자바 언어로 작성한 코드는 한 번 작성하고 컴파일하면 운영체제의 종류에 관계 없이 그 코드를 그대로 실행할 수 있습니다. 이렇게 코드를 실행하는 환경(platform)에 관계 없이 동일한 코드를 실행할 수 있다는 의미에서 자바를 cross-platform language라고 부릅니다. 그리고 자바가 가지는 플랫폼 독립성의 특성을 가리켜 'Write-Once, Run-Anywhere'(WORA) 라고 칭합니다.

 

자바의 동작 방식

우리가 작성한 자바 코드는 .java 파일로 저장됩니다. 코드를 실행하려고 하면 자바는 다음과 같은 과정을 거쳐서 코드를 컴파일 및 실행(run) 합니다.

1. 유저가 작성한 코드를 바이트 코드로 컴파일한다.

유저가 작성한 .java 코드는 javac 컴파일러를 거쳐서 바이트 코드로 컴파일됩니다. 컴파일 된 바이트 코드는 .class 확장자를 가진 파일로 저장됩니다. 그래서 intelliJ나 VSCode에서 .java 파일을 실행하면 항상 .class 라는 이름의 파일이 생기는 것을 확인할 수 있습니다.

바이트 코드는 우리가 작성한 코드를 OS가 읽을 수 있도록 컴파일 한 중간 단계의 코드 파일입니다. 이름부터 byte code 인 것처럼 모든 명령을 1바이트로 표현한 코드 파일입니다. 1byte는 8bit에 해당하는데, 이는 곧 $2^8 = 16^2$ 가지의 상태를 표현할 수 있다는 의미이므로 바이트 코드의 모든 라인은 2자리의 16진수로 표현됩니다.

추후에 이 바이트 코드를 JVM이 읽고 실행하게 되므로 바이트 코드를 일종의 JVM을 위한 기계어라고 볼 수도 있습니다.

 

2. 바이트 코드를 JVM이 인터프리팅(interpreting) 한다.

JVM은 Java Virtual Machine 의 줄임말로 바이트 코드(.class 파일)를 운영체제가 읽을 수 있게 번역하는 역할을 합니다. JVM은 운영체제의 종류에 따라 서로 다르게 구현되어 있어서, 같은 바이트 코드를 읽더라도 코드를 실행하는 운영체제에 맞게 번역하여 전달합니다. 따라서 유저는 자바 코드를 실행하는 플랫폼의 종류를 신경 쓰지 않고 동일한 코드를 어디에서나 똑같이 실행할 수 있습니다. 이러한 JVM의 존재 덕분에 자바는 플랫폼 독립성을 가진 언어가 될 수 있습니다.

 

 

자바는 컴파일 언어? 인터프리터 언어?

'컴파일'(compile)이란 프로그래밍 언어로 작성한 코드 파일을 컴퓨터가 읽을 수 있는 형태로 가공하는 작업을 의미합니다. 즉 컴파일은 사람이 작성한 코드를 기계어로 변환하는 작업을 의미하며, 이 작업을 수행하는 것이 컴파일러(compiler) 입니다. 일반적으로 '컴파일 언어'란 컴파일러를 이용해 코드를 컴파일 하고 실행하는 언어를 의미합니다.

반면 '인터프리터(interpreter)'는 컴파일러와 달리 코드 파일 전체를 기계어로 변환하지는 않습니다. 대신 코드를 실행할 때 유저가 작성한 코드를 한 줄씩 기계어로 변환하여 실행하는 방식으로 동작합니다. 이렇게 line-by-line으로 코드를 기계어로 변환하며 실행하는 작업을 인터프리팅(interpreting)이라고 합니다.

 

컴파일러와 인터프리터의 동작 방식은 각자 명확한 장단점이 존재합니다.

  컴파일러 인터프리터
기계어로 변환하는 시점 코드를 실행하기 전에 기계어로 변환 코드를 실행하면서 한 줄씩 기계어로 변환
실행 속도 빠름 느림
에러 발견 시점 실행 전 컴파일 과정에서 에러를 발견 한 줄씩 실행하다가 에러가 발생하는 지점에서 발견
별도의 실행 파일 생성 여부 컴파일 된 별도의 파일을 생성해야 함 별도의 실행 파일을 생성하지 않음
예시 언어 C, C++, Java Java, Python, JavaScript, Ruby

 

 

  • 기계어로 변환하는 시점
    컴파일러는 코드를 실행하기 전에 컴파일을 진행하면서 전체 파일을 기계어로 먼저 변환합니다. 반면 인터프리터는 전체 파일을 기계어로 변환하지 않고 실행 중에 한 줄씩 기계어로 변환합니다.

  • 실행 속도
    컴파일 언어는 파일 전체를 기계어로 번역해 둔 다음, 실행 시에는 만들어 둔 기계어 파일을 실행하기만 합니다. 반면 인터프리터 언어는 코드 파일을 실행하면서 동시에 한 줄씩 코드를 기계어로 번역합니다. 때문에 컴파일 언어가 인터프리터 언어에 비해 실행 시간이 빠릅니다.

  • 에러 발견 시점
    컴파일 언어는 컴파일을 할 때 전체 코드를 기계어로 번역합니다. 에러 검사도 이 시점에 한 번에 이루어집니다. 반면 인터프리터 언어는 전체 파일을 확인하지 않고 실행부터 시작해서 한 줄씩 코드를 기계어로 번역합니다. 때문에 에러가 발생하기 전까지는 코드가 정상적으로 실행되다가, 에러가 발생하는 지점에서 실행이 멈추고 오류를 발견하게 됩니다.

  • 별도의 실행 파일 생성 여부
    자바의 바이트 코드 파일(.class)처럼 컴파일러는 전체 코드를 번역한 별도의 기계어 파일을 생성해야 합니다. 반면 인터프리터 언어는 별도의 실행 파일을 두지 않고 실행할 때마다 한 줄씩 코드를 번역합니다. 때문에 인터프리터 언어는 별도의 실행 파일이 생성되지 않습니다.

 

자바의 동작 방식을 알아볼 때, 자바는 javac 컴파일러를 이용해 컴파일도 하고 JVM에 의해 인터프리팅도 수행한다고 했습니다. 즉 자바는 컴파일 언어인 동시에 인터프리터 언어라고 볼 수 있습니다. 인터프리터를 사용하면 매 실행마다 한 줄씩 코드를 읽고 기계어로 번역해야 하므로 실행 속도가 매우 느립니다. 하지만 JVM이 없다면 자바는 특유의 플랫폼 독립성을 잃게 됩니다. 같은 바이트 코드라도 JVM이 실행 환경에 맞게 인터프리팅 해주기 때문입니다.

 

JIT 컴파일러

인터프리터 언어 특유의 느린 실행 속도를 개선하기 위해, 자바는 JVM에 JIT 컴파일러를 포함시켜 두었습니다. JIT란 Just-In-Time의 줄임말로, 자주 실행되는 바이트 코드를 컴파일 한 것을 저장해 두었다가 필요할 때 제공하는 역할을 수행합니다. JIT 컴파일러 덕분에 자주 실행되는 바이트 코드는 별도의 컴파일을 거치지 않고 바로 실행할 수 있게 됩니다. 따라서 인터프리터 언어 특유의 느린 실행 속도가 상당히 개선됩니다.

 

JDK와 JRE

 

JDK는 Java-Development-Kit 의 줄임말로, 자바를 이용해 프로그램을 개발하기 위한 요소들을 묶어 놓은 파일입니다. 앞서 살펴보았던 JVM, Javac 등을 비롯해 각종 라이브러리와 파일들을 모두 포함하고 있습니다. JRE는 Java-Runtime-Environment 의 줄임말로, 자바로 작성된 프로그램을 실행하기 위한 요소들을 묶어 놓은 파일입니다.

 

가령 Javac 컴파일러는 자바로 작성한 코드를 바이트 코드로 만들어 줍니다. 하지만 이미 완성된 자바 프로그램을 받아서 실행만 하는 사용자라면 이미 컴파일 된 바이트 코드를 가지고 있습니다. 즉 자바 프로그램을 실행만 하는 입장에서는 Javac가 필요하지 않습니다. 이외에도 자바 프로그램을 개발하는 데는 필요하지만, 실행만 할 때는 필요하지 않은 여러 파일들이 존재할 겁니다. 따라서 프로그램의 실행만을 목적으로 하는 사용자를 위해 JDK에서 프로그램 실행에 필요한 파일만 추려 제공하는 것이 JRE 입니다.