Java와 C#은 정말 비슷할까? VM, JIT, AOT로 다시 보기
Java와 C# 닮은 듯 다른 두 언어
Java와 C#은 가상머신 위에서 돌아간다는 공통점이 있다. 그런데 최근에 두 언어를 다시 배우면서 대충 넘어갔던 부분, 그리고 새롭게 알게된 부분이 있어서 한번 정리해보려고 한다.
VM은 언어마다 하나만 있는게 아니다
나는 Java와 C#에 각각 VM이 한 종류만 존재하는 줄 알고 있었다. 그런데 이번에 Unity 6에 대한 소개글을 보면서 알게 된 것이, VM은 언어마다 여러 종류가 있을 수 있었다. 사실 이게 이번에 개념을 정리하게 된 계기다.
각 언어의 가상머신은 여러 종류가 있는데, Java의 경우 JVM 명세에 맞는 여러 JVM 구현체가 있고, C#의 경우 ECMA-335 CLI에 맞춰 CLR, CoreCLR, Mono 같은 런타임 구현체가 만들어진다.
Java (HotSpot JVM, Zing JVM, GraalVM...)
Oracle의 경우 HotSpot JVM을, Azul의 경우 Zing JVM 등이 있다. 그리고 Graal JIT 컴파일러와 Native Image 등을 제공해 JVM 실행 성능과 배포 방식을 확장한 GraalVM이 있다.
C# (.NET CLR, mono, CoreCLR...)
C#은 기존에 사용하고 있던 Microsoft .NET CLR과 오픈소스 VM인 mono가 있다. 그리고 기존 CLR의 성능을 개선하기 위해 내놓은(그리고 이번에 유니티 6가 기존의 mono를 대체하기 위해 도입 중인) CoreCLR 등이 있다.
VM의 실행방식이 다르다
JVM(인터프리터 + JIT 컴파일)
HotSpot JVM 기준으로, JVM은 변환된 바이트코드(.class)를 한줄씩 읽는 인터프리터 방식으로 동작하다가, 자주 실행되는 코드를 감지하면 해당 코드를 네이티브 코드로 변환한다.
CLR(JIT)
전통적인 CLR의 경우 메서드 단위의 JIT가 중심이다. 메서드의 첫 호출 시 JIT 컴파일을 통해 네이티브 코드로 변환한다.
즉, Java는 자주 사용되는 코드를 JIT 컴파일하는 방식이 기본이라면, C#은 호출되는 메서드만 JIT 컴파일하는 방식을 기본으로 한다. 다만 두 언어 모두 자주 실행되는 코드를 추가적으로 최적화할 수 있다.
네이티브로 돌릴 수 있다
원래 Java와 C#은 가상 머신에서 돌아간다고 알고 있었는데, GraalVM Native Image나 .NET NativeAOT 같은 방식이 등장하면서 이야기가 달라졌다. Java 코드나 C# 코드가 AOT 방식으로 컴파일 되어서 실행될 수 있는 것이다.
(* 알아보니 C#은 NGEN이나 ReadyToRun 같은 AOT 컴파일 방식이 기존에 있었다.)
Java(GraalVM)
GraalVM은 Native Image라는 방식으로 네이티브 컴파일이 가능하다. 이 때 실행에 필요한 최소 런타임 구성요소를 포함한 네이티브 실행 파일을 만든다.
C#(CoreCLR)
CoreCLR의 경우 NativeAOT 방식을 통해 네이티브 코드로 컴파일하는데, 이때 CLR의 핵심적인 부분을 포함해 컴파일을 한다.
| 항목 | Native Image | NativeAOT |
|---|---|---|
| 컴파일 방식 | Java/JVM 바이트코드를 AOT 컴파일 | .NET IL을 AOT 컴파일 |
| 런타임 | HotSpot JVM에서 실행하지 않지만 필요한 런타임 구성요소 포함 | 필요한 런타임 라이브러리와 축소된 CoreCLR 구성요소 포함 |
| 장점 | 빠른 시작, 낮은 메모리 사용량 | 빠른 시작, 낮은 메모리 사용량 |
네이티브 방식이 무조건 빠른 것은 아니다
나는 VM으로 돌리면 무조건 네이티브보다 느린 줄 알았는데, 그건 또 아니었다. 네이티브의 경우 별도의 VM/JIT 실행 환경에 의존하지 않고 실행에 필요한 런타임 구성요소를 함께 포함하기 때문에, 시작 시간이 빠르고 메모리 사용량이 낮아지는 경우가 많다.
가상머신을 이용한 경우는 어느 플랫폼에서든 돌릴 수 있다는 장점 하나 때문에 사용하는 것인 줄 알았다. 그런데 JIT 컴파일이 이럴 때는 장점이 되기도 한다는 것을 알았다. 실제 데이터를 기반으로 코드 최적화를 하기 때문에, 이런 경우에는 네이티브와 비교했을 때 오히려 성능이 높게 나온다고 한다.
