Classpath : JVM의 주소록
Classpath란?
Classpath는 JVM에게 다음과 같은 것들이 위치한 디렉토리, JAR 파일, ZIP 파일 목록을 지정한다.
- 프로젝트 소스 코드의 컴파일된 클래스 파일 (.class 파일)
- 의존하는 외부 라이브러리 (.jar 파일) 안에 있는 클래스 파일
- 리소스 파일 (설정 파일, 이미지, 텍스트 파일 등.)
JVM은 애플리케이션이 실행될 때 또는 실행 중에 특정 클래스를 로드해야 할 때, 이 Classpath 목록을 순서대로 탐색하여 해당 클래스 파일을 찾는다.
Classpath 지정 방법
Classpath는 운영체제 환경 변수, 빌드 도구 설정, 또는 실행 명령어를 통해 지정할 수 있다.
A. java 명령어 사용 시 (-cp 또는 -classpath)
가장 기본적이고 명시적인 지정 방법이다. 여러 경로는 운영체제별 구분자(; 또는 :)로 연결한다.
# Windows (구분자: semi-colon ;)
java -cp ".;lib/mylib.jar;bin" com.mypackage.MainClass
# Linux/maxOS (구분자: colon :)
java -cp .:lib/mylib.jar:bin com.mypackage.MainClass
- bin : 현재 프로젝트의 컴파일된 클래스 파일이 있는 디렉토리
- lib/mylib.jar : 의존하는 외부 라이브러리 파일
B. 실행 가능한 JAR 파일 사용 시 (Fat JAR)
- 일반 jar : 매니페스트 파일(MANIFEST.MF)에 Classpath 속성을 추가하고, 필요한 모든 외부 JAR 파일 목록을 명시해야 한다. (번거로움)
- Fat JAR : 모든 의존성 클래스를 .jar 파일 내부에 통합했기 때문에, 실행 시 별도의 -cp 지정이 필요 없다. JVM은 이 단일 JAR 파일 내에서 모든 클래스를 찾을 수 있다.
java -jar Server-1.0-SNAPSHOT.jar

Class Loader
Class Loader는 Classpath를 관리하고 클래스 로딩을 담당하는 JVM의 하위 시스템이다.
Class Loader는 위임 모델 (Delegation Model)에 따라 계층적으로 작동하며, 애플리케이션이 특정 클래스를 요청하면 가장 상위 로더(Bootstrap) 부터 순서대로 찾기 시작한다.
- Bootstrap Class Loader : JVM 자체의 핵심 라이브러리(JRE의 rt.jar 등)를 로드한다.
- Extension Class Loader : JRE의 확장 라이브러리(Java 9 이전의 ext 디렉터리)를 로드한다.
- Application Class Loader : 주로 우리가 설정하는 Classpath에 있는 애플리케이션 및 외부 라이브러리 JAR 파일을 로드한다.
이러한 메커니즘을 토대로 표준 라이브러리와 사용자 라이브러리가 분리되어 안정적으로 실행될 수 있다.
의존성 관리 : 왜 외부 라이브러리가 필요한가?

의존성 (Dependency)
의존성(Dependency)은 한 소프트웨어 모듈이나 컴포넌트가 자신의 기능을 수행하기 위해 다른 모듈이나 라이브러리의 코드를 필요로 하는 관계를 의미한다.
- 외부 라이브러리 : 프로젝트 외부의 코드를 가져와 사용하는 것.
(예: JSON 처리를 위한 Jackson, 웹 요청을 위한 OkHttp) - 내부 모듈 : 모노레포(Monorepo) 구조에서 한 서브 프로젝트가 다른 서브 프로젝트의 코드를 사용하는 것.
(예: Server 모듈이 Share 모듈의 Protocol.kt를 사용하는 경우)
의존성 관리의 필요성
Java나 Kotlin 같은 JVM 언어는 대규모 프로젝트에서 수백 개의 라이브러리를 사용한다. 이 때 의존성 관리가 없다면 다음과 같은 문제가 발생한다.
- Classpath 관리 복잡성 : 필요한 모든 .jar 파일을 직접 다운로드하여 프로젝트 실행 경로에 수동으로 추가해야 한다.
- 전이 의존성 문제 : 내가 사용하는 라이브러리 A가 또 다른 라이브러리 B와 C에 의존할 때, 개발자는 B와 C까지 모두 수동으로 찾아 추가해야 한다.
- 버전 충돌 (Dependency Hell) : 서로 다른 라이브러리 A와 D가 같은 하위 라이브러리 F를 사용하는데, A는 F의 1.0 버전을, D는 F의 2.0 버전을 요구할 때 충돌이 발생한다.

의존성 범위(Scope)
의존성은 프로젝트의 어떤 단계에서 필요한지에 따라 그 범위(Scope)가 나뉜다.
plugins {
// 코틀린 JVM 플러그인 적용
kotlin("jvm") version "1.9.23"
}
group = "com.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral() // 의존성 라이브러리를 검색할 저장소 지정
}
dependencies {
// 1. implementation (가장 일반적인 의존성)
implementation(kotlin("stdlib-jdk8"))
implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")
// 2. api (의존성 노출; Legacy) - 모듈 간 라이브러리 공유 시 사용
api("com.fasterxml.jackson.core:jackson-databind:2.16.0")
// 3. testImplementation (테스트 전용)
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.mockito:mockito-core:5.8.0")
// 4. runtimeOnly
runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
// 5. compileOnly
compileOnly("org.projectlombok:lombok:1.18.30")
}
// 테스트 엔진 지정 (JUnit 5 사용 선언)
tasks.test {
useJUnitPlatform()
}
| 의존성 유형 (Gradle 기준) |
설명 | 사용 예시 |
| implementation | 컴파일 및 런타임에 모두 필요하다. 가장 일반적으로 사용한다. | 웹 프레임워크 (SpringBoot), DB 드라이버 |
| api (Legacy) | implementation과 동일하지만, 외부에 노출되어 내 모듈을 사용하는 다른 모듈도 이 의존성에 접근할 수 있다. (모듈 간 API 공유 시 사용) |
Jackson 등 라이브러리를 외부에 배포할 때 |
| testImplementation | 테스트 코드를 컴파일하고 실행할 때만 필요하다. 실제 배포되는 .jar에는 포함되지 않는다. | JUnit, Mockito, Kotest 등 테스트 라이브러리 |
| runtimeOnly | 컴파일 시점에는 필요 없지만, 프로그램이 실행될 때 동적으로 로드되어야 하는 의존성 | JDBC 드라이버 (인터페이스만 컴파일 시) |
| compileOnly | 컴파일 시점에만 필요하고, 런타임에는 호스트 환경에 존재한다고 가정하는 의존성. | Lombok (어노테이션 프로세서), Servlet API (웹 서버가 제공.) |
> implementation vs. api (api가 Legacy인 이유)
api 설정 자체가 완전히 Deprecated되어 사용 금지된 것은 아니다. 하지만 현대적인 멀티 모듈 개발에서는 implementation을 기본으로 사용하는 것이 강력히 권장된다.
api 의존성 유형은 implementation 보다 더 넓은 범위를 가진다.
| 특징 | implementation (권장) | api (제한적 사용) |
| 의존성 노출 | 모듈 A가 의존하는 라이브러리 X를, 모듈 A에 의존하는 모듈 B는 볼 수 없다. | 모듈 A가 의존하는 라이브러리 X를, 모듈 A에 의존하는 모듈 B도 볼 수 있다. |
| 컴파일 효율 | 라이브러리 X가 변경되어도 모듈 B는 다시 컴파일할 필요가 없다. | 라이브러리 X가 변경되면 모듈 B도 불필요하게 다시 컴파일해야 할 수 있다. |
| 캡슐화 | 모듈 내부 구현을 캡슐화하여 격리성이 높다. | 내부 구현 라이브러리까지 외부에 유출하여 캡슐화가 깨진다. |
빌드 도구 : 프로젝트를 구축하는 건설 현장
빌드 도구의 역할
빌드 도구는 의존성을 관리하고, 소스 코드를 컴파일하며, 테스트를 실행하고, 최종적으로 배포 가능한 아티팩트(.jar, .waf)를 생성하는 소프트웨어다.
대표적으로 Maven, Gradle, Ant가 있으며, 현대 JVM 생태계에서는 Maven과 Gradle이 주로 사용된다.
Maven vs. Gradle
Maven
Maven은 규약 기반 (Convention over Configuration)의 XML 설정 파일을 사용하는 빌드 도구다.
| 특징 | 설명 |
| 설정 파일 | pom.xml (Project Object Model) 이라는 단일 XML 파일 사용 |
| 장점 | 구조가 매우 명확하고 단순하며, 학습 곡선이 낮다. 문서와 레퍼런스가 풍부하다. |
| 단점 | XML 설정이 장황해지고 복잡한 커스터마이징이 어렵다. 병렬 처리 및 증분 빌드 성능이 Gradle보다 떨어진다. |
| 의존성 관리 | 중앙 레포지토리(Maven Central) 기반으로 동작하며, 의존성 선언은 <dependencies> 태그 내에서 이루어진다. |
<!-- Maven Project Object Model (POM) 파일 -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 프로젝트 기본 정보 -->
<groupId>com.example</groupId>
<artifactId>maven-web-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<!-- 속성(Properties) 정의: 버전 관리를 쉽게 합니다. -->
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
...
</properties>
<!-- 의존성(Dependencies) 정의 -->
<dependencies>
<!-- 1. 핵심 애플리케이션 의존성 (Spring Web Starter) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
</project>
Gradle
Gradle은 유연성과 성능을 극대화한 현대 빌드 도구다. Kotlin 기반의 DSL (Domain Specific Language)을 사용하며 설정 파일을 작성하는 것이 일반적이다.
| 특징 | 설명 |
| 설정 파일 | build.gradle (Groovy DSL) 또는 build.gradle.kts (Kotlin DSL) 파일 사용. |
| 장점 | Kotlin/Groovy 코드 기반으로 로직 구현이 용이하다. 증분 빌드 및 병렬 실행을 지원하여 대규모 프로젝트에서 빌드 속도가 매우 빠르다. |
| 의존성 관리 | 중앙 레포지토리를 사용하며, dependencies 블록 내에서 함수 호출 형태로 의존성을 선언한다. |
plugins {
java
kotlin("jvm") version "1.9.23"
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
}
group = "com.example"
version = "1.0-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
// 레포지토리: 의존성 라이브러리를 가져올 위치를 정의합니다.
repositories {
mavenCentral() // 가장 기본적인 중앙 저장소
}
// 의존성 블록: 프로젝트에 필요한 라이브러리를 선언합니다.
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
}
tasks.test {
useJUnitPlatform()
}
실제 배포와 최적화
Fat JAR (Uber JAR)의 필요성
JAR(Java Archive) 파일은 기본적으로 컴파일된 클래스 파일와 리소스 파일만 포함하고, 의존하는 외부 라이브러리(.jar 파일들)는 포함하지 않는다. 이 경우, 프로그램을 실행하려면 모든 의존성 JAR 파일을 Classpath에 수동으로 추가해야 한다.
이를 보완하는 것이 Fat JAR이다. Fat JAR은 프로젝트 코드 뿐 아니라, 의존하는 모든 라이브러리의 .class 파일까지 모두 압축 해제하여 단일 .jar 파일 안에 통합한 것이다.
- 단일 파일 배포 : `java -jar your-app.jar' 명령 하나만으로 실행이 가능하며, 추가적인 의존성 파일 설정이 필요 없다.
- 배포의 단순화 : 컨테이너(Docker) 환경이나 클라우드 환경에서 배포가 매우 단순해진다.
빌드 성능 최적화
빌드 성능은 특히 대규모 프로젝트나 CI/CD 환경에서 개발 속도에 직결된다.
빌드 캐시 (Build Caching)
Gradle은 각 빌드 태스크(예: 컴파일, 테스트)의 입력(소스 코드, 의존성)을 해시(Hash)로 저장한다. 다음 빌드 시 입력 해시가 이전과 동일하다면, 실제로 작업을 다시 수행하지 않고 이전에 저장해 둔 출력 결과를 가져와 사용한다.
코드가 변경되지 않은 모듈은 0.1초 만에 `FROM CACHE` 메시지와 함께 빌드가 완료되어, 전체 빌드 시간을 획기적으로 단축한다. (증분 빌드의 심화 형태)
Wrapper의 중요성 (Gradle/Maven Wrapper)
Wrapper 빌드 도구는 프로젝트와 함께 배포되는 스크립트(gradlew 또는 mvnw)다.
로컬 개발 환경에 빌드 도구가 설치되어 있는지 여부와 관계 없이, 프로젝트가 요구하는 특정 버전의 빌드 도구를 자동으로 다운로드하고 사용하여 실행을 보장한다.
모든 개발자, 모든 CI/CD 환경에서 동일하고 일관된 빌드 결과를 얻을 수 있다.
Garbage Collection (GC)
GC는 JVM이 개발자 대신 메모리를 관리하는 기능이다. JVM의 힙(Heap) 메모리 영역에서 더 이상 어떤 변수도 참조하지 않는 객체(쓰레기)를 찾아 자동으로 메모리에서 해제한다.
개발자의 메모리 관리 부담을 줄여 생산성을 높이고, 메모리 누수를 방지한다.
Stop-The-World (STW) 현상
GC가 메모리를 청소하는 동안, 애플리케이션의 모든 쓰레드의 실행이 일시적으로 중단되는 현상이다. STW 시간이 길어지면 사용자 요청 처리 속도가 느려지거나 멈춰서 서비스의 응답 지연(Latency)이 발생한다.
현대의 GC 알고리즘 (G1 GC, ZGC 등)은 이 STW 시간을 최소화하거나, 애플리케이션 쓰레드와 동시에 GC 작업을 수행하는 동시성(Concurrent) 방식으로 발전하고 있다.
Young 영역과 Old 영역 (Generation)
JVM의 힙 메모리는 효율적인 GC를 위해 세대(Generation)로 나뉜다.
- Young Generation : 새로 생성된 객체가 저장되는 공간. 대부분의 객체는 금방 쓰레기가 되어 빠르게 사라지므로 여기서 자주 GC가 일어난다. (Minor GC)
- Old Generation : Young 영역에서 여러 번의 GC에도 살아남은 오래된 객체들이 이동하는 공간. 여기서 일어나는 GC는 STW 시간이 길어질 가능성이 높다. (Major GC or Full GC)
참고 출처
- CLASSPATH in Java - GeeksforGeeks
- ClassLoader in Java - GeeksforGeeks
- https://docs.gradle.org/5.3.1/userguide/java_plugin.html#sec:java_plugin_and_dependency_management
- https://tomgregory.com/gradle/maven-vs-gradle-comparison/
- https://happy-jjang-a.tistory.com/219
-
Powered By. Gemini
'Backend' 카테고리의 다른 글
| [Backend] Slf4J 와 Logback 로깅 도입 가이드 (0) | 2025.11.17 |
|---|---|
| Mockito-inline 5.2.0 설정 시 문제 해결 가이드 (Kotlin + Gradle + JDK17) (0) | 2025.10.30 |
| [Backend] ByteArray class 이해하기 (1) | 2025.10.16 |
| [Backend] 서버-클라이언트 연결 (ServerSocket & Socket) (0) | 2025.10.16 |
| [Backend] Kotlin N:N Chat Application (Blocking Socket) (0) | 2025.10.14 |