Java (1) 컴파일
불과 한 달 전에 어느 기업의 코딩 테스트, 필기 테스트를 통과하고 면접을 보게 되었습니다.
당시 첫 면접이라 굉장히 떨리고 대답도 못한 것이 많아 떨어졌습니다.
하지만 꼭 알아야 겠다는 생각이 먼저 들더군요.
이번엔 면접 질문의 대부분이었던 Java에 대해 정리하려고 합니다.
막상 Java 코드를 작성하는 것에만 중점을 뒀지, 뭐가 어떻게 돌아가는지에 대해선 공부했던 적이 없었습니다.
누군가에게 설명하는 것이 내가 제대로 이해했는지 알 수 있는 가장 좋은 방법이라고 생각하기 때문에
나중에 면접을 보게 될 때, 꼭 기억하자라는 마음을 가지고 이 포스트를 작성합니다.
Eclipse, IntelliJ에서 프로젝트를 생성하고, Java 코드를 작성한 후 Run을 누르면?
저도 그렇고, 많은 학생분들이 Eclipse나 IntelliJ 등의 IDE를 사용하실 겁니다.
해당 언어의 프로젝트를 만들고, Run을 이용하여 만들어진 코드의 결과를 콘솔에서 확인합니다.
Run을 하면 어떻게 실행되는 걸까요?
[출처: Naver D2]
혹시 이 그림을 어디선가 보지 않으셨나요?
위의 그림이 자바 코드를 실행하는 과정을 정말 잘 표현한 그림입니다.
IDE는 위의 과정을 일일이 손으로 타이핑하지 않고, Run이란 버튼 하나로 해결해주죠.
그렇다면, 컴파일 부분을 IDE를 사용하지 않고 시도해 볼까요?
여태 IDE를 이용하여 자바 프로젝트를 진행해오셨을 것입니다. 저 또한 그랬구요.
익숙하진 않겠지만, 한 번 시도해보신다면 어떻게 자바 코드가 어떻게 컴파일 되는지 잘 이해할 수 있을 것입니다.
들어가기에 앞서, 각 운영체제에 맞는 JDK(Java Development Kit)를 다운로드 받고, 시스템 변수 설정을 완료한 채로 진행하셔야 합니다.
일단 프로젝트를 만들지 않고, 아무 폴더 내에서 java 파일 두 개를 만들어보도록 하겠습니다.
main.java
import java.util.LinkedList;
public class main{
public static void main(String args[]){
People people = new People();
people.age = 26;
people.job = "백수";
System.out.println(people.toString());
LinkedList<Integer> list = new LinkedList<>();
list.add(5);
}
}
People.java
public class People{
public int age;
public String job;
public String toString(){
return "내 나이는 " + age + ", 직업은 " + job + "입니다.";
}
}
(코드에 대해 잠깐 설명하자면, main 클래스는 People 클래스를 사용하고 있습니다. 그 뿐입니다)
윈도우 커맨드 창에서 javac를 이용하여 바이트 코드를 만들어 봅시다.
cd 커맨드를 이용하여 java 파일이 만들어진 위치로 이동한 후
cd C:\ㅇㅇㅇ\ㅁㅁㅁ\POST_JAVA\example
javac main.java
java 파일에 문법적인 오류가 없다면 정상적으로 바이트 코드로 컴파일이 됩니다.
분명 main.java만 javac를 이용하여 컴파일을 했지만, 의도치않은 People.java까지 바이트 코드(.class)가 만들어졌습니다.
main.java에서 People 클래스를 사용하고 있으니 어찌보면 당연한 결과입니다.
폴더 내의 모든 java 파일들을 컴파일 하는거 아님?
과 같은 생각이 들 수도 있습니다.
그렇다면, 만들어진 바이트 코드 파일 두 개를 지워 보고
javac People.java
만 진행해본다면, main.class는 생성되지 않는 것을 볼 수 있습니다.
이로써 컴파일을 할 때, 연관되있지 않은 클래스는 바이트 코드로 변환하지 않는다는 것을 볼 수 있습니다.
어쨌건, 바이트 코드 파일이 만들어 졌다면 이는 뭐로 이루어져 있을까요?
메모장으로 열면 알 수 없는 문자로 되어 있지만, 사실 이 파일은 바이트 코드 구조를 굉장히 잘 이루고 있습니다.
바이트 코드의 구조는 다음과 같습니다.
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
...
근데 너무 사람이 읽기 힘들죠? 그렇다면 읽기 쉽게 바꾸어 봅시다.
바이트 코드가 만들어져 있어야 합니다.
javap -verbose main.class > main.txt
폴더 내에 생성된 main.txt를 열면 다음과 같습니다.
Classfile ***/POST_JAVA/example/main.class
Last modified 2019. 4. 28; size 717 bytes
MD5 checksum fc6ba1f843c18070e938101dd3255b8d
Compiled from "main.java"
public class main
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#24 // java/lang/Object."<init>":()V
#2 = Class #25 // People
#3 = Methodref #2.#24 // People."<init>":()V
#4 = Fieldref #2.#26 // People.age:I
#5 = String #27 // 백수
#6 = Fieldref #2.#28 // People.job:Ljava/lang/String;
#7 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Methodref #2.#31 // People.toString:()Ljava/lang/String;
#9 = Methodref #32.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #34 // java/util/LinkedList
#11 = Methodref #10.#24 // java/util/LinkedList."<init>":()V
#12 = Methodref #35.#36 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#13 = Methodref #10.#37 // java/util/LinkedList.add:(Ljava/lang/Object;)Z
#14 = Class #20 // main
#15 = Class #38 // java/lang/Object
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 SourceFile
#23 = Utf8 main.java
#24 = NameAndType #16:#17 // "<init>":()V
#25 = Utf8 People
#26 = NameAndType #39:#40 // age:I
#27 = Utf8 백수
#28 = NameAndType #41:#42 // job:Ljava/lang/String;
#29 = Class #43 // java/lang/System
#30 = NameAndType #44:#45 // out:Ljava/io/PrintStream;
#31 = NameAndType #46:#47 // toString:()Ljava/lang/String;
#32 = Class #48 // java/io/PrintStream
#33 = NameAndType #49:#50 // println:(Ljava/lang/String;)V
#34 = Utf8 java/util/LinkedList
#35 = Class #51 // java/lang/Integer
#36 = NameAndType #52:#53 // valueOf:(I)Ljava/lang/Integer;
#37 = NameAndType #54:#55 // add:(Ljava/lang/Object;)Z
#38 = Utf8 java/lang/Object
#39 = Utf8 age
#40 = Utf8 I
#41 = Utf8 job
#42 = Utf8 Ljava/lang/String;
#43 = Utf8 java/lang/System
#44 = Utf8 out
#45 = Utf8 Ljava/io/PrintStream;
#46 = Utf8 toString
#47 = Utf8 ()Ljava/lang/String;
#48 = Utf8 java/io/PrintStream
#49 = Utf8 println
#50 = Utf8 (Ljava/lang/String;)V
#51 = Utf8 java/lang/Integer
#52 = Utf8 valueOf
#53 = Utf8 (I)Ljava/lang/Integer;
#54 = Utf8 add
#55 = Utf8 (Ljava/lang/Object;)Z
{
public main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class People
3: dup
4: invokespecial #3 // Method People."<init>":()V
7: astore_1
8: aload_1
9: bipush 26
11: putfield #4 // Field People.age:I
14: aload_1
15: ldc #5 // String 백수
17: putfield #6 // Field People.job:Ljava/lang/String;
20: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: invokevirtual #8 // Method People.toString:()Ljava/lang/String;
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: new #10 // class java/util/LinkedList
33: dup
34: invokespecial #11 // Method java/util/LinkedList."<init>":()V
37: astore_2
38: aload_2
39: iconst_5
40: invokestatic #12 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
43: invokevirtual #13 // Method java/util/LinkedList.add:(Ljava/lang/Object;)Z
46: pop
47: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 14
line 8: 20
line 10: 30
line 11: 38
line 12: 47
}
SourceFile: "main.java"
(컴파일러 강의를 들어보셨다면 이해가 빠르실 것입니다)
잘 보면 어셈블리어와 굉장히 유사합니다.
자세한 설명은 하지 않겠지만, JVM도 어쨌건 스택 기반이라는 것을 말씀드리고 싶었습니다.
바이트 코드도 살펴봤으니, JVM을 통해 People.class를 실행해봅시다!
java People
public static void main(String[] args){
...
}
시작 메서드 main이 없다면, 실행조차 되지 않습니다. 어찌보면 당연한 말이지만, 진입점이 없다면 시작조차 할 수 없습니다.
자동으로 찾아주지는 않는다는 것이죠.
java main
시작 지점이 있는 클래스를 실행하면 실행할 수 있습니다.
하지만, 시작 지점이 여러개가 있는 경우엔 어떻게 될까요?
이미 답은 위에 있지만, java xxx
를 통해 시작 지점을 정해주어야 합니다. xxx
가 두 개 이상이 될 수는 없습니다. 따라서 반드시 하나를 결정해야 한다는 것입니다.
아마 한 프로젝트에 여러 시작 지점이 포함된 Java 코드를 작성한 경우가 있을 것입니다. 그리고 그 프로젝트에서 Run 버튼을 통해 눌렀을 때, 엉뚱한 코드가 실행된 적도 있을 것입니다.
이는 Run 버튼을 눌렀을 때, 프로젝트에서 어떤 클래스를 시작점으로 결정하지 못해서 발생했던 해프닝입니다.
여기까지 자바의 컴파일 과정을 알아봤습니다. 다음엔 실행
에 관련된 포스트로 찾아뵙겠습니다.
댓글남기기