저번에 우리는 Android의 구조에 대해 알아보았습니다.
그 중 ART에 대해서는 ~이런게 있다. 정도로만 알고 넘어갔는데요.
오늘은 그 내용을 자세히 알아보도록 하겠습니다.
우선 정의부터 살펴봅시다.
Android Runtime, ART
Linux Kernel과 HAL의 상위 계층에 위치하여 운영체제를 활용하여 Java 애플리케이션 실행에 대한 중추 역할을 수행하는 VM(Virtual Machine)
즉, ART는 물리적인 하드웨어(CPU, RAM 등)를 소프트웨어로 에뮬레이션하여, 어떤 기기에서든 동일한 코드가 실행될 수 있도록 돕는 역할 가상머신입니다.
ART의 등장 배경 및 역할을 이해하기 위해서는 JVM에 대해 이해하고 넘어갈 필요가 있습니다.
바로 위에서 우리는 ART가 가상머신인 것을 알았습니다.
가상머신하면 보통 떠오르는 단어가 있죠? 바로 JVM(Java Virtual Machine)입니다.
안드로이드는 얼마전까지(2018년까지) Java를 공식언어로 채택해왔습니다.
때문에 High-Level Language인 Java를 컴퓨터로 이해할 수 있도록 변환해주는 과정인 컴파일 과정(.java -> .class)을 거쳐야하죠. 일반적인 컴파일 언어는 다이텍트로 기계어로 변환하지만, 자바 컴파일러는 기계어가 아닌 JVM이 이해할 수 있는 바이트코드(.class)로 변환합니다.덕분에 자바 바이트 코드는 JVM만 설치되어 있으면 어떤 운영체제에서라도 실행될 수 있습니다.
현재는 Android에서는 JVM을 사용하지 않습니다.
위에서 언급하였듯이 기본 언어로 Java를 채택했기 때문에 JVM이 필수적이였으나, 라이센스 및 효율 문제로 인해 안드로이드 운영체제 구조에 맞춰 구동할 수 있는 가상머신이 필요해졌습니다.
이러한 배경으로 Dalvik VM과 ART가 등장하게 됩니다. 이제 본적적으로 이 둘에 대해 알아보도록 하죠.
먼저 달빅 가상머신에 대해 알아봅시다.
컴파일러를 통해서 생성되는 바이트 코드(.class)는 기계어가 아닙니다.
바이트 코드는 특정 플랫폼에 종속된 것이 아닌 VM을 위한 코드을 뿐이죠.
따라서 인터프리터를 이용해서 기계어로 해석하는 과정이 필요합니다.
위는 컴파일 과정을 도식화 해둔 그림입니다.
보시다시피, 컴파일러를 통해 우선적으로 바이트코드로 변환되고 그 이후 각 가상머신에 따라 한번 더 변환되죠. Dalvik VM을 사용하는 경우 달빅코드(.dex)로 변환되는 것을 확인할 수 있습니다.
우리는 그럼에서 JIT(Just-In-Time) 컴파일이라는 부분에 초점을 둘 필요가 있습니다.
JIT 컴파일 방식은 프로그램 실행 시 자주 사용되는 바이트코드(Hotspot)에 대해서 미리 기계어로 해석 해놓는 방식(Caching)입니다. 때문에 바이트코드가 사용될 때 재해석할 필요가 없어 속도가 빠릅니다.
JIT의 특징은 다음과 같습니다.
DVM에서의 JIT은 임계값을 초과하면 바이트코드를 기계어로 해석하는 방식입니다.
즉, 어떤 구간이 특정 횟수 이상 반복되면 컴파일하는 Trace JIT인 것이죠.
컴파일은 별도의 컴파일 스레드에 의해 진행되며, 컴파일이 완료되면 Traslastion Cache에 저장합니다. 초기 DVM에는 JIT이 없었고 Android 2.2에서 적용되었습니다. 이전까지는 애플리케이션 구동 시 실시간으로 CPU에 맞춰 코드를 변환했죠. JIT 도입 후 코드의 일정 부분을 RAM에 올려두고 작업할 수 있기에 비약적인 성능 향상이 일어났으나, 다음과 같은 문제가 발생했습니다.
해당 문제를 해결하기 위해 AOT 컴파일 방식을 기반으로 만들어진 VM이 바로 ART입니다.
ART는 AOT(Ahead-Of-Time) 컴파일 방식을 사용합니다.
도식화를 확인하면 알 수 있듯이 앱 설치 시 컴파일을 진행하게 됩니다.
AOT의 특징은 다음과 같습니다.
위 Dalvik 파트와 ART 파트를 정독하셨다면 알고 계실겁니다.
Dalvik과 ART의 가장 큰 차이는 컴파일 방식이고, 서로 상반된 장단점을 가지고 있습니다.
Android 7.0 부터는 앱 설치시간을 단축하기 위해 최초 설치 시에는 JIT 컴파일 방식을 사용하고, 기기를 사용하지 않는 시간에 일부분 컴파일 작업을 실시하여 점진적으로 AOT 컴파일 방식으로 바꿔 나가도록 되어 있음. 즉, DVM과 ART의 장점을 합치고 단점을 해결하는 방식으로 문제를 해결했습니다.
조금 더 자세히 알아볼까요?
AOT 컴파일러는 APK(Android Application Package)를 설치할 때 Dalvik에서 사용하는 dex 파일를 활용하여 기계어로 해석합니다.
Dalvik은 dexopt라는 툴을 통해서 dex파일을 최적화한 odex(optimized dex)라는 파일을 생성합니다. odex는 특정 기기의 시스템에 최적화된 코드이기 때문에 다른 기기에서 사용할 수 없으며 DVM은 odex 파일을 앱 실행 시 기계어로 해석하죠.
ART는 AOT 컴파일 시 dex2oat라는 툴을 사용해서 dex파일을 odex로 변경한 후에 oat 파일로 변경합니다. oat 파일은 elf(executable file) 파일 형식의 기계어를 포함하는 파일이구요.
아직까지는 잘 이해하시지 못할 수 있습니다.
다음 포스팅인 안드로이드 컴파일 과정을 참고하여 보신다면 조금 더 이해하기 수월하실 수 있을 겁니다.
결과적으로 안드로이드 런타임은 성능과 효율 사이에서 최적의 균형을 찾기 위해 끊임없이 진화해 왔습니다. 처음엔 JVM의 철학을 빌려왔지만, 모바일이라는 특수한 환경(제한된 배터리, 메모리)에 최적화하기 위해 Dalvik을 거쳐 현재의 ART에 이르게 된 것이죠.
우리가 작성한 코드가 사용자 기기에서 어떻게 컴파일되고 최적화되는지 이해한다면, 단순히 코드를 짜는 것을 넘어 성능최적화에 대한 더 깊은 고민을 할 수 있는 안드로이드 개발자가 될 수 있지 않을까 생각합니다.
다음 포스팅에서는 우리가 작성한 소스 코드가 어떤 단계를 거쳐 기계가 이해할 수 있는 언어로 변환되는지, 안드로이드의 컴파일 과정(Compile Process)에 대해 다루어 보겠습니다.