사전 JAR
포맷

JAR

gabury1고친 사람 github-actions[bot]

JAR 는 자바로 만든 프로그램을 파일 하나로 묶어 옮기고 실행하게 해 줍니다. 컴파일해서 나온 클래스 파일 수백 개와 그림·설정 같은 곁딸린 파일을 한 덩이에 담습니다. 자바 도구들은 이 덩이를 풀지 않은 채로 열어서 필요한 클래스만 그때그때 꺼내 씁니다.

쉽고 빠른 이해

JAR 는 자바 프로그램 한 벌을 파일 한 개로 묶어 둔 것입니다. app.jar 하나를 건네면 받는 쪽은 그 안에 든 클래스 수백 개를 통째로 받습니다.

자바는 소스 파일 하나를 클래스 파일 하나로 컴파일합니다. 클래스가 수백 개면 파일도 수백 개라, 묶어 두지 않으면 그 무더기를 디렉터리 모양 그대로 옮기고 한 개도 빠뜨리지 않았는지 세어야 합니다.

어떻게 도나:

  1. 클래스 파일과 곁딸린 파일을 디렉터리 모양 그대로 담아 압축합니다
  2. 설명 쪽지를 한 장 같이 넣습니다. 어느 클래스에서 실행을 시작하는지 같은 것을 적습니다
  3. 자바 도구는 이 파일을 풀지 않고 열어, 지금 필요한 항목 하나만 꺼내 읽습니다

혼자 도는 프로그램이든 남이 가져다 쓰는 라이브러리든 이것으로 건넵니다. 웹 서버에 얹을 때만 다른 묶음을 씁니다.

대가는 안에 무엇이 들었는지만 적을 수 있다는 점입니다. 이 묶음이 혼자 돌 수 없고 다른 묶음이 더 있어야 한다는 사정은 적을 데가 없습니다. 그래서 어떤 묶음이 더 필요한지는 빌드 도구가 곁에 따로 적어 둡니다.

상세

JAR 파일 한 개는 압축 파일 하나입니다. 아래에서는 그 파일을 열어 항목 이름과 설명 쪽지의 내용을 직접 꺼내 놓고, 자바가 그것을 어떻게 읽어 쓰는지까지 봅니다.

이름과 바탕이 된 압축 포맷

JAR 는 Java Archive 를 줄인 이름입니다. 우리말로 옮기면 자바 묶음입니다. 파일 이름은 app.jar 처럼 .jar 로 끝납니다.

이 포맷은 바닥부터 새로 만든 것이 아닙니다. ZIP(ZIP archive, 파일 여럿을 하나로 묶어 압축해 담는 파일 형식)을 그대로 쓰고, 그 위에 자바가 쓰는 약속을 얹었습니다. 그래서 JAR 파일은 일반 압축 프로그램으로도 열립니다.

얹은 약속은 두 가지입니다. 안에 담는 파일을 패키지 이름과 같은 디렉터리 모양으로 넣는다는 것, 그리고 META-INF 라는 이름의 디렉터리를 자바 도구가 읽는 특별한 칸으로 비워 둔다는 것입니다.

묶는 까닭과 쓰이는 곳

묶음이 필요한 까닭은 자바가 소스 파일 하나를 클래스 파일 하나로 컴파일하기 때문입니다. 클래스가 수백 개면 파일도 수백 개가 됩니다. 묶지 않으면 그 무더기를 디렉터리 모양 그대로 옮겨야 합니다. 하나도 빠지지 않았는지도 세어야 합니다.

파일 하나로 묶어 두면 옮기는 일이 파일 한 개를 옮기는 일이 됩니다.

쓰임은 둘로 갈립니다. 하나는 혼자 실행되는 프로그램을 담는 묶음입니다. 다른 하나는 남이 가져다 쓰는 라이브러리를 담는 묶음입니다.

둘 다 같은 포맷입니다. 실행을 시작할 클래스를 적어 두었는지만 다릅니다. 다만 웹 서버에 얹는 묶음은 무엇을 어디에 담을지가 정해져 있어서 WAR(Web Application Archive, 웹 애플리케이션 묶음)라는 다른 약속을 씁니다.

묶음 안에 담기는 것

JAR 안에 든 파일 하나하나를 항목이라고 부릅니다. 항목에는 이름이 붙어 있습니다. 그 이름이 곧 묶음 안에서의 경로입니다.

묶음 하나를 쪼개 보면 항목이 두 칸으로 갈립니다.

flowchart TD
    J["app.jar"]
    subgraph P["프로그램이 읽는 것"]
        X["com/example/ · 패키지와 같은 디렉터리"]
        X --> A["App.class · 항목"]
        X --> B["util/Text.class · 항목"]
        Z["messages.properties · icons/logo.png · 곁딸린 파일"]
    end
    subgraph M["묶음 자신에 대한 설명"]
        V["META-INF/MANIFEST.MF · 설명 쪽지"]
    end
    J --> X
    J --> Z
    J --> V

항목 이름만 뽑아 보면 이런 모양입니다.

com/example/App.class          # 클래스 파일
com/example/util/Text.class
messages.properties            # 곁딸린 설정
icons/logo.png                 # 곁딸린 그림
META-INF/MANIFEST.MF           # 설명 쪽지

앞 넷은 클래스 파일과 곁딸린 파일입니다. 클래스 파일은 자바 소스를 컴파일해서 나온 것으로, 바이트코드라고 부르는 기계어 비슷한 명령이 들어 있습니다.

곁딸린 파일은 프로그램이 실행 중에 읽는 그림·문구·설정입니다. 코드가 아니라서 클래스 파일과 성격이 다르지만, 같이 옮겨 다녀야 하므로 한 묶음에 넣습니다.

마지막 META-INF 디렉터리만 뜻이 다릅니다. 프로그램이 읽는 자료가 아니라 묶음 자신에 대한 설명이 들어가는 칸입니다.

매니페스트 — 묶음에 딸린 설명 쪽지

META-INF/MANIFEST.MF 는 묶음에 딸린 설명 쪽지입니다. 이 파일을 매니페스트라고 부릅니다. 안은 이름: 값 꼴의 줄이 이어지는 평범한 글자 파일입니다.

Manifest-Version: 1.0
Main-Class: com.example.App
Class-Path: lib/util.jar lib/text.jar

Main-Class 는 실행을 어느 클래스에서 시작할지 적는 줄입니다. 이 줄이 있으면 묶음 하나를 그대로 실행할 수 있습니다. 없으면 시작할 곳을 실행할 때 손으로 대 줘야 합니다.

Class-Path 는 이 묶음 곁에 같이 놓여야 하는 다른 묶음의 경로입니다. 경로를 적을 뿐이라 그 파일이 거기 없으면 실행 중에 클래스를 못 찾고 멈춥니다.

만들고 들여다보는 명령

자바 배포판에는 묶음을 다루는 jar 명령이 들어 있습니다. 묶고, 안을 들여다보고, 푸는 일을 합니다.

터미널
jar --create --file app.jar -C classes .   # 묶는다
jar --list --file app.jar                  # 항목 이름을 늘어놓는다
java -jar app.jar                          # 쪽지가 가리킨 클래스부터 시작

마지막 줄이 매니페스트를 쓰는 대목입니다. -jar 를 붙이면 JVM(Java Virtual Machine, 자바 가상 머신)이 묶음 안의 쪽지를 먼저 읽고, Main-Class 가 가리키는 클래스에서 실행을 시작합니다.

손으로 묶는 일은 드뭅니다. 실무에서는 빌드 도구가 컴파일한 뒤 이 묶는 단계까지 이어서 돌립니다.

풀지 않고 항목 하나만 꺼내는 구조

JAR 파일은 항목을 하나씩 압축해 앞에서부터 쌓습니다. 맨 뒤에는 목차가 붙습니다. 목차에는 항목마다 이름과 그 항목이 파일 어디쯤에서 시작하는지가 적혀 있습니다. 그리고 파일 맨 끝에 목차가 어디서 시작하는지를 적은 짧은 기록이 하나 더 붙습니다.

아래 그림은 위가 파일의 앞이고 아래가 파일의 끝입니다.

block-beta
columns 1
  a["항목 · 압축된 클래스 파일"]
  b["항목 · 압축된 클래스 파일"]
  c["나머지 항목들"]
  d["목차 · 항목 이름과 시작 위치"]
  e["끝 기록 · 목차가 어디서 시작하나"]

읽는 쪽은 파일 끝에서 시작합니다. 끝 기록을 보고 목차가 어디 있는지 알아낸 다음, 목차에서 찾는 항목의 이름을 보고 그 항목만 읽어 풉니다. 앞에 놓인 항목들은 건드리지 않습니다.

sequenceDiagram
    participant JVM
    participant 묶음
    JVM->>묶음: 끝 기록을 읽는다
    묶음-->>JVM: 목차가 시작하는 위치
    JVM->>묶음: 목차에서 찾는 항목의 이름을 본다
    묶음-->>JVM: 그 항목이 시작하는 위치
    JVM->>묶음: 그 항목만 읽어 푼다
    Note over JVM,묶음: 앞에 놓인 항목은 건드리지 않는다

이 구조 덕분에 실행 중에 클래스를 하나씩 꺼내 쓸 수 있습니다. 자바는 프로그램을 시작할 때 모든 클래스를 읽지 않습니다. 클래스가 처음 쓰이는 순간에 그것만 찾아 읽습니다.

어느 묶음을 뒤질지는 클래스패스가 알려 줍니다. 클래스를 찾을 때 뒤져 볼 묶음들을 순서대로 늘어놓은 목록입니다. 자바는 이 목록의 순서대로 뒤지다 처음 만난 것을 씁니다.

서명 — 묶은 뒤 바뀌었는지 가리는 장치

META-INF 에는 서명도 들어갑니다. 서명은 항목마다 낸 해시 값의 목록과, 그 목록이 진짜라는 것을 보증하는 디지털 서명으로 이루어집니다.

받는 쪽은 항목을 읽을 때마다 해시를 다시 내서 목록의 값과 견줍니다. 값이 어긋나면 그 항목은 묶은 뒤에 누군가 바꿔치기한 것입니다.

flowchart TD
    subgraph M["META-INF 안"]
        H["항목마다 낸 해시 값의 목록"]
        S["목록을 보증하는 디지털 서명"]
    end
    A["항목"] --> B["해시를 다시 낸다"]
    B --> C{"목록의 값과 같나"}
    H --> C
    C -->|같다| D["그대로 쓴다"]
    C -->|다르다| E["묶은 뒤 바뀐 것이다"]

그래서 서명된 묶음은 뜯어 고칠 수 없습니다. 안의 클래스 하나를 갈아 끼우고 다시 묶으면 해시가 달라집니다. 서명은 그 순간 무효가 됩니다.

이 포맷이 못 담는 것

JAR 가 담는 것은 파일 목록과 그 내용입니다. 그 밖의 사정은 적을 곳이 마땅치 않습니다. 실무에서 겪는 골치 대부분이 거기서 나옵니다.

못 담는 것 그래서 생기는 일
필요한 다른 묶음의 이름과 판 빌드 도구가 곁에 따로 목록 파일을 둔다
같은 이름 항목 둘 중 어느 쪽이 맞나 클래스패스에서 먼저 나온 쪽이 쓰이고 나머지는 묻힌다
묶음 안의 묶음 자바가 안 읽어 준다. 안을 헐어 한 층으로 펴서 다시 묶는다

첫 줄이 제일 아프게 옵니다. Class-Path 줄이 있긴 하지만 곁에 놓인 파일의 경로를 적을 뿐이라, 어떤 판이 필요하고 어디서 받아 오라는 말은 못 적습니다. 그 몫을 빌드 도구의 목록 파일이 대신 집니다.

둘째 줄은 같은 경로의 클래스가 여러 묶음에 들어 있을 때입니다. 포맷은 어느 쪽이 맞는지 판정해 주지 않습니다. 자바는 클래스패스에서 먼저 만난 쪽을 씁니다. 그래서 묶음 순서를 바꾸면 프로그램 동작이 달라집니다.

셋째 줄은 묶음 여럿을 하나로 합칠 때 걸립니다. 필요한 묶음을 그대로 안에 넣어 봐야 자바가 안 읽으므로, 합치는 도구는 묶음들을 전부 헐어 항목을 한 층으로 펴 놓고 다시 묶습니다. 이때 같은 경로의 항목이 서로를 덮어써서, 합친 묶음에만 있는 고장이 생기기도 합니다.

flowchart TD
    subgraph U["합치기 전 · 묶음 셋"]
        A["util.jar · com/example/util/Text.class"]
        B["text.jar · com/example/util/Text.class"]
        C["나머지 항목들"]
    end
    A --> F["전부 헐어 항목을 한 층으로 편다"]
    B --> F
    C --> F
    F -->|"같은 경로는 한쪽만 남는다"| G["합친 묶음 하나"]

관련 항목

JAR 안에 담기는 파일

클래스 파일 · 바이트코드 · 매니페스트 · 리소스 파일

JAR 를 열어 클래스를 꺼내는 실행 환경

JVM · 클래스 로더 · 클래스패스 · JRE · 모듈 경로

JAR 를 만들어 내는 도구

javac · jar 명령 · Gradle · Maven · JDK

JAR 가 딛고 선 압축 포맷과 알고리즘

ZIP · Deflate · gzip · tar · 압축

자바에서 JAR 옆에 쓰는 다른 묶음 포맷

WAR · EAR · jmod · 멀티 릴리스 JAR · fat JAR

JAR 를 여러 개 쌓을 때 터지는 문제

의존성 충돌 · 클래스패스 지옥 · NoClassDefFoundError · ClassNotFoundException

JAR 를 올려 두고 내려받는 배포 경로

아티팩트 저장소 · Maven Central · 의존성 관리 · 빌드 도구

JAR 의 내용이 안 바뀌었음을 보이는 수단

디지털 서명 · 해시 · 인증서 · 체크섬

JAR 가 속하는 상위 분류

포맷 · 아카이브 파일 · 배포 산출물 · 패키지

다른 이름: jar · Java Archive · 자바 아카이브 · JAR 파일