DEX: 바이트코드의 구조와 작동 원리

저자: IT Sectr 게시일: 2026-04-15 읽는 시간: 8 분

DEX(Dalvik Executable)는 Java 및 Kotlin으로 작성된 Android 애플리케이션의 소스 코드가 컴파일되는 바이트코드 형식입니다. DEX 파일은 Dalvik 가상 머신(Android 4.4까지) 또는 Android Runtime(ART, Android 5.0부터)에 의해 실행됩니다. Android Open Source Project, 2026에 따르면 DEX 형식은 표준 JVM Java 바이트코드에 비해 평균 30% 더 컴팩트한 코드 표현을 제공합니다.

핵심 요점

  • DEX는 Android용 바이트코드 형식으로, Dalvik 또는 ART에서 실행됩니다.
  • 컴팩트성 — DEX는 표준 Java 바이트코드보다 30% 적은 공간을 차지합니다.
  • Multidex — 단일 DEX 파일의 65536개 메서드 제한을 우회하는 메커니즘입니다.
  • ART — Dalvik을 대체한 Android Runtime은 설치 시 DEX를 네이티브 코드로 컴파일합니다.
  • D8 — 2018년부터 DX를 대체한 Java/Kotlin에서 DEX로의 최신 컴파일러입니다.

DEX란 무엇이며 왜 필요한가

DEX(Dalvik Executable)는 Android 모바일 기기용으로 특별히 설계된 바이트코드 형식입니다. 표준 Java 바이트코드(.class 파일)와 달리 DEX는 제한된 리소스에 최적화되어 있습니다: 더 적은 메모리, 더 작은 크기, 더 빠른 클래스 로딩.

Java에서 DEX로

Java 또는 Kotlin의 소스 코드는 javac/kotlinc에 의해 표준 .class 파일(Java 바이트코드)로 컴파일됩니다. 그런 다음 d8 도구(또는 이전의 dx)가 .class를 하나 이상의 DEX 파일로 변환합니다. 이 변환은 단순한 재패키징이 아닙니다 — d8은 최적화를 수행합니다: 상수 풀 병합, 레지스터 아키텍처로 명령어 재작성, 중복 데이터 제거.

아키텍처 특징

DEX는 레지스터 기반 아키텍처를 사용합니다(스택 기반 JVM과 반대). 각 메서드는 고정된 수의 레지스터(최대 65536)를 가집니다. DEX 명령어는 더 짧습니다 — 평균 2바이트(JVM의 1~4바이트와 비교). 이는 더 컴팩트한 코드를 생성합니다: 일반적인 애플리케이션은 10~15MB의 .class에서 4~6MB의 .dex로 줄어듭니다.

DEX 파일 구조: 섹션과 헤더

DEX 파일은 엄격하게 정의된 바이너리 구조를 가집니다. 각 파일은 헤더로 시작하며 오프셋을 통해 서로를 참조하는 여러 섹션을 포함합니다.

섹션목적
header헤더: 매직, 체크섬, 서명, 섹션 크기 및 오프셋
string_ids문자열 테이블: 클래스, 메서드, 필드 이름
type_ids유형: 유형 문자열 식별자에 대한 참조
proto_ids메서드 프로토타입: 반환 유형 및 매개변수
field_ids클래스 필드: 클래스, 유형, 이름
method_ids메서드: 클래스, 프로토타입, 이름
class_defs클래스 정의: 플래그, 슈퍼클래스, 인터페이스, 데이터 오프셋
data실제 데이터: 메서드 코드, 주석, 디버그 정보

DEX 헤더

DEX의 매직 넘버는 `dex\n035\0`(버전 035)입니다. 다른 버전: 036, 037, 038(Android 8.0+용). 헤더는 0x70바이트 크기이며 SHA-1 체크섬과 모든 섹션의 오프셋을 포함합니다. 가상 머신이 DEX를 로드할 때 첫 번째 단계는 헤더 검증입니다.

상수 풀

string_ids, type_ids, proto_ids, field_ids, method_ids — 인덱싱된 테이블입니다. 메서드 코드에 전체 이름을 저장하는 대신 4바이트 인덱스가 사용됩니다. 이것이 핵심 최적화입니다: 클래스가 100번 참조되면 그 이름은 string_ids에 한 번만 저장됩니다. dex2oat는 ART 컴파일 중에 이러한 테이블을 추가로 최적화합니다.

Java 및 Kotlin의 DEX 컴파일 과정

소스 코드를 DEX로 변환하는 과정은 여러 단계로 구성됩니다. 최신 툴체인은 D8 컴파일러를 사용하며, 2018년 Android Gradle Plugin 3.2와 함께 DX를 대체했습니다.

1단계: .class로 컴파일

javac(Java용) 또는 kotlinc(Kotlin용)가 소스 코드를 .class 파일로 컴파일합니다. 각 클래스는 Java 바이트코드의 별도 .class 파일입니다. 이 단계에서는 유형 검사, 브리지 메서드 생성 및 상수 인라인이 수행됩니다.

2단계: D8 컴파일

D8은 모든 .class 파일을 가져와 DEX 바이트코드로 변환합니다. D8은 여러 최적화를 수행합니다: 사용하지 않는 메서드 인수 제거, 다른 .class 파일의 상수 풀을 하나의 전역 DEX 풀로 병합, JVM 스택 명령어를 Dalvik 레지스터 명령어로 변환.

kotlin
// Kotlin 소스 코드
data class User(
    val name: String,
    val email: String
)

fun greet(user: User): String {
    return "Hello, ${user.name}!"
}

D8 컴파일 후 이 코드는 컴팩트한 DEX 명령어로 변환됩니다: 문자열 로드를 위한 const-string, 객체 필드 액세스를 위한 iget-object, StringBuilder.append 호출을 위한 invoke-virtual.

D8 vs DX

D8은 DX보다 2~3배 빠르며, 더 컴팩트한 DEX(5~10% 더 작음)를 생성하고 Kotlin 특정 구조(인라인 함수, 람다)를 더 잘 최적화합니다. DX는 2018년에 지원 중단이 선언되었고 Android Gradle Plugin 8.0에서 제거되었습니다.

Dalvik vs ART: DEX 실행의 변화

Android에서 DEX 코드의 실행은 두 단계를 거쳤습니다: 원래의 Dalvik 가상 머신(Android 2.2~4.4)과 Android Runtime ART(Android 5.0+). 컴파일 접근 방식의 차이는 근본적입니다.

Dalvik VM: JIT 컴파일

Dalvik은 Just-In-Time(JIT) 컴파일을 사용했습니다: DEX 바이트코드를 해석하고 자주 호출되는 메서드를 즉석에서 네이티브 코드로 컴파일했습니다. 장점 — 빠른 설치. 단점 — 느린 시작 및 JIT로 인한 지속적인 CPU 부하.

ART: AOT 컴파일

ART(Android Runtime)는 dex2oat를 통해 애플리케이션 설치 중에 DEX를 네이티브 코드로 컴파일합니다. 이는 Ahead-Of-Time(AOT) 접근 방식입니다: 설치는 더 오래 걸리지만 시작이 더 빠르고 전력 소비가 적습니다. Android 7.0부터 ART는 하이브리드 접근 방식을 사용합니다 — AOT + JIT + 프로필 기반 최적화.

dex2oat: 설치 중 변환

dex2oat 도구는 애플리케이션 설치 또는 업데이트 시 실행됩니다. DEX를 기기 아키텍처에 맞는 네이티브 코드가 포함된 ELF 파일로 컴파일합니다. 결과 — /data/dalvik-cache/ 디렉토리의 .oat 및 .art 파일. Google은 지속적으로 dex2oat를 개선하고 있습니다: Android 14에서는 폴더블 기기에 대한 최적화가 추가되었습니다.

Multidex: 64K 메서드 제한 극복

DEX 파일당 65536개 메서드의 제한은 Dalvik 아키텍처의 유산입니다. DEX 헤더의 method_ids 필드는 4바이트를 차지하여 최대 2^16 = 65536개의 고유 참조를 제공합니다. Google Play Services, Firebase 및 기타 SDK를 사용하는 최신 애플리케이션은 이 제한을 쉽게 초과합니다.

Multidex 메커니즘

Multidex는 코드를 여러 DEX 파일로 분할하는 메커니즘입니다. 기본 classes.dex에는 진입점(Application 클래스, 기본 Activity)이 포함되고, 나머지는 classes2.dex, classes3.dex 등입니다. 시작 시 추가 DEX의 클래스는 DexClassLoader를 통해 로드됩니다.

kotlin
// build.gradle.kts — multidex 활성화
android {
    defaultConfig {
        multiDexEnabled = true
    }
}

// Multidex를 지원하는 Application 클래스
class MyApp : Application() {
    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        MultiDex.install(this)
    }
}

Multidex 문제

애플리케이션 시작 중 추가 DEX 로드는 Android 5.0 미만 기기에서 ANR(Application Not Responding)을 유발할 수 있습니다. 권장 사항 — 필요한 경우에만 multidex를 사용하고 제한을 초과하지 않도록 종속성을 최소화하세요.

DEX 최적화: ProGuard, R8 및 난독화

DEX 최적화는 릴리스 Android 애플리케이션 빌드의 표준 단계입니다. R8 및 ProGuard 도구는 DEX 크기를 줄이고 코드를 난독화하며 사용하지 않는 클래스를 제거합니다.

R8 vs ProGuard

R8은 ProGuard의 후속 제품으로, 2019년부터 Android Gradle Plugin에 내장되었습니다. R8은 단일 패스에서 축소, 난독화 및 최적화를 수행하는 반면, ProGuard는 두 단계가 필요했습니다: ProGuard → D8. ProGuard는 여전히 지원되지만 Google은 새 프로젝트에 R8을 권장합니다.

R8은 사용하지 않는 클래스, 메서드 및 필드를 제거하고, 짧은 이름(a, b, c)으로 이름을 바꾸며, 인라인 함수를 포함하고 데드 코드를 제거합니다. 결과 — 기능 손실 없이 DEX가 20~40% 감소합니다.

R8 규칙

R8 구성은 proguard-rules.pro 파일에 지정됩니다. 개발자는 이름을 바꿀 수 없는 클래스를 지정할 수 있습니다(예: 리플렉션 또는 Gson 직렬화). Firebase 및 기타 SDK는 종속성에 자체 규칙을 제공합니다.

DEX 역컴파일: 도구와 보호

DEX는 Java 코드로 역컴파일될 수 있습니다. 이는 Android 애플리케이션의 주요 보안 문제입니다: 난독화 없이 코드는 원본에 가까운 수준으로 복원됩니다.

역컴파일 도구

JADX는 가장 널리 사용되는 DEX-to-Java 역컴파일러입니다. 클래스 이름, 메서드, 필드 및 대부분의 로직을 복원합니다. apktool은 DEX를 smali 코드(Dalvik 어셈블러)로 역컴파일합니다 — 원래 명령어에 가까운 저수준 표현입니다. Bytecode Viewer는 여러 역컴파일러를 하나의 인터페이스에 통합합니다.

보호 방법

R8/ProGuard를 사용한 난독화는 첫 번째 방어선입니다: 클래스 및 메서드 이름을 읽을 수 없게 만듭니다. DexGuard는 추가 방법을 갖춘 상용 도구입니다: 문자열 암호화, 무결성 검사, 변조 방지. 제어 흐름 난독화(O-LLVM)는 기능을 유지하면서 코드 구조를 변경하여 분석을 훨씬 어렵게 만듭니다.

자주 묻는 질문

DEX와 Java 바이트코드의 차이점은?

DEX는 스택 기반 JVM 대신 레지스터 기반 아키텍처를 사용하고, 더 컴팩트한 형식(30% 더 작음)이며, 모든 .class 파일을 단일 상수 풀이 있는 하나의 파일로 병합하고, 8비트 대신 16비트 인덱스를 사용합니다.

smali란 무엇인가요?

Smali는 DEX 바이트코드용 어셈블러입니다. 각 DEX 명령어는 smali 형식의 텍스트 표현을 가집니다. baksmali 도구는 DEX를 smali로 변환(디스어셈블)하고, smali는 smali를 다시 DEX로 어셈블합니다.

DEX의 메서드 수를 확인하는 방법은?

Gradle 태스크 countMethods 또는 dex-method-counts 플러그인은 각 DEX 파일의 메서드 수를 표시합니다. adb shell 명령어와 dumpsys는 설치된 애플리케이션에 대해 로드된 DEX의 통계도 표시합니다.

DEX 파일 수가 성능에 영향을 미치나요?

, Android 8.0 미만 기기에서는 여러 DEX 파일이 애플리케이션 시작을 느리게 합니다. 각 추가 파일이 별도로 로드되기 때문입니다. Android 8.0+의 ART에서는 단일 .oat 파일로의 dex2oat 컴파일 덕분에 차이가 미미합니다.

Android 없이 DEX를 실행할 수 있나요?

, dexplorer 및 Android 호환 JVM 구현과 같은 프로젝트가 Android 외부에서 DEX 바이트코드를 실행할 수 있습니다. 그러나 대부분의 DEX 파일은 Android API를 사용하므로 표준 JVM에서 실행하기에 적합하지 않습니다.

요약

  • DEX는 레지스터 기반 아키텍처와 컴팩트한 코드 표현을 가진 Android 바이트코드 형식입니다.
  • 구조는 헤더, 식별자 테이블 및 명령어가 포함된 데이터 섹션으로 구성됩니다.
  • 컴파일은 D8을 통해 DEX로 수행됩니다: .class → DEX(최적화 및 상수 풀 병합 포함).
  • ART는 설치 중에 DEX를 네이티브 코드(AOT)로 컴파일하여 애플리케이션 시작을 가속화합니다.
  • Multidex는 여러 DEX 파일로 분할하여 65536 메서드 제한을 해결합니다.
  • 최적화 — R8은 DEX를 20~40% 줄이고, 이름을 난독화하며 데드 코드를 제거합니다.
  • 보호 — R8/ProGuard, DexGuard 및 O-LLVM을 사용한 난독화는 DEX 역컴파일을 방지합니다.

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

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

프로젝트 논의

더 읽어보기