본문 바로가기
☕️ JAVA

JAVA의 컴파일과 실행

by kukim 2021. 11. 1.

크로스 플랫폼, 컴파일, 실행, JVM, JDK, JRE를 살펴봅니다.

1. 크로스플랫폼과 간단한 컴파일 과정

1.1 크로스플랫폼

Java란 1991년 James Gosling, Mike Sheridan, and Patrick Naughton이 당시 프로그램을 작성하는데 특정 운영체제, 디바이스마다 다른 규격에 어려움을 느끼고 "Write once, run any where(WORA), 한 번 작성하면 어디서든 실행" 된다는 가치관을 가지고 (크로스플랫폼) 시작되었다.

크로스 플랫폼 : 특정 언어의 같은 소스코드를 여러 운영체제, 플랫폼에서 실행 가능

1.2 C언어 컴파일 과정과 크로스플랫폼

  1. 소스코드 작성(.h, .c)
  2. 전처리 & 컴파일
  • input : 소스코드 (.h, .c) ⇒ 전처리기 ⇒ output : 트랜스레이션 유닛 (.pre)
  • input : 트랜스레이션 유닛(.pre) ⇒ 컴파일러 ⇒ output : 어셈블리어 코드 (.s)
  • input : 어셈블리어 코드(.s) ⇒ 어셈블러 ⇒ output : 오브젝트 코드 (.o)
  • input : 오브젝트 코드(.o) ⇒ 링커 ⇒ output : 실행파일 (.out | .exe)
  1. 실행파일 실행 (.out | .exe)

C언어의 컴파일 최종 결과는 실행파일이 나온다. 이 실행파일은 특정 운영체제(Linux, Window, MacOS) 전용 실행파일이기 때문에 Linux에서 만들 실행 파일(.out)을 Window에서 실행할 수 없다. 하지만 Linux에서 작성한 C언어 소스 코드를 Window 전용 컴파일러로 컴파일 한다면 Window에서 실행할 수 있다. 이처럼 C언어는 소스코드에 대해선 크로스 플랫폼이라 할 수 있고 컴파일 결과에 대해선 그렇지 않다 라고 할 수 있다.

1.3 크로스플랫폼, Java 컴파일 과정

  1. 소스코드 작성 (.java)
  2. 컴파일 - input : 소스코드(.java) ⇒ 컴파일(javac) ⇒ output : .class(bytecode)
  3. JVM에서 바이트코드 실행

자바의 컴파일 최종 결과는 실행파일이 아닌 바이트코드(bytecode)이다. 이는 특정 운영체제가 이해할 수 있는 기계어가 아니며 JVM(Java Virtual Machine)이라는 프로그램이 이해할 수 있는 명령어이다. C언어 같은 경우에는 운영체제가 실행할 수 있는 결과로 컴파일 되어 각 운영체제에 종속되어 바로 실행 할 수 있는 반면 Java는 바이트코드로 컴파일 되면 운영체제와 상관없이 실행할 운영체제에서 JVM이란 프로그램을 설치하면 그 위에서 실행할 수 있다. 이는 C와는 다르게 소스코드와 컴파일된 후의 바이트코드에서 크로스 플랫폼이라 할 수 있는데 문제는 JVM이 설치되어 있지 않다면 실행할 수 없다는 문제가 있다. 이를 두고 완벽한 크로스 플랫폼이라 할 수 있는가 의문이 남는다.

  • 컴파일러를 프로그래밍 언어 → 기계어로 바꾸는 과정이라 말한다면 javac는 100 컴파일러라고 할 순 없지만 사람이 이해할 수 없는 코드로 변환하는 과정이 있으므로 컴파일러라 하자. 실제 기계어로 바꾸는 과정은 뒤쪽의 JIT 컴파일 과정이 있다.

Bytecode : JVM의 인터프리터가 효율적으로 실행할 수 있는 형태(기계어가 아니다.)

java_compile

2. Hello World! (컴파일 & 실행)

  • 아래 코드는 Hello, World! 를 커맨드 창에 출력하는 코드이다.
    • 소스코드 내용은 다음에 이야기하기로 하고 이곳에선 컴파일(빌드)와 실행을 살펴본다.
// Main.java

package com.kukim;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

2.1 컴파일

# javac -d <컴파일 결과 저장할 경로> <컴파일할 .java 파일>
# -d : .class 파일 저장할 경로
# class란 폴더가 없을 땐 자동으로 생성된다.

javac -d class/ srcs/com/kukim/*.java
  • javap 를 활용한 bytecode opcode check!
javap -c Main.class
  • bytecode 내용을 살펴보면 각 코드 번호와 명령어 aload_0, invokevirtual, return 등이 있는데 이것이 JAVA에서 사용하는 opcode 이다. 이 opcode가 1바이트로 되어 있어서 bytecode라 불리며 8비트이기 때문에 2^8 = 256개의 바이트코드를 가지고 있고 모두 사용되고 있진 않다.

2.2 RUN

컴파일한 .class(bytecode) 파일을 JVM에서 실행된다.

2.2.1. java -classpath를 활용한 실행

# java -classpath <class 파일 위치> <클래스이름>
java -classpath class/ com.kukim.Main

2.2.2 jar 파일을 활용한 실행

  • 소스코드가 적을 때는 위의 실행처럼 단일 .class 파일만 실행하면 된다. 하지만 많은 양의 소스코드를 넘겨줄 땐 C나 C#의 .dll 이나 lib으로 만들어 배포하듯이 JAVA에서는 .jar 압축 파일형태로 묶어 배포한다. jar은 단순히 .zip 파일과 유사하지만 그 안에 META-INF라는 폴더에 MANIFEST 파일에 압축 하일에 대한 상세 내용이 들어 있고 JVM은 이 데이터를 가지고 실행한다.
  • 따라서 jar로 압축할 때 Manifest.txt 파일을 활용해 내 소스코드의 main 함수 위치(entry point)를 알려주어야 한다.
# 1. Manifest.txt 파일 만들기
# 2. jar로 압축하기
jar -cfm <jar 저장할 디렉토리> <Manifest.txt 위치> <최상단 .class 디렉토리 위치>
## -c : create / -f : .jar 파일 이름 설정 / -m : manifest 파일 경로
# 3. java로 실행하기
java -jar ./lib/deploy.jar

2.2.3 IDE를 활용한 실행

  • 항상 자바를 실행할 때 위의 복잡한 과정을 거쳐야 하는가? NO!
  • 실제로는 IDE를 활용해 컴파일, 실행한다.
  • Intellij & Eclipse을 활용하자.

+a) 서로 다른 JAVA 버전 간의 컴파일과 실행

자바는 버전 1부터 현재 15까지 있다.

  • java 14의 javac(컴파일 옵션 X) 컴파일하여 java 8 버전에서 실행이 가능할까?
    • 정답 : X
  • java 8의 javac(컴파일 옵션 X) 컴파일하여 java 14 버전에서 실행이 가능할까?
    • 정답 : O
  • java 14의 javac(컴파일 옵션 O) 컴파일하여 java 8 버전에서 실행이 가능할까?
    • 정답 : O

하위버전에서 컴파일한 자바 소스코드는 상위버전에서 문제없이 돌아가지만 상위버전애서 컴파일한 소스코드는 어떤 버전에서 실행할 지 컴파일 할 때 옵션을 주어야 하위 버전에서 실행할 수 있다.

# javac version = JAVA 14
## 컴파일 옵션 : -source, -tartget 을 주어 1.8(java 8 명시)
javac main.java -source 1.8 -target 1.8

# java 14에서 버전8 옵션을 주고 컴파일한 결과는 java 8 버전에서 문제없이 실행 가능
java main.class

JVM 구성 요소

🔎 JVM(Java Virtual Machine) : javac를 통해 컴파일 된 .class(Bytecode)를 각각의 운영체제에 맞게 기계어로 번역하여 실행할 수 있게 만들어주는 프로그램이다.

1. Class Loader Sub System

  • 앞에서 컴파일된 .class file(bytecode)들을 로딩, 링킹, 초기화 단계를 거쳐 실제 메모리를 할당하는 역할을 한다.

2. Runtime Data Area

  • Class Loader sub System을 통해 할당된 메모리 공간이다.
    1. Method Area
    2. Heap Area
    3. Stack Area
    4. PC register
    5. Native Method Stack

3. Execution Engine

3.1 Interpreter

  • 컴파일된 .class의 바이트 코드를 기계어(0101010...)으로 변환하며 실행한다.
  • 최초의 JVM은 인터프리터 방식이었다. 하지만 동일한 메서드를 매번 해석하여 실행하는 성능의 이슈가 발생하였다.

3.2 JIT(Just In Time) Compiler

  • 인터프리터의 성능 이슈를 해결하기 위해 생긴 방법이다.
  • 자주 사용되는 메서드를 체크한 뒤 기계어로 변환한 뒤 캐싱하여 재사용하는 방법이다.
  • JIT 컴파일러는 모든 메소드에 사용되는 것이 아니다. 빈도가 적은 메소드에는 인터프리터가 더 빠를 수 있다.

3.3 GC(Garbage Collector)

  • C언어 같은 경우 동적 메모리 할당을 통해 힙 영역의 데이터를 읽고 쓴다. 이때 사용자가 직접 동적 메모리 할당을 해제해야 하는 어려움이 있다. (C = unmanaged language)
  • JAVA는 힙 영역의 메모리 관리를 사용자가 하지 않고 GC가 해준다. (JAVA = managed language)
  • 이는 매우 강력하다.

4. Native Method

  • JAVA에서 C, C++, assembly로 작성된 라이브러리를 사용할 수 있게 도와준다.

JDK와 JRE의 차이

  • JDK(Java Development Kit)
    • JDK = JRE(JVM + Library) + JAVA 개발도구(컴파일러javac, 디버거 등)
  • JRE(Java Runtime Environment)
    • JRE = JVM + Library
    • JAVA9 버전부터 더이상 JRE는 배포하지 않고 JDK만 배포한다.

Reference 🌏

'☕️ JAVA' 카테고리의 다른 글

📕 소프트웨어의 품질과 그 특성들  (0) 2022.01.08
JAVA의 클래스  (0) 2021.11.01
JAVA의 제어문  (0) 2021.11.01
JAVA의 연산자  (0) 2021.11.01
JAVA의 변수와 타입  (0) 2021.11.01

댓글