자바 바이트 코드, 왜 만들었을까?
1. 소개
JVM 계열 언어를 다룰 때 꼭 등장하는 용어. 바이트 코드란 무엇일까요?
- 바이트 코드는 중간intermediate 언어의 일종입니다.
- 중간 언어는 주로 컴파일러의 세계에서 쓰이는 용어입니다. 원시source 언어를 목적target 언어로 변환하는 작업 중간에 생성되는 언어를 뜻합니다.1
- 가령 C++ 컴파일러는 C언어를 거쳐 기계어를 생성합니다.
- LATEX는 TEX를 거쳐 .div 파일을 생성합니다.
바이트 코드는 곧 Java 바이트 코드를 의미할까요?
- 아닙니다. 바이트 코드라는 개념은 다른 프로그래밍 언어에서도 사용됩니다.
- Python은 파이썬 바이트코드를 생성해 Python VM에서 실행합니다.2
- .NET 계열 언어들은 CIL 바이트코드를 생성해 CLR(Common Language Runtime)에서 실행합니다.3
2. Java 바이트 코드는 어떻게 생겼을까
다음과 같은 .java 파일이 있습니다.
int a = 0;
a += 1;
컴파일을 하면 .class 파일이 생성됩니다. 이 파일은 16진수의 나열입니다. 그래서 hex 에디터로 열었습니다.
00000000: cafe babe 0000 0034 000f 0a00 0300 0c07
ca, fe.. 는 각 1바이트에 해당합니다. 16진수, 즉 비트 4개(2^4)가 연달아 이어져 있기 때문입니다.
+---+---+---+---+ +---+---+---+---+
| 1 | 1 | 0 | 0 | | 1 | 0 | 1 | 0 |
+---+---+---+---+ +---+---+---+---+
0b1100 = 0xC 0b1010 = 0xA
사실 이 맨 앞 바이트들은 코드와 관련은 없습니다. cafe babe는 자바 클래스 파일의 매직넘버입니다. 왜 이런 이름이 되었는지 재밌는 읽을거리도 있습니다.
아무튼 이제 메타데이터가 아닌 main 코드(int a = 0; a += 1;)에 해당하는 부분을 찾아보겠습니다.
000000e0: .... .... .... .... 033c 8401 01b1 ....
각 바이트는 대응하는 명령어가 있습니다. 그래서 이름이 바이트 코드입니다.
- 0x03: iconst_0
- 0x3c: istore_1
- 0x84: iinc
- 0xb1: return
JVM은 이 명령어들을 해석해 기계어로 변환하고 실행합니다. 16진수에 대응하는 명령어와 뜻은 JVM Specification 문서를 참조하면 됩니다4.
그런데 문서를 일일이 찾아가며 16진수를 읽는 것은 너무 불편합니다. 다행히 javap
명령어를 사용하면 바이트 코드를 해석해 줍니다.
0: iconst_0
1: istore_1
2: iinc 1, 1
5: return
뜻은 다음과 같습니다.
- iconst_0: 0을 스택에 push
- istore_1: 1번 지역변수에 값 저장
- iinc 1, 1: 1번 지역변수의 값에 정수 1 추가
스택, push등의 용어가 나오는 이유는 JVM이 스택을 활용해 명령어를 처리하기 때문입니다.
3. 바이트 코드를 사용하는 이유
번거롭게 컴파일 중간 단계에 바이트 코드를 만든 이유는 뭘까요? 플랫폼에 구애받지 않는 실행 환경을 위해서입니다. 중간 언어의 장점 중 하나이기도 합니다.
왜 중간 언어는 이식성을 향상시킬까요? 한 책1에서 아주 좋은 예를 찾았습니다.
“If a different product is needed for each situation, then this company must develop and support s x t compilers, as shown on the left in Fig. 1; however, this work can be reduced to s + t if an IL can be introduced between the source and target specifications, as shown on the right in Fig. 1:”
IL(중간 언어)은 컴파일 번역의 수를 s*t에서 s+t로 줄여 “portable”하게 만든다는 것이 핵심입니다.
Java의 창시자 James Gosling도 동일한 이유로 바이트 코드를 도입한 것으로 보입니다. 다음은 Oak에 관한 발표 중 일부입니다5. (Oak은 프로그래밍 언어로, Java의 전신입니다)
“The solution we settled on was to compile to a byte coded machine independent instruction set that bears a certain resemblance to things like the UCSD Pascal P-Codes.”
UCSD Pascal의 P-Codes의 개념과 다소 비슷하다고 하는군요.
4. 중간 언어의 역사와 추상 기계
그렇다면 컴파일러에 중간 언어를 도입한 것은 Pascal이 처음일까요? Pascal의 창시자 Niklaus Wirth에 따르면 아닙니다6.
- BOB ROSIN(INTERVIEWER)
“Was the P-code concept inspired by earlier work? For example, the Snobol-4 implementation?”
- WIRTH
“It wasn’t particularly the Snobol-4 implementation. The technique of interpreters was well known at the time.”
인터프리터를 사용한 컴파일 기법은 이미 유행하던 개념이었다고 합니다. 그렇다면 이 개념은 누가, 언제 최초로 떠올린 걸까요?
- 중간 언어와
- 중간 언어의 실행 기계
를 나누어 살펴보겠습니다.
4-1. 중간 언어: UNCOL
Introduction to Operating Systems라는 책의 서두에서 짧은 단서를 찾았습니다7.
“The idea behind the JVM is not new. Some Pascal implementations were based on a virtual machine which executed an instruction set known as PCODE, which was also implemented in hardware at one point. BCPL had OCODE, Algol 68 had ZCODE, and so on. The idea dates back to the notion of UNCOL (the Universal Compiler-Oriented Language) in the early 1960s, which would be the emulated machine that all compilers would generate code for. UNCOL never made it off the drawing board (if that far), but its successors are alive and well.”
정리하자면
- Pascal의 중간 언어: PCODE
- BCPL의 중간 언어: OCODE
- Algol 68의 중간 언어: ZCODE
와 같이 많은 프로그래밍 언어들이 이미 “virtual machine”을 사용하고 있었으며,
- 그 시초는 UNCOL이라는 개념
이라고 합니다.
위키피디아에서 UNCOL을 찾아보면:
“UNCOL was intended to make compilers economically available for each new instruction set architecture and programming language, thereby reducing an N×M problem to N+M.”8
위에서 본 Figure 1 이미지와 정확히 일치하는 설명입니다.
4-2. 중간 언어의 실행 기계: Abstract machine
중간 언어를 해석하는 프로그램을 지칭하는 용어는 조금 복잡합니다. 인터프리터, 가상머신이라는 용어가 번갈아가며 나왔습니다. FGCS라는 저널의 한 투고글에서는 이렇게 설명합니다9.
“…some authors use the terms
emulator
orinterpreter
and some use the termvirtual machine
for implementations of abstract machines…”
이들을 묶는 개념은 Abstract machine이며, 구현 방식에 따라 이름이 달리 지어졌다는 설명입니다.
(위 투고글은 과거부터 현대까지 유명한 abstract machine들을 차례로 소개합니다. Algol부터 Python까지 다양한 프로그래밍 언어들과, 중간 언어를 처리하는 특별한 마이크로프로세서들까지 폭넓은 사례가 포함돼 있습니다. abstract machine의 각 특장점과 변천사는 이 글의 주제를 벗어나므로 읽을거리로 남겨 두겠습니다.)
5. 결론
정리해 보겠습니다.
- 바이트 코드
-
중간 언어의 일종입니다. JVM뿐만 아니라 다른 언어에서도 폭넓게 사용됩니다. 즉, 바이트 코드는 반드시 자바 바이트 코드만을 의미하지는 않습니다.
- 중간 언어
-
컴파일의 중간 단계에서 쓰이는 언어입니다. 중간 언어의 장점은 많지만, 특히 플랫폼 이식성을 높여준다는 점이 높게 평가됩니다.
- 중간 언어는 명령어 집합Instruction Set을 포함합니다. 자바 바이트 코드도 JVM 명세에 따라 16진수로 명령어를 표현합니다.
- 중간 언어의 변천사
-
컴파일러에 중간 언어를 두고자 하는 노력은 1950년대부터 계속돼 왔습니다. UNCOL이라는 개념부터 정립해 Algol, Pascal, Java 순으로 개선해 왔습니다.
- 추상 기계
-
중간 언어를 해석하고 실행하는 기계는 추상 기계Abstract machine라고 불립니다. 추상 기계는 소프트웨어일수도, 하드웨어일수도 있습니다.
- 추상 기계의 구현체는 구현자(주로 vendor사)에 따라 다른 이름으로 불리곤 했습니다. Emulator, Interpreter, Virtual Machine 등이 있습니다.
References
-
R. Cytron, “Intermediate languages,” Encyclopedia of Computer Science, vol. 5, p. 910, Jan. 2003. ↩ ↩2
-
“Glossary,” Python 3.13.0 documentation, https://docs.python.org/3/glossary.html#term-bytecode ↩
-
“Compile into .NET Framework CIL,” Microsoft Learn, https://learn.microsoft.com/en-us/previous-versions/dynamicsax-2012/appuser-itpro/compile-into-net-framework-cil ↩
-
“Chapter 6: The Java Virtual Machine Instruction Set,” The Java® Virtual Machine Specification Java SE 8 Edition, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html ↩
-
J. Gosling, “Java intermediate bytecodes,” Proc. ACM SIGPLAN Workshop on Intermediate Representations, Mar. 1995, p. 111. ↩
-
N. Wirth, “Recollections about the development of Pascal,” Pascal Session of the Institut for Computersysteme, Mar. 1993, p. 118. ↩
-
J. English, Introduction to Operating Systems: Behind the Desktop, Palgrave MacMillan, 2005, p. 10. ↩
-
J. Levine, “UNCOL and Reversing modifications from mailing lists,” IETF Mail Archive, Nov. 23, 2021. ↩
-
S. Diehl, P. Hartel, and P. Sestoft, “Abstract machines for programming language implementation,” Future Generation Computer Systems, vol. 16, no. 7, pp. 739-751, May. 2000. ↩