[Java] 컴파일 & 빌드 기본 개념 정리

rockstar·2023년 6월 6일
1

Java

목록 보기
1/2

컴파일

우리가 작성한 코드가 실행이 되기 위해서는 어떻게 해야 될까? 컴퓨터를 조금 공부해본 사람이라면 알겠지만, 사람이 작성한 코드를 컴퓨터가 이해할 수 있는 언어로 컴파일이라는 것을 통해서 바꿔줘야 한다. 그래야 컴퓨터가 코드를 읽고 문제를 찾아서 지적을 하거나 올바르게 코드가 작성이 되었을 때는 비로소 하나의 실행 파일로 만들어줄 수 있는 것이다.

일반적인 실행 환경에서는 저렇다. 그렇지만 Java는 OS(운영체제)에서 직접 실행이 되는 게 아니라, JVM이라는 가상 머신을 통해서 실행이 된다. 즉, JVM에서 돌아갈 수 있도록 JVM이 이해할 수 있는 파일로 먼저 바꿔야 된다는 것이다. 물론 이렇게 JVM이 이해할 수 있는 코드로 변환을 하는 것도 하나의 컴파일이지만, 우리가 일반적으로 알고 있던 컴파일과는 좀 다른 것 같다. 왜 컴퓨터가 아닌 JVM이 이해할 수 있는 언어로 먼저 변환을 하는 걸까? 그 전에 왜 JVM에 대해서 먼저 생각해보는 게 좋을 것 같다.

JVM의 가장 큰 존재 이유는 다양한 운영체제 위에서 돌아갈 수 있다는 것이다. Java는 플랫폼 독립적인 언어이기 때문에 운영체제가 서로 다르더라도 각각의 운영체제에 JVM 환경만 구성을 해준다면, Windows OS에서 작성된 Java코드도 Mac OS에서 무리 없이 돌아갈 수 있는 것이다.

.java 파일 (소스 코드) -> 컴파일 -> .class 파일(바이트 코드)

여기서 중요한 것은 소스코드를 컴파일했을 때 JVM이 이해할 수 있는 언어가 된 것이지 컴퓨터가 바로 이해할 수 있는 게 아니다. 일반적으로는 JVM이 .class파일을 실행하여 한 줄씩 읽고 운영체제의 인터프리터가 이해할 수 있는 형태로 변환을 해서 실행을 한다. 즉, 처음에 컴파일을 했지만 실제 실행할 때는 인터프리터 언어처럼 보여질 수 있는 것이다. 그래서 Java라는 언어가 인터프리터 방식인지 컴파일 방식인지 헷갈릴 수 있다. 이러한 인터프리터 방식의 코드 실행은 규모가 큰 프로그램의 경우 비효율적일 수 있다. 이러한 단점을 보완하기 위해 나온 JIT 컴파일이다.

JIT컴파일은 바이트 코드를 바로 네이티브 코드로 컴파일을 해서 빠른 실행을 할 수 있게 해주는 동적 컴파일 방식인데, JIT 컴파일은 사실 선택적인 사항이다. 규모가 작은 프로그램의 경우는 굳이 고려를 하지 않아도 속도의 차이를 체감하기는 어려울 것이다. 그렇지만 위에서도 말했듯이 규모가 어느 정도 있는 프로그램이라면 생각을 해보는 게 좋지 않을까 싶다.

Java 컴파일 요약

  1. java 소스코드를 JVM이 이해할 수 있는 .class 형식으로 컴파일을 한다. .class 파일은 바이트 코드로 변환이 되어 있다

  2. (바이트 코드(JVM이 이해할 수 있는 언어)와 이진 코드(운영체제가 이해할 수 있는 언어)는 다름)

  • 인터프리터 방식을 사용한다면, JVM이 프로그램을 실행할 때 .class(바이트 코드)파일을 한 줄씩 읽어 운영체제마다 가지고 있는 인터프리터라는 코드를 해석할 수 있는 프로그램이 이해할 수 있는 형태로 변환이 되어 실행이 된다.
  • JIT 컴파일 방식을 사용한다면 .class(바이트 코드)파일을 이진 코드로 변환을 한 후에, 실제 실행하게 될 운영체제가 정해질 때까지 유보하고 있다가 OS에서 실행이 될 때마다 컴파일을 하고 이진 코드는 네이티브 코드로 변환이 되어 실행이 된다.

빌드

건축에서는 건축물을 짓기 전에 설계라는 작업을 하고, 설계도가 다 만들어졌으면 실제로 시공을 하는 과정을 거칠 것이다. 개발자들도 이와 비슷하게 애플리케이션을 만들기 전 코드를 작성하는 과정을 거치는데, 이것을 설계에 비유할 수 있겠다. 그리고 나서 실행을 할 수 있게 하나의 실행 파일로 변환을 해줘야 한다. 코드에서 실행 파일로 변환하는 과정이 빌드를 하는 과정인 것이다.

빌드를 하고 Jar파일을 실행하는 것까지 Gradle 빌드 도구를 사용할 예정이다.

빌드 과정

우선 빌드를 하기 위해 거쳐야 하는 것들을 생각해보자.

초기화

Gradle 프로젝트를 초기화하고 빌드 스크립트를 설정하는데, 프로젝트에 대해서 Gradle이 알아야 되는 것들(프로젝트 구조, 의존성 관리 등의 옵션)을 입력해주는 과정이라고 생각하면 된다.

구성

이전에 작성했던 build.gradle 파일에 작성된 여러 항목들이 있을 것이다. 작성된 플러그인이나 의존성들을 가져와서 빌드 작업을 하기 위해 필요한 설정들을 수행한다.

컴파일

.java파일(소스 코드)을 .class파일(바이트 코드)로 변환을 해주는 과정을 거친다.

리소스 처리

개발을 하는 동안 작성한 속성 파일들이 있을텐데, 이러한 파일들에 접근하기 위해서는 우선 클래스 패스에 복사를 해야 한다. 예를 들어 'src/main/resources/config/application.yml'파일이 있다면, Gradle 빌드를 할 때 해당 파일은 클래스 패스에 복사되어 'classpath:config/application.yml' 경로로 접근할 수 있게 된다.

테스트 실행

테스트 코드를 실행하고, 결과를 확인한다. 테스트를 통과하지 못한다고 하더라도 .jar파일은 만들어지지만 빌드 실패로 간주될 수 있다.

패키징

컴파일된 .class(바이트 코드)파일과 리소스 파일을 패키징하여 실행 가능한 .jar파일 또는 .war파일을 생성한다.

배포

빌드한 결과물을 원하는 위치로 배포하거나 릴리즈한다.

빌드 방법

CLI(Command Line Interface)방식과 Intellij가 내부적으로 제공하는 방식대로 빌드를 해보자.

CLI

.gradlew가 저장되어 있는 프로젝트 경로에서 ./gradlew build 명령어를 실행한다.
//기본 경로가 프로젝트 내 build 디렉토리이며, jar파일이 잘 생성된 걸 볼 수 있다.

build
    ├── libs
	└── web-0.0.2-SNAPSHOT-plain.jar
	    └── web-0.0.2-SNAPSHOT.jar
6:19:48 PM: Executing 'build'...

> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :resolveMainClassName UP-TO-DATE
> Task :bootJar UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses

> Task :test

> Task :check
> Task :build

BUILD SUCCESSFUL in 3s
7 actionable tasks: 3 executed, 4 up-to-date
6:19:52 PM: Execution finished 'build'.

build에 의해 생성되는 .jar파일 또는 .war파일의 이름은 build.gradle파일 내에서 설정할 수 있다.

version = '{원하는 패키지명}' 을 통해 패키징 되는 파일의 이름을 원하는 대로 바꿀 수 있다.


잘못된 정보는 지적해주시면 감사하겠습니다.

0개의 댓글