Java 언어와 실행 플랫폼
Java를 배우기 시작할 때 문법보다 먼저 잡아야 할 것은 소스 코드가 운영체제에서 실행되기까지의 경계입니다.
Java 컴파일러는 소스를 특정 CPU 명령이 아니라 JVM이 읽는 바이트코드로 바꿉니다.
이 중간 표현 덕분에 같은 클래스 파일을 여러 운영체제에서 실행할 수 있습니다.
다만 JVM이 차이를 흡수한다고 해서 파일 경로, 문자 인코딩, 네이티브 라이브러리까지 저절로 같아지는 것은 아닙니다.
이 절에서는 언어와 플랫폼을 분리해 보고 Java가 잘 맞는 문제를 판단하는 기준을 세웁니다.
소스와 바이트코드는 역할이 다르다
.java는 사람이 작성하는 소스이고 .class는 JVM 명령을 담은 바이트코드입니다.
배포 단위와 수정 단위를 구분하면 컴파일이 필요한 이유가 선명해집니다.
JDK와 JVM의 경계를 먼저 긋는다
JDK에는 javac, java, 진단 도구와 표준 라이브러리가 들어 있고 JVM은 클래스 로딩과 실행을 담당합니다.
개발 환경과 실행 환경을 같은 말로 부르면 오류 위치를 찾기 어려워집니다.
PlatformLayers.java는 현재 실행 환경에서 언어 버전, JVM 이름, 운영체제 이름을 각각 읽습니다. 세 값의 출처를 나누어 보면 “Java 버전”이라는 한마디에 서로 다른 층이 섞이는 일을 피할 수 있습니다.
public class PlatformLayers {
public static void main(String[] args) {
Runtime.Version version = Runtime.version();
System.out.println("feature=" + version.feature());
System.out.println("vm=" + System.getProperty("java.vm.name"));
System.out.println("os=" + System.getProperty("os.name"));
// byte[] machineCode = PlatformLayers.class; // 컴파일 오류: Class 객체는 기계어가 아니다
}
}출력 세 줄은 각각 언어 버전, 가상 머신 구현, 운영체제라는 서로 다른 층을 가리킵니다. 마지막 주석은 Class 객체가 실행 파일의 바이트 배열이 아니라 JVM이 읽은 타입 정보라는 점을 컴파일러가 먼저 막아 주는 사례입니다.
feature=25
vm=<현재 JVM 이름>
os=<현재 운영체제 이름>출력의 JVM과 운영체제 이름은 환경마다 다르지만 feature는 JDK 25에서 25여야 합니다.
javac PlatformLayers.java가 문법과 타입을 확인합니다. 이 단계의 오류는 JVM을 시작하기 전에 소스 위치와 함께 보고됩니다.
검사를 통과하면 PlatformLayers.class가 생깁니다. 이 파일은 특정 Windows나 Linux 실행 파일이 아니라 JVM 명령을 담습니다.
java PlatformLayers가 현재 운영체제용 JVM을 시작하고 main을 찾습니다. 플랫폼 차이는 이 마지막 실행 층에서 흡수됩니다.
이식성은 JVM 위에서 만들어진다
클래스 파일은 공통 형식이지만 JVM 구현은 운영체제와 CPU에 맞게 제공됩니다.
따라서 이식성은 하드웨어 차이가 사라진다는 뜻이 아니라 JVM이 그 차이를 대신 처리한다는 뜻입니다.
두 명령이 성공하는 이유는 운영체제가 .class를 직접 실행해서가 아니라 java 실행기가 JVM을 시작하고 클래스를 찾아 주기 때문입니다. 같은 파일을 다른 운영체제로 옮길 때도 그곳에 맞는 JVM은 따로 필요하다는 조건까지 포함해야 이식성을 정확히 설명할 수 있습니다.
// 같은 PlatformLayers.class를 각 운영체제용 JVM이 읽는다.
javac PlatformLayers.java
java PlatformLayers// 클래스 파일은 CPU에서 직접 실행하는 파일이 아니다.
// ./PlatformLayers.class // 운영체제 실행 파일이 아님Java가 잘 맞는 문제를 고른다
장기간 유지하는 서버, 풍부한 라이브러리가 필요한 업무 시스템, 여러 플랫폼에서 같은 동작을 원하는 도구에 Java가 잘 맞습니다.
짧은 네이티브 유틸리티나 극단적인 메모리 제약에서는 다른 선택이 더 단순할 수 있습니다.
바이트코드는 곧바로 기계어가 아니다
JVM은 바이트코드를 해석하거나 JIT 컴파일해 현재 CPU의 기계어로 바꿉니다.
클래스 파일과 실제 실행 중 생성된 네이티브 코드를 같은 산출물로 보지 않습니다.
JIT 컴파일은 실행 중 자주 호출되는 코드를 현재 CPU에 맞게 최적화하는 과정이므로, 소스 컴파일과 시점도 산출물도 다릅니다. 처음에는 javac → .class → JVM 경로만 확실히 잡고, JIT은 성능을 조사할 때 다시 꺼내 보는 편이 좋습니다.
Java 코드는 언어, 바이트코드, JVM이라는 세 층으로 읽으면 오류와 성능 문제를 어느 층에서 조사할지 빠르게 결정할 수 있습니다.