JDK 23 핵심 기능 — Markdown Javadoc과 ZGC 세대별 기본값

JDK 23의 위치

2024년 9월 17일 출시된 JDK 23은 비LTS 릴리스이지만, 개발자 경험과 GC 성능 양쪽에서 의미 있는 진전을 이룬 버전입니다. 특히 Markdown Documentation Comments(JEP 467)가 정식 확정되면서, 20년 넘게 유지되어 온 Javadoc 작성 방식에 근본적인 변화가 시작되었습니다.

이 글에서는 JDK 23의 핵심 JEP을 실행 가능한 예제와 함께 정리합니다.

Markdown Documentation Comments (JEP 467)

기존 Javadoc은 HTML 태그 기반이었습니다. 단순한 목록 하나를 작성하려면 <ul>, <li> 태그를 일일이 넣어야 했고, 코드 블록은 <pre>{@code ...}</pre> 같은 번거로운 문법을 사용해야 했습니다. JDK 23부터 Javadoc 주석을 Markdown으로 작성할 수 있습니다.

Markdown Javadoc은 주석의 시작 부분에 ///(슬래시 세 개)를 사용합니다. 기존 /** ... */ 블록 주석과 구분됩니다.

import java.util.List;
import java.util.stream.Collectors;

/// 사용자 이름 목록을 처리하는 유틸리티 클래스입니다.
///
/// ## 주요 기능
/// - 이름 필터링 (길이 기준)
/// - 대문자 변환
/// - 정렬
///
/// ## 사용 예시
/// ```
/// List<String> result = NameProcessor.filterAndSort(names, 3);
/// ```
///
/// @param names 처리할 이름 목록
/// @param minLength 최소 글자 수
/// @return 필터링 및 정렬된 이름 목록
public class NameProcessor {

    /// 주어진 최소 길이 이상의 이름만 필터링하여 정렬된 리스트를 반환합니다.
    ///
    /// **참고**: `null` 요소는 자동으로 제외됩니다.
    public static List<String> filterAndSort(List<String> names, int minLength) {
        return names.stream()
            .filter(name -> name != null && name.length() >= minLength)
            .sorted()
            .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        // Markdown Javadoc 기능 테스트
        List<String> names = List.of("김", "이순신", "홍길동", "박", "세종대왕");
        List<String> result = filterAndSort(names, 3);
        System.out.println("3글자 이상 이름: " + result);
        // 출력: 3글자 이상 이름: [세종대왕, 이순신, 홍길동]

        List<String> all = filterAndSort(names, 1);
        System.out.println("전체 이름 (정렬): " + all);
        // 출력: 전체 이름 (정렬): [김, 박, 세종대왕, 이순신, 홍길동]
    }
}

Markdown Javadoc의 핵심 이점은 가독성입니다. HTML 태그 없이 자연스러운 문서를 작성할 수 있고, GitHub이나 IDE 미리보기에서도 바로 렌더링됩니다. 기존 @param, @return 같은 Javadoc 태그는 그대로 사용할 수 있으므로 기존 워크플로우와도 호환됩니다.

ZGC Generational Mode by Default (JEP 474)

ZGC는 JDK 15에서 정식 도입된 저지연 GC입니다. JDK 21에서 세대별(Generational) 모드가 추가되었고, JDK 23에서 이 모드가 기본값이 되었습니다.

세대별 GC는 “대부분의 객체는 금방 죽는다”는 **약한 세대 가설(Weak Generational Hypothesis)**에 기반합니다. 마트의 신선식품 매대를 생각하면 됩니다. 유통기한이 짧은 상품(Young 객체)은 자주 점검하고, 장기 보관 식품(Old 객체)은 가끔 확인하는 것이 효율적입니다.

import java.util.ArrayList;
import java.util.List;

public class ZgcGenerationalDemo {
    public static void main(String[] args) {
        // ZGC 세대별 모드 효과를 보여주는 시나리오
        // 실행: java -XX:+UseZGC ZgcGenerationalDemo

        // Young 세대: 대부분의 객체가 여기서 생성되고 빠르게 회수됨
        long startMemory = Runtime.getRuntime().freeMemory();

        // 짧은 수명의 객체를 대량 생성 (Young 세대 대상)
        for (int i = 0; i < 1_000_000; i++) {
            String temp = "임시객체-" + i; // 즉시 참조 해제 → Young GC에서 회수
        }

        long afterYoung = Runtime.getRuntime().freeMemory();

        // 장수 객체: Old 세대로 승격됨
        List<String> longLived = new ArrayList<>();
        for (int i = 0; i < 10_000; i++) {
            longLived.add("장수객체-" + i); // 참조 유지 → Old 세대로 승격
        }

        long afterOld = Runtime.getRuntime().freeMemory();

        System.out.println("=== ZGC Generational Mode 시뮬레이션 ===");
        System.out.println("초기 여유 메모리: " + formatBytes(startMemory));
        System.out.println("Young 객체 생성 후: " + formatBytes(afterYoung));
        System.out.println("Old 객체 유지 중: " + formatBytes(afterOld));
        System.out.println("장수 객체 수: " + longLived.size());
        // 출력 예시:
        // === ZGC Generational Mode 시뮬레이션 ===
        // 초기 여유 메모리: 245.8 MB
        // Young 객체 생성 후: 198.3 MB
        // Old 객체 유지 중: 196.1 MB
        // 장수 객체 수: 10000
    }

    static String formatBytes(long bytes) {
        return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
    }
}

JDK 23 이전에는 세대별 모드를 사용하려면 -XX:+ZGenerational 플래그가 필요했습니다. JDK 23부터는 -XX:+UseZGC만 지정하면 자동으로 세대별 모드가 적용됩니다. 성능 수치로 보면 처리량(throughput) 약 10% 향상, P99 일시정지(pause) 시간 10~20% 개선이 보고되었습니다.

Module Import Declarations (JEP 476, Preview)

import java.util.*처럼 패키지를 가져오는 대신, 모듈 전체를 한 줄로 가져올 수 있는 기능입니다. JDK 23에서 프리뷰로 도입되었습니다.

// JDK 23 Preview 기능 — 컴파일: javac --enable-preview --source 23 ModuleImportDemo.java
// import module java.base; // 한 줄로 java.base 모듈의 모든 패키지 사용 가능

// 프리뷰 기능이므로 기존 import로 동일한 동작을 보여줍니다
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ModuleImportDemo {
    public static void main(String[] args) {
        // Module Import가 정식화되면 위의 import 3줄이 아래 한 줄로 대체됩니다
        // import module java.base;

        // 모듈 임포트의 효과: java.util, java.util.stream, java.io 등 모두 사용 가능
        List<String> frameworks = List.of("Spring", "Quarkus", "Micronaut", "Helidon");

        Map<Integer, List<String>> grouped = frameworks.stream()
            .collect(Collectors.groupingBy(String::length));

        System.out.println("프레임워크 (글자수별 그룹핑):");
        grouped.forEach((len, names) ->
            System.out.println("  " + len + "글자: " + names));
        // 출력:
        // 프레임워크 (글자수별 그룹핑):
        //   6글자: [Spring]
        //   7글자: [Quarkus, Helidon]
        //   10글자: [Micronaut]
    }
}

현재 Java 프로젝트에서 import 문이 10줄, 20줄씩 늘어나는 것은 흔한 일입니다. Module Import가 정식화되면 import module java.base; 한 줄로 java.util, java.io, java.time 등 기본 패키지를 모두 사용할 수 있어 보일러플레이트가 대폭 줄어듭니다.

그 외 주목할 JEP

Stream Gatherers (JEP 473, Second Preview): Stream API에 사용자 정의 중간 연산을 추가할 수 있는 gather() 메서드입니다. 기존 map, filter, flatMap으로 표현하기 어려운 윈도우 처리, 상태 기반 변환 등을 구현할 수 있습니다. 이 기능은 JDK 24에서 정식 확정되었습니다.

Implicitly Declared Classes (JEP 477, Third Preview): 클래스 선언 없이 바로 void main() 메서드만 작성하는 기능입니다. 교육용 또는 스크립팅 목적으로 Java 진입 장벽을 낮추는 것이 목표입니다. JDK 25에서 정식 확정되었습니다.

sun.misc.Unsafe 메모리 접근 메서드 Deprecation (JEP 471): 오래전부터 “사용하지 마세요”라는 경고가 붙어 있었지만, 수많은 라이브러리가 성능 때문에 사용해 온 sun.misc.Unsafe의 메모리 접근 메서드가 공식적으로 deprecated 되었습니다. 대안은 JDK 22에서 정식화된 Foreign Function and Memory API(java.lang.foreign)입니다.

JDK 23 성능 요약

항목변화
ZGC 처리량세대별 기본값으로 ~10% 향상
ZGC P99 일시정지10~20% 개선
ZGC 모드세대별이 기본값, 비세대별은 유지

정리

기능상태핵심 포인트
Markdown Javadoc정식/// 문법으로 Markdown 문서 주석 작성
ZGC Generational 기본값정식처리량 10%↑, P99 일시정지 10~20%↓
Module Import프리뷰import module java.base;로 전체 모듈 임포트
Stream Gatherers2nd 프리뷰사용자 정의 Stream 중간 연산
Implicitly Declared Classes3rd 프리뷰클래스 없이 void main() 작성
Unsafe Deprecation정식sun.misc.Unsafe 메모리 메서드 deprecated

JDK 23은 비LTS이지만 실무에 직접적인 영향을 주는 변화가 많습니다. Markdown Javadoc은 문서 작성 습관을 바꿀 수 있는 기능이고, ZGC 세대별 기본값은 별도 튜닝 없이도 성능 향상을 얻을 수 있는 변화입니다. 프리뷰로 등장한 Module Import와 Stream Gatherers는 이후 버전에서 정식화되었으니, 미리 문법에 익숙해지는 것을 권장합니다.

이 글이 도움이 되었나요?