Dalvik: 정의, 가상 머신 및 작동 방식

저자: IT Sectr 게시일: 2026-04-16 읽는 시간: 9 분

Dalvik 가상 머신은 Android 운영 체제의 핵심 구성 요소로, 버전 4.4 KitKat까지 애플리케이션 실행을 담당했습니다. Dan Bornstein이 개발한 레지스터 기반 VM은 표준 JVM 개념을 대체하고 제한된 RAM을 가진 모바일 장치에서 애플리케이션 시작을 최적화할 수 있게 했습니다. Google, 2024에 따르면, Dalvik은 JIT 컴파일을 통해 애플리케이션 호환성을 보장했으며, 실행 중에 DEX 바이트코드를 직접 기계 명령어로 변환했습니다.

핵심 요점

  • Dalvik은 레지스터 기반 아키텍처를 가진 가상 머신으로, Android에 최적화되어 있습니다.
  • JVM과 달리, Dalvik은 모바일 장치에 특별히 압축된 DEX 바이트코드를 실행합니다.
  • JIT 컴파일은 애플리케이션 실행 중에 DEX 코드의 일부를 직접 기계어로 변환합니다.
  • Android 5.0부터 Dalvik은 사전 AOT 컴파일을 수행하는 ART로 대체되었습니다.
  • Dalvik을 이해하는 것은 오래된 Android 버전 지원 및 역호환성 분석에 필요합니다.

Dalvik이란?

Dalvik은 레지스터 기반 아키텍처를 가진 가상 머신으로, Android 플랫폼을 위해 특별히 만들어졌습니다. 개발은 2005년 Dan Bornstein의 회사에서 시작되었고, 2007년 Google이 프로젝트를 인수했습니다. Dalvik의 첫 번째 상용 버전은 2008년 Android 1.0 출시와 함께 등장했습니다.

표준 Java 가상 머신(JVM)과 달리, Dalvik은 Java 바이트코드를 실행하지 않습니다. Java 컴파일러는 소스 코드를 class 파일로 변환하고, dx 유틸리티가 이를 Dalvik Executable(DEX) 형식으로 변환합니다. 이 형식은 class 파일보다 더 간결합니다. class 형식의 10MB 애플리케이션은 DEX에서 약 6–7MB를 차지합니다.

만들어진 배경

Dan Bornstein은 제한된 리소스를 가진 운영 체제를 위한 프로젝트로 Dalvik을 작성했습니다. 이름은 아이슬란드의 달비크 마을에서 따왔습니다. Google은 라이선스 제한과 ARM 아키텍처를 사용하는 모바일 프로세서에 대한 심층 최적화의 필요성 때문에 JVM 대신 Dalvik을 선택했습니다. 시스템은 빠르게 인기를 얻었습니다. 2012년까지 5억 대 이상의 Android 장치가 Dalvik에서 실행되었습니다.

Android 생태계에서의 역할

각 Android 애플리케이션은 자체 Dalvik VM 인스턴스를 가진 별도의 프로세스에서 실행됩니다. 이는 운영 체제 수준에서 데이터 격리와 악성 코드로부터 보호를 보장합니다. 이 접근 방식은 가상화의 장점과 Linux 샌드박스를 결합하여, 한 애플리케이션의 맬웨어가 인접 프로세스에 영향을 미칠 수 없습니다.

Dalvik 아키텍처: 레지스터 머신과 DEX

Dalvik의 레지스터 기반 아키텍처는 JVM의 스택 기반 아키텍처와 근본적으로 다릅니다. Dalvik은 스택 상단에서의 연산 대신 VM 내부의 가상 셀인 레지스터로 작동합니다. 각 명령어에는 피연산자 레지스터의 주소가 포함되어 있어, 작업당 명령어 수를 줄입니다.

JVM 스택 머신은 push, pop, add와 같은 명령어를 사용합니다—두 숫자를 더하는 데 세 개의 명령어가 필요합니다. Dalvik은 세 개의 레지스터가 있는 하나의 add-int 명령어로 동일한 작업을 해결합니다. Android Open Source Project에 따르면, 레지스터 기반 DEX 아키텍처는 스택 기반 class 형식과 비교하여 바이트코드 크기를 평균 30% 줄입니다.

DEX 형식

DEX 파일(Dalvik Executable)은 애플리케이션의 모든 클래스에 대한 압축 표현을 포함합니다. 파일 헤더에는 체크섬, 섹션 크기 및 오프셋이 포함됩니다. 주요 섹션은 문자열, 유형, 메서드 프로토타입, 필드 및 바이트코드 자체의 풀입니다. 단일 DEX 파일은 최대 65,536개의 메서드를 저장할 수 있습니다(Android 5.0에서 멀티덱스 도입으로 제한 해제).

Android SDK Build Tools에 포함된 dx 유틸리티는 class 파일을 DEX로 변환하는 데 사용됩니다. 명령 예: dx --dex --output=classes.dex myapp.jar. 최신 프로젝트는 최적화가 개선되고 Java 8+ 기능을 지원하는 dx의 후속 제품인 D8을 사용합니다.

bash
# dx를 사용하여 JAR를 DEX로 변환
dx --dex --output=classes.dex myapp.jar

# D8을 통한 최신 버전
d8 --lib android.jar --output dex/ myapp.jar

Zygote: 프레임워크 사전 로딩

Zygote 프로세스는 Dalvik 아키텍처의 중요한 요소입니다. 시스템이 시작되면 Zygote는 모든 Android SDK 클래스를 로드하고, 공유 라이브러리를 열며, 사전 로드된 리소스 풀을 만듭니다. 사용자가 애플리케이션을 열면 시스템이 Zygote 프로세스를 포크하여 이미 초기화된 프레임워크를 가진 새로운 Dalvik VM 인스턴스를 만듭니다. 이는 애플리케이션 시작 시간을 ~2–3초에서 300–500밀리초로 줄입니다.

Dalvik의 JIT 컴파일

JIT(Just-In-Time)은 애플리케이션 실행 중에 바이트코드를 직접 기계 명령어로 컴파일하는 기술입니다. Dalvik에서 JIT 컴파일러는 실행 중인 DEX 코드를 분석하고, 자주 사용되는(핫) 메서드를 식별하여 CPU용 네이티브 코드로 컴파일합니다.

초기 Android 버전에서 완전한 Ahead-Of-Time(AOT) 컴파일 대신 JIT를 선택한 것은 의도적이었습니다. 모바일 장치는 플래시 스토리지가 제한적이었고(4–16GB), 모든 애플리케이션을 사전 컴파일하면 상당한 공간을 차지했을 것입니다. 또한, 초기 장치의 ROM 메모리는 RAM보다 느렸고, 사전 컴파일된 코드를 읽으면 성능이 저하될 수 있었습니다.

JIT 컴파일 프로세스

애플리케이션이 시작되면 Dalvik은 DEX 바이트코드를 해석하기 시작합니다. 특수 프로파일러가 어떤 메서드가 가장 자주 호출되는지 추적합니다. 임계값(일반적으로 ~200회 호출)을 초과하면 JIT 컴파일러가 메서드를 기계어로 변환하고 RAM에 캐시합니다. 이후 호출은 재컴파일 없이 이미 컴파일된 버전을 사용합니다.

java
// JIT가 컴파일할 핫 메서드의 예
public class Calculator {
    public int sumArray(int[] arr) {
        int total = 0;
        for (int i = 0; i < arr.length; i++) {
            total += arr[i];
        }
        return total;
    }
}

JIT 성능

Google I/O 2013에 따르면, Android 2.2 Froyo에서 JIT 도입은 순수 해석에 비해 애플리케이션 실행을 평균 2–5배 가속화했습니다. 그러나 JIT는 첫 번째 실행 시 지연을 추가합니다. 애플리케이션이 준비되고 핫 메서드를 컴파일하는 데 3~10초가 필요합니다. 준비 후에는 성능이 네이티브 코드에 가까운 수준으로 안정화됩니다.

Dalvik vs JVM: 주요 차이점

Dalvik은 여러 근본적인 측면에서 JVM과 다릅니다. 첫째—아키텍처: JVM은 스택 기반, Dalvik은 레지스터 기반입니다. 둘째—바이트코드 형식: JVM은 class 파일을 사용하고, Dalvik은 DEX를 사용합니다. 셋째—메모리 관리: Dalvik은 모바일 장치의 제한된 RAM에 최적화되어 있습니다.

두 접근 방식 모두 장점이 있습니다. 스택 기반 JVM은 명령어 저장에 필요한 공간이 적습니다—피연산자가 스택에서 암시적으로 가져와지므로 각 명령어가 더 짧습니다. 레지스터 기반 Dalvik은 작업당 더 적은 명령어를 실행하여 CPU 시간을 절약하고 전력 소비를 줄입니다. 배터리로 작동하는 모바일 장치에서는 이것이 중요합니다.

매개변수DalvikJVM
아키텍처레지스터 기반스택 기반
바이트코드DEXclass
컴파일JIT (Android 2.2+)JIT / AOT
최적화저전력 소비높은 호환성
격리Linux 프로세스를 통해ClassLoader를 통해

라이선스 측면

JVM 대신 Dalvik을 선택한 것은 라이선싱에 의해서도 결정되었습니다. Oracle은 Java SE와 JVM에 대한 권리를 소유하고 있으며, Google은 라이선스 비용을 피하려고 했습니다. 대체 바이트코드 형식을 가진 자체 VM을 만듦으로써 Android가 Oracle로부터 독립적으로 발전할 수 있었습니다. 이 분쟁은 오랜 법적 공방인 Oracle 대 Google(2010–2021)으로 이어졌으며, Google의 승소로 끝났습니다.

DEX 형식과 dx 유틸리티

DEX(Dalvik Executable)은 Android 애플리케이션의 컴파일된 코드를 포함하는 바이너리 형식입니다. 각 DEX 파일은 헤더로 시작하며, 그 뒤에 문자열 상수(string_ids), 유형(type_ids), 메서드 프로토타입(proto_ids), 필드(field_ids), 메서드(method_ids), 클래스 정의(class_defs) 및 데이터 영역 섹션이 있습니다.

dx 유틸리티는 Java class 파일을 하나 이상의 DEX 파일로 변환합니다. 알고리즘에는 상수 중복 제거가 포함되어 있어, 동일한 문자열이나 유형이 한 번 저장되고 인덱스로 참조됩니다. 이는 최종 크기를 크게 줄입니다. 최신 프로젝트에서 dx는 D8(Android Studio 3.1에서 도입)로 대체되었으며, 2–3배 빠르고 Java 8 디슈가링을 지원합니다.

java
// dexdump를 통해 역컴파일된 DEX 바이트코드의 예
// 소스 코드: return a + b;
@Ldalvik/annotation/Code;
    registers: 3
    add-int v0, v1, v2
    return v0

멀티덱스: 65536 제한 극복

65,536개 메서드의 DEX 형식 제한(16비트 인덱스 제한)은 대규모 애플리케이션에 심각한 문제가 되었습니다. 해결책은 Android 5.0과 함께 왔습니다: 멀티덱스 지원을 통해 애플리케이션이 여러 DEX 파일을 포함할 수 있습니다. 기본 classes.dex는 진입점을 포함하고, 추가 classes2.dex, classes3.dex 등은 나머지 코드를 포함합니다. 멀티덱스 구성은 build.gradle에서 multiDexEnabled true 줄로 활성화됩니다.

메모리 관리 및 가비지 컬렉션

Dalvik의 가비지 컬렉션은 마크 앤 스윕을 사용하는 세대별 수집기로 구현됩니다. 메모리는 객체용 힙과 프리미티브 및 참조용 스택의 두 주요 영역으로 나뉩니다. 힙이 가득 차면 Dalvik은 모든 스레드를 중단하고(STW — Stop-The-World), 도달 가능한 객체를 표시하고, 도달 불가능한 객체를 해제합니다.

Android 2.2 이전에는 Dalvik이 최대 100–200ms의 일시 중지 시간을 가진 단일 스레드 수집기를 사용했습니다. Android 2.3 Gingerbread는 일반적인 일시 중지를 5–10ms로 줄이는 동시 수집기를 도입했습니다. 그리고 Android 4.0 Ice Cream Sandwich는 증분 정리를 가진 수집기인 Concurrent Mark and Sweep(CMS)을 추가했습니다.

메모리 누수

Dalvik 애플리케이션의 일반적인 문제는 Activity에 대한 정적 참조를 통한 메모리 누수입니다. 정적 필드가 Context나 View에 대한 참조를 보유하고 있으면, 화면이 닫힌 후에도 가비지 수집기가 Activity를 해제할 수 없습니다. Eclipse MATLeakCanary와 같은 도구는 이러한 누수를 감지하는 데 도움을 줍니다. 힙 덤프를 분석하고 객체를 보유하는 참조 체인을 표시합니다.

java
// 정적 참조를 통한 메모리 누수 예
public class Utils {
    private static Context context;

    public static void init(Context ctx) {
        context = ctx; // finish() 후 Activity 유지
    }
}

Dalvik의 한계와 ART로의 전환

성공에도 불구하고, Dalvik에는 몇 가지 단점이 있었습니다. JIT 컴파일에는 준비 시간이 필요하여 애플리케이션 작동의 첫 몇 초가 느렸습니다. 또한, JIT는 컴파일 중 CPU 전력을 소비하여 배터리 수명을 단축시켰습니다. 모바일 장치의 성능이 향상되고 내장 스토리지가 증가함에 따라 JIT의 필요성은 줄어들었습니다.

Android 4.4 KitKat에서 Google은 Dalvik의 실험적 대체품으로 ART(Android Runtime)를 도입했습니다. Android 5.0 Lollipop부터 ART는 유일한 런타임 환경이 되었습니다. 주요 차이점은 AOT 컴파일입니다. 실행 중에 컴파일하는 대신, 모든 애플리케이션이 설치 중에 기계어로 컴파일됩니다. 이로 인해 준비 지연이 제거되고 에너지 효율이 향상되었습니다.

역호환성

Dalvik에서 ART로의 전환은 개발자에게 투명했습니다. 두 런타임 모두 동일한 DEX 바이트코드를 실행합니다. Dalvik용으로 컴파일된 애플리케이션은 재컴파일 없이 ART에서 실행됩니다—system_server가 설치 중에 이를 네이티브 코드로 컴파일합니다. 예외는 리플렉션을 사용하여 Dalvik VM 내부 멤버에 액세스하는 코드입니다. 내부 아키텍처 변경으로 인해 이러한 코드는 ART에서 손상될 수 있습니다.

자주 묻는 질문

간단히 말해 Dalvik이란 무엇인가요?

Dalvik은 휴대폰에서 Android 애플리케이션을 실행하는 중개 프로그램입니다. 애플리케이션 코드를 가져와 사용자가 작업하는 동안 직접 프로세서가 이해할 수 있는 명령어로 변환합니다.

Dalvik은 JVM과 어떻게 다른가요?

Dalvik은 레지스터 기반 아키텍처와 DEX 형식을 사용하는 반면, JVM은 스택 기반 아키텍처와 class 형식을 사용합니다. Dalvik은 메모리와 처리 능력이 제한된 모바일 장치에 최적화되어 있으며, JVM은 데스크톱 컴퓨터와 서버용으로 설계되었습니다.

Google은 왜 Dalvik을 ART로 대체했나요?

ART는 사전 AOT 컴파일을 통해 더 높은 성능을 제공합니다—애플리케이션이 시작될 때마다가 아니라 설치 중에 한 번 컴파일됩니다. 이는 Dalvik의 JIT 방식에 비해 작동 속도를 높이고 배터리를 절약합니다.

오래된 애플리케이션이 ART에서 작동하나요?

네, ART는 Dalvik DEX 바이트코드와 완전히 역호환됩니다. 설치 중에 ART는 오래된 DEX 파일을 네이티브 코드로 컴파일합니다. 예외는 리플렉션을 사용하여 Dalvik 내부 메커니즘에 액세스하는 애플리케이션입니다.

DEX 파일이란 무엇인가요?

DEX(Dalvik Executable)는 Android 애플리케이션의 압축된 바이트코드를 포함하는 실행 파일 형식입니다. 하나의 APK에 65,536개 이상의 메서드가 있는 경우 여러 DEX 파일(멀티덱스)을 포함할 수 있습니다.

요약

  • Dalvik VM은 Android용으로 만들어져 버전 4.4 KitKat까지 사용된 레지스터 기반 가상 머신입니다.
  • DEX 형식은 간결한 바이트코드 저장을 제공합니다—JVM class 파일보다 30% 작습니다.
  • Dalvik의 JIT 컴파일은 순수 해석에 비해 애플리케이션 실행을 2–5배 가속화했습니다.
  • Zygote 프로세스는 Android 프레임워크를 사전 로드하여 애플리케이션 시작 시간을 300–500ms로 줄입니다.
  • 단일 DEX 파일의 65,536개 메서드 제한은 Android 5.0부터 멀티덱스를 통해 해결됩니다.
  • Dalvik의 가비지 컬렉션은 단일 스레드 STW에서 Concurrent Mark and Sweep으로 발전했습니다.
  • Android 5.0의 ART 전환은 JIT 준비 지연을 제거하고 에너지 효율을 개선했습니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기