맨위로가기

자바 바이트코드

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

자바 바이트코드는 자바 가상 머신(JVM)에서 실행되는 일종의 중간 코드이다. 자바 프로그래머는 일반적으로 자바 바이트코드를 직접 다룰 필요는 없지만, 바이트코드에 대한 이해는 자바 프로그래밍에 도움이 될 수 있다. 바이트코드는 로드 및 저장, 산술, 타입 변환, 객체 생성, 제어 전송 등 다양한 명령어로 구성되며, 각 명령어는 연산 코드와 피연산자로 이루어진다. 자바 컴파일러 외에도 다양한 컴파일러와 어셈블러가 자바 바이트코드를 생성하며, JVM을 타겟으로 하는 여러 프로그래밍 언어가 존재한다. 자바 바이트코드는 역컴파일러에 취약하여, 소스 코드 보호를 위해 난독화 도구를 사용하기도 한다.

더 읽어볼만한 페이지

  • 어셈블리어 - 기계어
    기계어는 컴퓨터 CPU가 직접 이해하고 실행하는 이진수 형태의 명령어 집합으로, 각 프로세서는 고유한 명령어 집합을 가지며 어셈블리 언어를 통해 니모닉 코드로 표현될 수 있다.
  • 어셈블리어 - 주소 지정 방식
  • 자바 플랫폼 - 블루레이
    블루레이 디스크는 DVD 후속 매체로, 청색 레이저를 사용하여 고화질 영상과 음향을 제공하며 HD DVD와의 경쟁 후 고밀도 광디스크 표준으로 자리 잡았으나 스트리밍 서비스 성장으로 녹화용 디스크 생산이 중단되는 추세이다.
  • 자바 플랫폼 - 자바 플랫폼, 마이크로 에디션
    자바 ME는 임베디드 및 모바일 장치에서 자바 앱을 실행하는 플랫폼으로, 피처폰에서 주로 사용되었으며 다양한 프로파일과 에뮬레이터, 개발 도구를 제공하고 JSR을 통해 기능이 확장된다.
자바 바이트코드
일반 정보
이름자바 바이트코드
다른 이름p-코드
유형이식 가능한 실행 파일
설계자썬 마이크로시스템즈
개발자오라클
최초 출시1995년
안정화 버전2022년 3월 (Java 18)
운영체제자바 가상 머신을 지원하는 운영체제
기술 정보
파일 확장자.class
MIME 유형application/java-vm
추가 정보
파생달빅 바이트코드

2. 자바와의 관계

자바 프로그래머가 자바 바이트코드를 반드시 알거나 이해할 필요는 없다. 하지만 IBM의 developerWorks 저널에서는 바이트코드와 자바 컴파일러가 생성하는 바이트코드를 이해하는 것이, C나 C++ 프로그래머에게 어셈블리어 지식이 도움이 되는 것과 마찬가지로 자바 프로그래머에게 도움이 된다고 언급했다.[21][4][15][16]

3. 명령어 집합 구조

자바 가상 머신(JVM)은 스택 머신이자 레지스터 머신의 특징을 모두 가진다.[5][2]메서드가 호출될 때마다 해당 메서드를 위한 프레임이 생성되는데, 이 프레임 안에는 계산에 사용될 값들을 임시로 저장하는 '피연산자 스택'과 메서드 인수를 전달받거나 지역 변수를 저장하는 '지역 변수 배열'이 있다.[5][2] 지역 변수 배열은 레지스터와 유사한 역할을 수행하며, 메서드 인수를 전달하는 데에도 사용된다. 피연산자 스택과 지역 변수 배열의 최대 크기는 컴파일러가 각 메서드를 컴파일할 때 미리 계산하여 정해진다.[5] 각각 0개부터 65,535개까지의 값을 저장할 수 있으며, 각 값은 기본적으로 32비트 크기이다. 64비트 크기인 `long`과 `double` 타입 데이터는 두 개의 연속된 지역 변수 공간[5](지역 변수 배열 내에서 64비트 경계에 맞춰 정렬될 필요는 없음) 또는 피연산자 스택의 한 칸을 차지하지만, 스택 깊이 계산 시에는 두 단위로 계산된다.[5]

바이트코드 명령어는 명령의 종류를 나타내는 1바이트 크기의 연산 코드(opcode)와, 필요한 추가 정보(피연산자)를 담는 0개 이상의 바이트로 이루어진다.[5] 바이트코드 명령어는 데이터를 읽고 쓰는 작업(로드 및 저장), 프로그램의 실행 흐름을 바꾸는 작업(제어 전송), 객체를 만들고 사용하는 작업(객체 생성 및 조작), 메서드를 호출하는 작업 등 다양한 종류로 구성된다.[1] 이러한 명령어들은 자바객체 지향 프로그래밍 모델을 지원하는 데 필수적이다.[1]

많은 명령어에는 어떤 종류의 데이터를 다루는지 알려주는 접두사나 접미사가 붙는다.[5] 예를 들어, 정수(integer) 덧셈 명령어는 `iadd`이고, 더블(double)형 실수 덧셈 명령어는 `dadd`이다. 이는 명령어가 어떤 타입의 피연산자에 대해 작동하는지를 명확히 나타낸다.

자바 바이트코드 명령어 목록에서 더 자세한 내용을 확인할 수 있다.

3. 1. 명령어 종류

바이트코드 명령어는 연산 코드(opcode)를 나타내는 1바이트와, 필요한 경우 추가적인 피연산자 정보를 담는 0개 이상의 바이트로 구성된다.[5]

자바 가상 머신(JVM)에서 사용할 수 있는 256개의 단일 바이트 연산 코드 중, 2015년 기준으로 202개는 실제 명령어에 사용되고 있고(~79%), 51개는 향후 사용을 위해 예약되어 있으며(~20%), 3개의 명령어(~1%)는 JVM 구현 과정에서 특별한 용도로 영구 예약되어 있다.[5] 이 3개의 예약된 명령어 중 impdep1impdep2는 특정 구현 환경에 따른 소프트웨어 및 하드웨어 트랩(trap)을 위해 사용되며, 나머지 하나는 디버거가 중단점(breakpoint)을 구현하는 데 사용된다.

바이트코드 명령어는 기능에 따라 크게 다음과 같은 그룹으로 나눌 수 있다.

  • 로드 및 저장: 지역 변수와 스택 간의 데이터 이동 (예: aload_0, istore)
  • 산술 및 논리: 숫자 연산 및 논리 연산 수행 (예: ladd, fcmpl)
  • 타입 변환: 데이터 타입을 다른 타입으로 변환 (예: i2b, d2i)
  • 객체 생성 및 조작: 객체 생성, 필드 접근, 메서드 호출 등 객체 관련 작업 (예: new, putfield)
  • 피연산자 스택 관리: 스택 위의 데이터 조작 (예: swap, dup2)
  • 제어 전송: 프로그램 실행 흐름 변경 (예: ifeq, goto)
  • 메서드 호출 및 반환: 메서드 호출 및 결과 반환 (예: invokespecial, areturn)


이 외에도 예외 발생, 동기화 등 특수한 작업을 위한 명령어들이 존재한다.

많은 명령어는 처리하는 피연산자의 데이터 타입을 나타내는 접두사나 접미사를 가진다.[5] 이는 다음과 같다.

접두사/접미사피연산자 유형
i정수 (integer)
l롱 (long)
s쇼트 (short)
b바이트 (byte)
c문자 (character)
f플로트 (float)
d더블 (double)
z부울 (boolean)
a참조 (reference)



예를 들어, iadd 명령어는 두 개의 정수(integer)를 더하고, dadd 명령어는 두 개의 더블(double) 값을 더한다. const, load, store 계열 명령어는 _''n'' 형태의 접미사를 가질 수 있는데, 여기서 ''n''은 숫자를 의미한다. loadstore 명령어의 경우 ''n''은 0부터 3까지의 값을 가지며, 이는 해당 번호의 지역 변수를 가리킨다. const 명령어의 경우 ''n''의 최댓값은 데이터 타입에 따라 다르다.

const 명령어는 지정된 타입의 상수 값을 피연산자 스택에 넣는다(push). 예를 들어, iconst_5는 정수 값 5를 스택에 넣고, dconst_1은 더블 값 1.0을 스택에 넣는다. aconst_null 명령어는 null 참조를 스택에 넣는다.

loadstore 명령어의 접미사 ''n''은 지역 변수 배열에서 데이터를 가져오거나 저장할 위치(인덱스)를 지정한다. 예를 들어, aload_0 명령어는 0번 지역 변수에 저장된 객체 참조를 스택에 넣는다(일반적으로 이 위치에는 this 객체 참조가 저장된다). istore_1 명령어는 스택 맨 위에 있는 정수 값을 1번 지역 변수에 저장한다. 만약 3번보다 큰 인덱스의 지역 변수를 사용해야 할 경우, 접미사 형태 대신 별도의 피연산자를 사용하는 명령어를 사용한다.

자바 바이트코드 명령어 목록에서 더 자세한 명령어 정보를 확인할 수 있다.

4. 예제

다음과 같은 자바 코드가 있다고 가정해 보자.

```java

outer:

for (int i = 2; i < 1000; i++) {

for (int j = 2; j < i; j++) {

if (i % j == 0)

continue outer;

}

System.out.println(i);

}

```

자바 컴파일러는 위 자바 코드를 아래와 같은 바이트코드로 번역할 수 있다. (아래 코드가 특정 메서드 안에 포함되어 있다고 가정)

```jasmin

0: iconst_2

1: istore_1

2: iload_1

3: sipush 1000

6: if_icmpge 44

9: iconst_2

10: istore_2

11: iload_2

12: iload_1

13: if_icmpge 31

16: iload_1

17: iload_2

18: irem

19: ifne 25

22: goto 38

25: iinc 2, 1

28: goto 11

31: getstatic #84; // Field java/lang/System.out:Ljava/io/PrintStream;

34: iload_1

35: invokevirtual #85; // Method java/io/PrintStream.println:(I)V

38: iinc 1, 1

41: goto 2

44: return

```

자바 가상 머신스택 머신 구조를 사용한다. 이는 연산을 수행할 때 레지스터 대신 스택 메모리 영역을 주로 활용하는 방식이다. 예를 들어, 두 값을 더하는 연산을 x86 아키텍처와 비교해 보면 다음과 같다.

다음은 두 값을 더해 다른 위치에 결과를 복사하는 x86 코드이다.

```asm

mov eax, byte [ebp-4]

mov edx, byte [ebp-8]

add eax, edx

mov ecx, eax

```

유사한 연산을 수행하는 자바 바이트코드는 다음과 같다.

```java

0 iload_1

1 iload_2

2 iadd

3 istore_3

```

여기서 더하려는 두 값(`iload_1`, `iload_2`)은 먼저 스택에 쌓인다. 덧셈 명령(`iadd`)은 스택에서 이 두 값을 꺼내 더한 후, 그 결과를 다시 스택에 넣는다. 마지막으로 저장 명령(`istore_3`)은 스택의 맨 위에 있는 결과값을 특정 변수 위치로 옮긴다. 코드 앞의 숫자는 메서드 시작 지점으로부터 각 명령어까지의 오프셋(상대적 거리)을 나타낸다.

이러한 스택 기반 방식은 자바의 객체 지향적인 측면에도 적용된다. 예를 들어, `getName()`이라는 메서드를 호출하는 바이트코드는 다음과 같다.

```java

Method java.lang.String getName()

0 aload_0 // "this" 객체가 변수 테이블의 위치 0에 저장된다.

1 getfield #5

// 이 명령은 스택의 맨 위에서 객체를 팝하고,

// 해당 객체에서 지정된 필드를 가져오고,

// 그리고 스택에 해당 필드를 푸시한다.

// 이 예에서는 "name" 필드가 이 클래스의 상수 풀의 다섯 번째에 해당한다.

4 areturn // 메서드에서 스택의 맨 위 객체를 반환한다.

```

`aload_0`은 `this` 객체(메서드를 호출한 객체 자신)를 스택에 로드한다. `getfield` 명령어는 스택 맨 위의 객체 참조를 가져와 해당 객체의 특정 필드(`name` 필드) 값을 읽어 다시 스택에 넣는다. 마지막으로 `areturn`은 스택 맨 위의 객체 참조(여기서는 `name` 필드의 값)를 메서드의 반환값으로 사용한다.

5. 생성

자바 바이트코드를 생성하는 가장 일반적인 언어는 자바이다. 자바 가상 머신(JVM) 위에서 실행되는 자바 바이트코드는 주로 자바 소스 코드를 컴파일하는 과정을 통해 만들어진다. 과거에는 Sun Microsystems(현재 오라클)에서 개발한 javac 컴파일러가 유일했지만, 현재는 자바 바이트코드 사양이 공개되어 다양한 컴파일러가 개발되었다.

주요 자바 컴파일러는 다음과 같다.


  • javac: Sun Microsystems(현 오라클)에서 개발한 자바 컴파일러이다.
  • 이클립스 자바 컴파일러 (ECJ): 이클립스 개발 환경에서 사용되는 컴파일러이다.
  • Jikes: IBM에서 개발했으며 C++로 구현되었다.
  • Espresso: 초기의 자바 컴파일러로, 자바 1.0 버전만 지원한다.
  • GNU 자바 컴파일러 (GCJ): GNU 컴파일러 모음(GCC)의 일부였으며(버전 6까지), 자바 코드를 바이트코드뿐만 아니라 네이티브 기계어로도 컴파일할 수 있었다.


컴파일러 외에도, 어셈블리 언어처럼 직접 자바 바이트코드를 작성할 수 있게 해주는 자바 어셈블러도 존재한다. 이러한 어셈블러는 다른 언어 컴파일러가 JVM을 대상으로 코드를 생성할 때 내부적으로 사용되기도 한다. 주요 자바 어셈블러는 다음과 같다.

  • Jasmin: 간단한 텍스트 기반 구문을 사용하여 자바 클래스 파일을 생성한다.[6][17]
  • Jamaica: 매크로 기능을 지원하는 어셈블리 언어이다. 클래스 정의 등에는 자바 구문을 사용하지만, 메소드 내용은 바이트코드 명령어로 직접 작성한다.[7][18]
  • Krakatau Bytecode Tools: 자바 클래스 파일을 위한 디컴파일러, 디스어셈블러, 어셈블러 기능을 포함하는 도구 모음이다.[8]
  • Lilac: 자바 가상 머신용 어셈블러 및 디스어셈블러이다.[9]


자바 외에도 다양한 프로그래밍 언어들이 자바 가상 머신(JVM) 위에서 동작하도록 자바 바이트코드를 생성하는 컴파일러를 제공한다. 이러한 언어들은 JVM의 이식성과 성능을 활용할 수 있다. 대표적인 예는 다음과 같다.

  • ColdFusion
  • JRuby 및 Jython: 각각 루비와 파이썬을 JVM 위에서 실행하기 위한 구현체이다.
  • Apache Groovy: 자바 플랫폼을 위한 동적 스크립트 언어이다.
  • Scala: 객체 지향 프로그래밍과 함수형 프로그래밍 패러다임을 모두 지원하는 언어이다.
  • JGNAT 및 AppletMagic: Ada 언어를 자바 바이트코드로 컴파일한다.
  • C에서 자바 바이트코드 컴파일러: C 언어 코드를 자바 바이트코드로 변환하는 컴파일러들이 개발되었다.
  • Clojure: Lisp 계열의 함수형 프로그래밍 언어이다.
  • Kawa: Scheme 프로그래밍 언어의 JVM 구현체이다.
  • MIDletPascal
  • JavaFX 스크립트: JavaFX UI 프레임워크를 위한 스크립트 언어였으며, 바이트코드로 컴파일되었다.
  • Kotlin: 구글이 안드로이드 공식 개발 언어로 지정한 정적 타입 언어이다.
  • Object Pascal: Free Pascal 컴파일러 버전 3.0 이상을 사용하여 자바 바이트코드로 컴파일할 수 있다.[10][11]

6. JVM 언어

자바 가상 머신(JVM)을 대상으로 자바 바이트코드를 생성하는 가장 대표적인 언어는 자바이다. 초기에는 자바 소스 코드를 자바 바이트코드로 변환하는 Sun Microsystems의 javac 컴파일러만 있었지만, 현재는 자바 바이트코드 명세가 공개되어 다른 여러 컴파일러도 등장했다. 예를 들어 IBMC++로 개발한 Jikes나 GCJ (GNU 자바 컴파일러) 등이 있다.

자바 외에도 다양한 프로그래밍 언어가 JVM을 대상으로 하며, 이들 언어의 컴파일러는 소스 코드를 자바 바이트코드로 변환한다. 이러한 언어들은 자바 생태계를 확장하고 다양한 프로그래밍 패러다임을 지원한다. 주요 JVM 언어들은 다음과 같다.


  • ColdFusion
  • JRuby 및 Jython: 각각 루비와 파이썬 기반의 스크립트 언어
  • Apache Groovy: 동적 타입과 정적 타입을 모두 지원하는 범용 언어
  • Scala: 객체 지향 프로그래밍과 함수형 프로그래밍을 모두 지원하는 타입 안전 언어
  • Ada: JGNAT, AppletMagic 등의 컴파일러를 통해 지원
  • C: C 언어 소스 코드를 자바 바이트코드로 컴파일하는 컴파일러 존재
  • Clojure: 동시성 처리에 강점을 둔 Lisp 계열의 함수형 언어
  • Kawa: Scheme 언어 구현체
  • MIDletPascal
  • JavaFX 스크립트
  • Kotlin: 타입 추론 기능을 갖춘 정적 타입 지정 언어. 특히 한국에서는 안드로이드 앱 개발의 공식 언어로 채택되면서 중요성이 커지고 있다.
  • Object Pascal: Free Pascal 3.0 이상 버전의 컴파일러를 통해 지원[10][11]


또한, 자바 바이트코드를 직접 다루거나 생성하기 위한 자바 어셈블러도 존재한다. 대표적인 예로는 Jasmin[6], Jamaica[7], Krakatau Bytecode Tools[8], Lilac[9] 등이 있다. 이러한 도구들은 컴파일러 개발이나 바이트코드 분석 등에 활용된다.

7. 실행

자바 바이트코드는 자바 가상 머신(JVM) 내에서 실행되도록 설계되었다. 오늘날에는 무료 및 상용으로 다양한 종류의 자바 가상 머신이 존재한다.

가상 머신 환경이 아닌 다른 방식으로 실행해야 할 경우, 개발자는 GNU 자바 컴파일러(GCJ)와 같은 도구를 사용하여 자바 소스 코드 또는 바이트코드를 특정 컴퓨터 환경에서 직접 실행 가능한 네이티브 코드로 컴파일할 수 있다. 또한, 일부 프로세서는 하드웨어 수준에서 자바 바이트코드를 직접 처리할 수 있는데, 이러한 프로세서를 자바 프로세서라고 부른다.

8. 동적 언어 지원

자바 가상 머신(JVM)은 동적으로 형식이 지정된 언어를 어느 정도 지원한다. 하지만 기존 JVM 명령어 집합의 대부분은 정적으로 형식이 지정되어 있다. 이는 메서드 호출 시 컴파일 시간에 시그니처 유형 검사를 거치며, 이 결정을 실행 시간으로 연기하거나 다른 방식으로 메서드 디스패치를 선택하는 메커니즘이 없음을 의미한다.[12][19]

JSR 292 (''자바 플랫폼에서 동적으로 형식이 지정된 언어 지원'' 또는 ''Java™ 플랫폼 상의 동적 타입 언어 지원'')[13][20]는 이러한 제약을 해결하기 위해 JVM 수준에서 새로운 `invokedynamic` 명령어를 추가했다. 이 명령어는 기존의 정적으로 유형 검사된 `invokevirtual` 명령어와 달리, 동적 유형 검사에 의존하는 메서드 호출을 가능하게 한다.

다 빈치 머신은 동적 언어 지원을 위한 JVM 확장을 호스팅하는 프로토타입 가상 머신 구현이다. Java SE 7을 지원하는 모든 JVM에는 `invokedynamic` 명령어가 포함되어 있다.

9. 역컴파일러

기계어가 아닌 중간 단계인 바이트코드 형태로 변환되기 때문에, 자바는 실행 코드에서 소스 코드를 역으로 추출하는 프로그램인 역컴파일러(decompiler)에 상대적으로 취약하다. 특히, 라인 정보와 같은 디버깅 정보를 포함하여 컴파일된 경우, 주석을 제외하고 거의 완전한 소스 코드를 추출할 수도 있다.

이러한 취약점에 대한 대비책으로, 역컴파일 과정을 어렵게 만들고 설령 성공하더라도 원본 소스 코드의 구조를 파악하기 어렵게 만드는 난독화 도구(obfuscator)가 존재한다. 이 도구들은 주로 클래스 파일에서 디버깅 정보를 제거하고, 클래스, 함수, 변수 등의 이름을 짧고 의미 없는 문자로 바꾸는 방식으로 작동한다. 이 과정에서 부가적으로 클래스 파일의 크기를 줄이는 효과도 얻을 수 있다.

참조

[1] 웹사이트 Java Virtual Machine Specification http://docs.oracle.c[...] Oracle 2023-11-14
[2] 서적 The Java Virtual Machine Specification Oracle
[3] 간행물 The Java Programming Language
[4] 웹인용 IBM Developer https://developer.ib[...] 2006-02-20
[5] 서적 The Java Virtual Machine Specification http://docs.oracle.c[...] 2015-02-13
[6] 웹사이트 Jasmin Home Page https://jasmin.sourc[...] 2024-06-02
[7] 웹사이트 Jamaica: The Java virtual machine (JVM) macro assembler https://www.javaworl[...] 2024-06-02
[8] 웹사이트 Storyyeller/Krakatau https://github.com/S[...] 2024-06-02
[9] 웹사이트 Lilac - a Java assembler https://lilac.source[...] 2024-06-02
[10] 웹사이트 FPC New Features 3.0.0 - Free Pascal wiki https://wiki.freepas[...] 2024-06-02
[11] 웹사이트 FPC JVM - Free Pascal wiki https://wiki.freepas[...] 2024-06-02
[12] 웹사이트 InvokeDynamic: Actually Useful? http://headius.blogs[...] 2008-01-25
[13] 웹사이트 The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 292 https://www.jcp.org/[...] 2024-06-02
[14] 문서 VM Spec - Reserved Opcodes http://docs.oracle.c[...]
[15] 문서 Understanding bytecode makes you a better programmer http://www-128.ibm.c[...]
[16] 문서 A Formal Introduction to the Compilation of Java, Stephan Diehl, "Software - Practice and Experience", Vol. 28(3), pages 297-327, March 1998. http://people.cis.ks[...]
[17] 문서 Jasminホームページ http://jasmin.source[...]
[18] 문서 Jamaica : Java仮想マシン (JVM) マクロアセンブラ http://www.judoscrip[...]
[19] 웹사이트 InvokeDynamic: Actually Useful? https://headius.blog[...] 2008-01-25
[20] 문서 see JSR 292 http://www.jcp.org/e[...]
[21] 문서 Understanding bytecode makes you a better programmer http://www.ibm.com/d[...]



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com