
해당 글은 뭘 잘 모를 때 작성된 글이라 많은 뇌피셜과 오류가 있습니다. 그러나 기록을 위해 남겨둡니다.
자바가 내걸은 표어인 이 문장은 보통 객체지향프로그래밍 수업을 들을 때 첫 시간에, 그리고 시중에 판매되는 자바 입문서 첫 장에서 만나게 된다. 자바 언어의 특징으로 '자바는 운영 체제에 독립적이다.', '윈도우에서 개발된 프로그램을 수정 없이 맥OS 또는 리눅스에서도 실행할 수 있다.'(출처: 이것이 자바다, 한빛미디어) 라는 이야기가 자주 등장한다.
이 부분에 대해 처음 접했을 때는 별로 아는 게 없다보니 대충 '아 그렇구나 정말 좋은걸?' 하고 넘어갔지만, 이제와서 다시 보니 너무 모호한 설명이 아닌가 하는 의심이 들었다.
그럼 C와 C++ 같은 다른 언어는 운영 체제마다 소스 코드를 다시 써야하는가? 'Write Once, Run Somewhere'인가?
이거는 좀 애매모호하다. C나 C++는 특정 운영 체제 API를 직접 호출할 수 있다. 윈도우 API를 호출하는 프로그램을 짰다면, 그건 분명 리눅스나 맥에서는 제대로 동작하지 않을 것이다.
예를 들어 Redis는 대부분 C언어로 짜여있으며, 윈도우에서는 기본적으로 동작하지 않는다. 이는 Redis가 POSIX 표준을 따르는 리눅스 환경에서 동작하도록 설계되었으며, POSIX 인터페이스를 따르고 해당 API를 사용하기 때문이다. 특히나 커넥션 관리를 위해 I/O 모델로서 epoll 모델을 사용하는데, 이게 리눅스의 기능이다. 따라서 윈도우에서 Redis를 실행시키려면 이와 같은 리눅스 기능을 지원하는 WSL 같은 가상 환경에서 실행해야 한다.
반면, 마찬가지로 C언어로 쓰인 대표적인 소프트웨어 중 하나인 SQLite는 Redis와 달리 온 동네방네 어디든 깔아서 쓸 수 있다(OS별 소스코드 간 어느정도 차이가 있는지는 잘 모르겠다). 이는 SQLite가 대부분 표준 C 라이브러리만 사용하기 때문이다.
또 다른 의문으로 '그럼 자바로는 특정 OS API를 절대 못 쓰는가?' 하면 또 그건 아니다. 당연히 특별한 이유가 없으면 추천되지는 않겠지만, 설계나 구현 의도에 따라 JNI(Java Native Interface) 등을 이용하면, 자바에서 직접 C나 C++ 등 다른 언어로 작성된 외부 라이브러리를 호출하거나 특정 플랫폼에서 지원하는 API를 사용할 수 있다. 이러한 코드를 네이티브 코드(Native Code)라고 한다. 물론 이건 본인이 직접 플랫폼 독립성을 포기한 것이니 비교 대상으로 적절치 않을지도 모르겠지만, 그렇게 따지면 C/C++ 입장에서도 마찬가지다. 그러므로 프로그래밍 언어 레벨에서의 플랫폼 독립성은 설계의 차이이다.
내가 이런 혼란을 겪은 이유는 일반적인 자료에서 자바를 소개하는 방식과 저 슬로건 때문이라고 본다. 'Write Once, Run Anywhere'의 반대는 'Write Once, Run Somewhere' 혹은 'Should Write Multiple Times'가 아니다. 자바를 C나 C++같은 다른 프로그래밍 언어와 비교하는 거라면 반대의 의미는 'Write Once, Compile Anywhere'이다.
나는 해당 문구를 자바를 소개하는 책과 강좌에 함께 적어줘야 한다고 생각한다. C언어의 등장 자체가 이식성의 발전을 의미했다. 그리고 그 비교 대상은 어셈블리어다. 어셈블리어는 혹시 컴퓨터 구조를 공부했다면 알겠지만, 프로세서가 제공하는 기능을 직접 사용한다. 기계가 달라지면 어셈블리어도 달라지는 것이다. 어셈블리어가 바로 그 'Should Write Multiple Times'인 녀석이다. C언어는 그 위에 추상화 계층을 덧붙였다. 바로 컴파일러다. C언어의 문법과 코드는 어느 곳에서든 같다. 맞는 어셈블리어로 바꿔주는 역할을 이제 컴파일러에게 떠넘겼기 때문이다.

이제 개발자는 현재 기기에 알맞는 컴파일러만 찾아서 컴파일을 시행해주면 된다. 물론 말했다시피 컴파일된 어셈블리어는 해당 컴퓨터 구조의 기능을 담고 있다. 특정 기계를 위해 컴파일 된 프로그램은 해당 기계에서만 실행할 수 있다. 다른 기계를 위해서는 맞는 컴파일러로 다시 컴파일해줘야 한다. 이러한 C언어를 이야기하는 표현이 바로 Write Once, Compile Anywhere이다. 자바는 이 부분에 추상화를 하나 추가한다. 그것이 자바 컴파일러와 JVM이다.
자바 컴파일러는 어셈블리 코드가 아니라 자바 바이트 코드(Java Byte Code, .class)를 만든다. JVM(Java Virtual Machine)은 이 코드를 마치 자기가 컴퓨터인 것 마냥(자체적인 메모리와 실행 엔진을 가진다) 실행한다. 프로그램 실행을 위해서는 실제 하드웨어와 상호작용 해야하고, 이를 위해서 당연히 실행 환경마다 다른 JVM이 필요하다. 하지만 지원하는 환경에 적응하는 것은 JVM의 역할이다. 자바 컴파일러는 사실상 이 세상에 존재하는 컴퓨터 구조는 JVM 하나 뿐이라고 생각하고 컴파일을 하는 것이다. 그러므로 한 번 컴파일된 바이트 코드는, 이상한 짓을 하지 않았다면, 어느 JVM에서든 실행된다. 맞는 JVM을 설치했다면 같은 코드가 어느 곳에서든 실행되는 것이다.
다시 정리하면, 컴퓨터는 JVM을 실행시키며, JVM이 바이트코드를 실행시키는 것이다.

살짝 어그로성 소제목이지만 만약 컴파일러를 우리가 흔히 아는
A computer software that translates (compiles) source code written in a high-level language (e.g., C++) into a set of machine-language instructions that can be understood by a digital computer’s CPU.
컴파일러는 고급 언어(C++ 등)로 작성된 소스 코드를 디지털 컴퓨터의 CPU가 이해할 수 있는 기계어 명령 집합으로 변환(컴파일)하는 컴퓨터 소프트웨어이다.
출처: Britannica
라고 하면, 자바 컴파일러로 만든 바이트 코드는 컴퓨터가 직접 실행하는 코드가 아니기 때문에 쬐끔 다르다고 볼 수 있지 않겠는가? - 물론 JVM 자체를 버추얼이긴 해도 '기계'로 본다면 가능하겠지만 - 아무튼 다르다!
또한 JVM이라는 계층이 하나 추가되었을 뿐, 자바가 마법처럼 세상 모든 운영체제 및 환경에서 실행될 수 있다고 착각해서는 안 된다. 말했다시피 JVM이 있어야 한다. 해당 환경에 맞는 JVM이 없다면 못하는 거다. 마치 C언어를 컴파일해 실행하려고 해도 해당 환경을 지원하는 컴파일러가 없다면 못하는 것과 같다.
그러니 슬로건을 좀 더 나같은 코린이를 위한 것으로 바꾸자면
Write Once Without Native Code, Run Anywhere With a Compatible JVM
정도가 되지 않을까? 물론 전혀 캐치하지는 않지만ㅋㅋㅋ 실제 자바가 어떤 것인지에 대한 느낌은 더 잘 와닿는 것 같다. 앞으로 나는 저렇게 기억할 것이다.
자바에 대해 헷갈리던, 혹은 잘 와닿지 않던 개념들의 이미지가 전보다 많이 잡히는 것 같다. 다음에는 JDK 혹은 JRE와 JVM 등에 대해 좀 더 자세히 알아봐야겠다.