목차

자바 최적화 2판 – 책 소개

🗓️

자바, 느림의 미학을 파헤치다

한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬 받아 작성된 서평입니다.

  • 원제 : Optimizing Cloud Native Java, 2E
  • 저자 : Benjamin J. Evans, James Gough
  • 출판 : 한빛미디어 2025

우리는 종종 자바를 “느린 언어”라고 여긴다. 무겁고, 메모리를 많이 잡아먹고, 성능을 확보하려면 많은 노력이 든다고. 하지만 그런 고정관념을 뒤흔들며, 자바 성능 문제의 본질을 ‘시스템 전체의 흐름’ 속에서 조망한 책이 있다. 바로 이 ‘자바 최적화’다.

이 책은 단순한 튜닝 가이드가 아니라, 현대 JVM 기반 애플리케이션이 직면한 병목의 원인과 해법을 심층적으로 파헤친 기술서이자 실천서다. 성능 이슈를 툴의 문제가 아니라, 언어, 하드웨어, 운영환경이 결합된 결과물로 이해하도록 이끈다는 점에서, 매우 흥미로운 독서 경험이었다.

느려지는 원인을 추적하며, JVM을 제대로 이해하기

자바 최적화의 1~3장은 “성능 저하의 원인”에 대한 집요한 추적이다. 자바 애플리케이션이 느려지는 구조적인 이유를 설명하기 위해, 저자는 JVM의 내부 구조부터 현대 하드웨어의 병렬 처리 방식까지 차근차근 짚어 나간다. 이 파트는 단순한 GC나 JIT 설정 팁을 알려주는 섹션이 아니라, 성능 문제의 근원을 “기술 시스템 전체”의 맥락에서 분석하는 문제 해결 프레임을 제공한다.

시스템적 시각으로 성능을 바라보다

2장까지는 성능 테스트의 방법론을 이야기하면서, 단순한 벤치마크 이상의 시야를 제시한다. 저자는 “테스트는 진단도구일 뿐이며, 그 결과를 해석하고 조치를 취하는 것이 진정한 성능 최적화”라고 강조한다. 특히 시스템적 사고 편향, 확증 편향, 혼란 속의 행동 편향 등 개발자가 실제로 빠지기 쉬운 판단 오류를 지적하며, 성능 문제를 복잡계 시스템의 관점에서 이해해야 한다고 주장한다.

이 과정에서 소개된 예시들은 실제 대규모 서비스에서 발생 가능한 병목, 예기치 못한 GC 일시정지, 또는 OS 스레드 충돌 상황 등을 현실감 있게 설명해 준다. 단순히 자바만이 아니라, “애플리케이션이 어떤 시스템 위에서 동작하는가” 에 대한 근본적 성찰을 유도한다.

JVM을 면밀히 해부하다

3장은 JVM의 구성요소—인터프리터, 클래스 로딩 메커니즘, 바이트코드 처리 흐름, 메모리 모델과 GC 방식, 그리고 스레드 스케줄링 전략—을 통해 자바 프로그램이 실제로 어떻게 실행되고 있는지를 상세히 설명한다. 이 장에서는 특히 아래와 같은 핵심 개념들이 깊이 다루어진다:

  • 클래스 로딩 구조: ClassLoader 체인을 통해 부트스트랩 클래스, 시스템 클래스, 사용자 클래스가 단계적으로 로딩되는 과정을 시각화하며, 자바 9 이후 모듈화된 런타임의 변화도 함께 짚는다.
  • 바이트코드와 javac: 자바 소스 코드가 .class 파일로 컴파일되고 JVM에서 인터프리트되어 실행되기까지의 단계를 실제 예제를 통해 설명한다.
  • JMM (Java Memory Model): 스레딩 환경에서 변수의 가시성과 동기화 문제를 해결하기 위한 JMM의 설계 철학을 소개하며, 자바가 멀티스레드 환경에서도 안정적으로 동작할 수 있도록 한 메커니즘을 분석한다.
  • 가비지 컬렉션: 자바의 자동 메모리 관리를 가능케 하는 GC의 개념과 그로 인한 “Stop-the-world” 현상이 성능에 어떤 영향을 미치는지를 알기 쉽게 설명한다.
  • 가상 스레드 (Project Loom): 자바 21 이상에서 도입되는 가상 스레드 개념을 소개하며, 고병렬 애플리케이션 개발에서 새로운 가능성을 제시한다.

1~3장은 자바 최적화란 단순히 GC 튜닝이나 JVM 플래그 조정이 아니라, 자바 언어의 동작 메커니즘을 깊이 이해하고, 시스템 레벨의 지식까지 고려한 분석을 요구하는 고차원적 문제임을 보여준다. 이 책은 초보 개발자에게는 어렵게 느껴질 수도 있지만, 중급 이상의 개발자에게는 자바 성능 병목을 해석하는 프레임워크를 제공하는 탐색 도구로서 유용하다.

GraalVM 네이티브 이미지와 AOT 컴파일 실습 – 몸집을 줄이기 위한 깊은 여정

이 책의 4~6장은 자바 애플리케이션의 ‘무거운 몸집’을 어떻게 가볍게 만들 수 있는지에 대한 실용적이고도 인사이트 깊은 실험의 연속이다. 흔히 JVM 기반의 서비스는 빠른 개발과 풍부한 생태계를 자랑하지만, 런타임에 필요한 리소스와 메모리 사용량이 과한 경우가 많다. 저자는 이 문제의 근본적인 해결책을 GraalVM 기반의 네이티브 이미지 빌드와 AOT(Advance-Of-Time) 컴파일로 접근하며, 실습 중심의 설명으로 독자를 끌고 간다.

JIT vs AOT: 실행 vs 사전 컴파일의 본질적 차이

우선 AOT 컴파일이란 무엇인가에 대한 개념적 설명이 눈에 띈다. JVM이 기본적으로 채택한 JIT(Just-In-Time) 컴파일은 실행 시점의 최적화를 도모하지만, 이는 런타임 메모리 사용량 증가와 cold start 지연이라는 단점이 있다. 이와 달리 AOT 컴파일은 빌드 시점에 모든 코드를 기계어로 변환해 실행 파일을 생성함으로써 실행 속도를 빠르게 하고 메모리 사용을 줄일 수 있다는 장점이 있다. 특히 네이티브 이미지를 활용한 AOT 빌드는 컨테이너 환경이나 클라우드 네이티브 환경에서 JVM의 한계를 극복하려는 흐름과 맞물려 실용적인 선택으로 부상하고 있다는 설명이 설득력 있게 다가온다.

GraalVM의 네이티브 이미지 실습: 설정부터 트러블슈팅까지

실제 GraalVM을 이용한 네이티브 이미지 빌드 과정은 단순히 명령어 몇 줄로 끝나지 않는다. 저자는 JVM 기반 바이트코드를 네이티브 코드로 전환하는 과정에서 발생할 수 있는 reflection 처리, resource 등록, class 초기화 시점 제어 등 여러 난제를 상세하게 짚고 넘어간다. 특히, --initialize-at-build-time, --no-fallback, resource-config.json 같은 실무에서 반드시 마주치는 설정 요소들을 생략 없이 다룬 점이 이 책의 강점이다.

예를 들어 AOT 컴파일로 인한 클래스 초기화 시점 차이나, 네이티브 이미지에서 동적으로 로드되는 리소스 누락으로 인한 실행 실패 문제 등은 실무자들이 직접 부딪히는 이슈들인데, 저자는 이를 피상적으로 설명하지 않고 코드 예제와 함께 해결책을 안내한다. 단순한 따라 하기 실습을 넘어, 문제 해결 중심의 사고를 유도한다는 인상을 받았다.

작고 빠른 실행파일을 향한 여정

6장에서 다뤄지는 JVM과 네이티브 실행파일의 메모리 사용량 비교, 시작 시간 측정, 성능 개선 정도에 대한 수치는 AOT 빌드의 효과를 직관적으로 보여준다. 특히, 실제 AOT 컴파일을 통해 생성한 실행파일의 크기나 실행 시간의 변화는 ‘몸집 줄이기’가 왜 필요한지를 강하게 체감하게 만든다. 단순히 이론으로 AOT의 장점을 이야기하지 않고, 숫자와 사례로 명확하게 증명해 보인 점은 독자 입장에서 신뢰를 더한다.

다음 단계를 향한 계단 역할

이 장을 읽으며 느낀 가장 큰 강점은 실습의 구체성과 문제 해결의 실용성이다. GraalVM이나 AOT 컴파일은 표면적으로는 JVM을 대체하는 기술로 보일 수 있지만, 이 책은 그 기술들을 기존 자바 생태계에 유연하게 녹여내는 방법을 보여준다. 실습을 마치고 나면 단순히 ‘알게 되었다’ 수준이 아니라, 내 프로젝트에 도입할 수 있을지를 고민하게 되는 수준으로 사고의 깊이가 확장된다.

지표, 로그, 추적 그리고 컨테이너

최근 들어 자바 애플리케이션을 컨테이너 기반 환경에 배포하는 일이 점점 더 일반화되고 있다. 하지만 자바는 실행 환경에 민감한 언어인 만큼, 단순히 Docker에 올린다고 해서 끝나지 않는다. 성능 문제, 장애 진단, 리소스 설정까지 — 그 복잡도는 오히려 높아질 수 있다.

이 파트에서는 자바 관측 가능성(Observability)을 주제로, 지표(Metrics), 로그(Log), 추적(Tracing) 이라는 세 가지 핵심 요소와 이를 컨테이너 환경에 적용하는 방법, 그리고 실제 운영에서 문제를 진단하고 대응하는 흐름을 정리했다.

컨테이너 환경에서 자바를 ‘잘’ 실행하는 법

컨테이너에서 자바를 실행할 때 JVM 옵션이 리소스 제약을 제대로 인식하지 못하는 경우가 자주 발생한다. 예를 들어 -Xmx를 설정하지 않으면 cgroup 리밋을 넘겨 OOM(Out of Memory)이 발생할 수 있다. 또한 컨테이너 이미지의 계층 분리, 슬림화, 보안 업데이트 등도 중요하다.

자바를 쿠버네티스에 올릴 경우 라이브니스/레디니스 프로브 구성 시 애플리케이션 기동 시간이나 GC 지연도 고려해야 한다. 이 모든 요소는 자바의 실행 환경 최적화라는 첫 단추다.


지표, 로그, 추적: 관측 가능성의 3요소

관측 가능성은 단일 기술이 아니라 세 가지 데이터 흐름의 조합이다

  • 지표(Metrics): 정량적 수치. request/sec, 메모리 사용량, GC 횟수 등.
  • 로그(Log): 텍스트 기반 이벤트 기록. 로그 레벨, 사용자 ID, 에러 메시지 등.
  • 추적(Tracing): 분산 시스템에서 호출 경로를 추적. trace_id / span_id 기반.

이 3요소는 서로 보완 관계에 있다. 문제 상황에서는 지표로 징후를 포착하고, 로그로 원인을 좁히며, 추적을 통해 어디서 병목이 발생했는지 확인하게 된다.

문제는 언제나 예고 없이 온다: 진단과 통찰의 확보

우리는 종종 “그때 이상한 징후가 있긴 했지”라고 말한다. 하지만 그 이상함을 사건 전에 알아차릴 수 있는가가 진정한 관측 가능성이다. 책에서는 Roberta Wohlstetter의 인용을 통해 “사건 이후의 징후는 선명하지만, 그 전에는 모호하다”고 지적한다.

그래서 중요한 것이 데이터 간의 연결이다. 단순히 수집만 해서는 소용없다. 지표에서 경고가 감지되면, 관련 로그와 추적 데이터를 함께 살펴보며 이상 징후 → 원인 분석 → 조치 라는 흐름을 만들어야 한다.

오픈소스를 활용한 실전 구현

관측 도구는 모두 직접 만들 필요가 없다. 이미 많은 오픈소스와 상용 APM 도구가 존재한다

  • Prometheus: 지표 수집 및 경고 시스템
  • Jaeger / Zipkin: 분산 추적 시각화
  • Grafana: 시각화 대시보드
  • OpenTelemetry: 통합 수집 스펙 (metrics, logs, traces)

이외에도 Datadog, Dynatrace, Honeycomb 같은 상용 도구와의 연계도 가능하며, 실제 운영에서는 이들을 조합해서 사용하는 것이 일반적이다.

분산 시스템의 시대, 자바가 나아갈 길: 가상 스레드와 패턴, 그리고 클라우드 설계까지

자바는 오랫동안 서버 사이드 개발의 핵심 언어로 자리해 왔지만, 클라우드 네이티브 시대를 맞아 또 한 번의 전환점을 맞이하고 있다. 이번 장들에서는 자바가 이러한 전환에 어떻게 대응하고 있는지를 동시성 최적화, 가상 스레드(Virtual Thread), 분산 시스템 패턴, 클라우드 기반 설계 요소 등 네 가지 핵심 관점에서 탐구한다.

가상 스레드로 풀어낸 병목 해소의 실마리

기존 플랫폼 스레드의 가장 큰 제약은 운영체제와 1:1로 매핑된다는 점이다. 이는 스택 세그먼트 등의 고정된 메모리 사용으로 인해 요청 수가 많은 시스템에서는 심각한 병목, 이른바 스레드 병목(thread bottleneck) 을 야기한다.

이에 대한 자바의 해법은 가상 스레드다. 이는 캐리어 스레드를 기반으로 하며, 컨텍스트 전환을 훨씬 경량화하여 고성능 I/O 중심 애플리케이션에 적합한 구조를 제공한다. 특히 다음과 같은 특징을 지닌다.

  • 생성 비용이 적고 수만 개의 동시 실행이 가능
  • 캐리어 스레드에 연결되며 운영체제 레벨 스레드 자원을 절약
  • 스택 세그먼트 문제와 동기화 병목에서 자유로움
  • Thread.ofVirtual() API를 통한 빌더 방식의 생성 지원

이러한 가상 스레드는 향후 자바의 동시성 모델에서 주류가 될 것으로 보이며, 도구(tooling) 측면에서도 JFR, 디버깅 등 새로운 관점의 개선이 요구된다.

자바의 동시성 코드는 어디로 향하는가?

현대 자바는 더 이상 수동적인 Thread 생성이나 공유 객체 락(lock)을 권장하지 않는다. 오히려 고수준 추상화와 도메인 기반의 스레드 설계가 강조되며, 다음과 같은 전환이 제시된다.

  • ExecutorService, CompletableFuture, StructuredTaskScope 같은 고수준 API 활용
  • 비동기 추상화(async abstraction)를 통해 스레드를 직접 다루지 않는 모델 지향
  • 데이터 병렬성보다 작업 병렬성과 추상적 비동기 표현으로의 이행

이 흐름은 단지 개발 편의성을 넘어서 성능, 안정성, 예측 가능한 동작까지 담보하는 방향이다.

분산 시스템 설계는 더 이상 선택이 아니다

자바가 단일 JVM 환경을 넘어서 클러스터 기반 분산 애플리케이션을 지원하게 되면서, 분산 시스템 설계 원칙에 대한 이해가 필수가 되었다. 이 책에서는 다음과 같은 대표적 오해(fallacies of distributed computing)를 언급하며 현실적인 설계 인식을 강조한다.

  • 네트워크는 항상 안정적이지 않다
  • 지연 시간은 0이 아니다
  • 대역폭은 유한하고, 전송 비용은 무시할 수 없다
  • 관리자, 토폴로지, 주소는 항상 변한다

이러한 전제 위에서 자바의 동시성 구조는 클러스터 환경에서도 유연하게 작동하도록 설계되어야 한다.

클라우드 인프라와 자바: 패턴의 재구성

마지막으로, 클라우드 시대에 요구되는 높은 가용성과 유연성을 지원하기 위해 자바는 아래 요소들과 긴밀히 결합되고 있다.

  • 서비스 지향 아키텍처(SOA), 마이크로서비스 패턴
  • 리액티브 프로그래밍과 이벤트 기반 설계
  • 합의 알고리즘 기반의 데이터 구조 (ex. Paxos, Raft)
  • 자바 기반의 클라우드 구성 요소 (ex. Spring Cloud, Micronaut)

결국 이 장들에서는 자바가 어떻게 현대 분산 시스템과 클라우드 환경에 자연스럽게 적응해가고 있는지를 통합적으로 설명하며, 이행기의 복잡성과 트레이드오프까지도 날카롭게 짚어낸다.

요약하자면, 이번 장은 자바의 동시성 구조가 가시성 높은 코드, 클라우드-분산 지향 설계, 가상 스레드 기반의 구조적 전환을 통해 어떻게 진화하고 있는지를 보여준다. 특히 동시성의 미래를 대비하는 실무 개발자들에게 필독할 만한 흐름과 설계 패턴을 가시화한 점에서 인사이트가 깊다.

라이덴, 파나마, 발할라 – 자바의 미래

5장은 자바의 미래를 설계하고 구현해가는 세 가지 주요 프로젝트 라이덴, 파나마, 발할라를 중심으로, JVM 기반 언어의 진화와 도전 과제를 명확히 조명한다. 이 장은 단순히 기술적인 설명을 나열하는 것이 아니라, 각 프로젝트가 해결하고자 하는 문제와 그것이 자바 생태계에 미치는 영향, 그리고 실무 개발자가 어떤 관점으로 이를 받아들여야 하는지를 통찰력 있게 제시한다.

성능 최적화의 출발점 – “기본 지식만으로는 충분하지 않다”

첫 페이지부터 강하게 던지는 메시지는 이렇다: “journeyman’s knowledge(기본 수준의 지식)만으로는 이제 부족하다.” 오케스트레이션, 관측성, 클라우드 네이티브 기술이 일상화된 시대에, 개발자는 성능 최적화에 대해 더 깊고 다층적인 사고를 해야 한다. 소프트웨어 엔지니어링의 고전적 원칙인 “성능은 근본 설계에 있다”는 정신은 여전히 유효하며, 여기에 지속적인 적용과 다변량 최적화(multivariate optimization) 의 요구가 더해진다.

라이덴 프로젝트 – 네이티브 실행과 정적 최적화의 미래

라이덴은 JVM의 정적 이미지 생성(Static Image Generation) 을 목표로 한다. 네이티브 실행 성능을 추구하면서도 자바의 기존 생태계와의 호환성을 유지하고자 하는 이 프로젝트는 아직 실험적이며, 2024년 기준으로 메인라인에 반영되지 않은 상태다. invokedynamic과 같은 동적 메커니즘의 정적 계산을 시도하며, 자바가 컴파일 타임 최적화를 얼마나 수용할 수 있을지에 대한 실험적 경로를 보여준다.

파나마 프로젝트 – 외부 세계와의 연결 고도화

파나마는 자바가 외부 네이티브 코드(C, C++, GPU 등)와 안전하고 효율적으로 통신할 수 있도록 하는 프로젝트다. 기존 JNI(Java Native Interface)의 복잡성과 성능 저하를 극복하기 위한 현대적 대안으로, 외부 함수 호출(Foreign Function), 외부 메모리 접근(Foreign Memory) API를 중심으로 재설계되고 있다.

구조체 배열을 GPU로 오프로드하거나 SIMD 연산 최적화를 목표로 하는 이 프로젝트는 자바를 고성능 과학/산업 컴퓨팅에 진입시키는 열쇠 중 하나로 보인다.

발할라 프로젝트 – 값 타입(value type)과 메모리 최적화

가장 기술적으로 도전적인 주제는 단연 발할라다. 값 타입(value class) 을 통해 객체의 참조 오버헤드를 줄이고, 메모리 배치를 최적화하며, JVM의 GC 비용까지 줄이려는 시도다.

예를 들어 Point3D[] 배열을 객체 배열이 아닌 연속된 메모리로 배치하는 그림은 매우 설득력 있게 다가온다. 객체가 아닌 값으로서 비교, 할당, 캐시 최적화가 가능해지고, 이는 성능 측면에서 획기적인 개선을 기대하게 만든다.

그러나, 클래스 간 동일 필드 값을 가진 값 타입의 비교에서는 여전히 ==이 아닌 동일성(identity) 에 주의해야 한다는 점에서 새로운 프로그래밍 모델을 요구하게 된다.

이 책은 자바를 단순히 “프로그래밍 언어”로 다루지 않는다. 그것은 하나의 실행 환경이자, 시스템 아키텍처이며, 성능이라는 문제를 풀기 위한 도구이자 사유의 대상이다.

가장 인상 깊었던 부분은 자바의 동시성과 네이티브 이미지, 그리고 관측 가능성을 ‘각각의 기술’이 아니라 ‘서로 연결된 맥락’으로 바라본다는 점이었다. 이를 통해 현대 자바 개발자가 진짜로 신경 써야 할 것은 무엇인지, 명확하게 드러났다.

자바 개발자로서 정체기를 느끼거나, 기술적 맥락의 변화에 뒤처지고 있다고 느낀다면 이 책은 그 고민을 풀어줄 하나의 나침반이 될 수 있다. 이제는 자바도, 개발자도 더 가벼워지고, 더 통찰력 있어야 할 때다.

한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬 받아 작성된 서평입니다.