VisualVM 및 JConsole을 이용한 Java 애플리케이션 성능 프로파일링

지난 포스팅에서는 서버의 내부 상태를 외부로 노출해 주는 기술인 JMX(Java Management Extensions)의 원리와 원격 접속 설정 방법에 대해 알아보았습니다. 문을 열어두었으니 이제 그 안을 훤히 들여다볼 수 있는 ‘망원경’이 필요합니다. 개발 환경이나 QA(테스트) 환경에서 Java 애플리케이션의 성능 병목을 추적할 때, 비싼 상용 APM 도구 없이도 훌륭하게 분석을 수행할 수 있는 무료 내장 도구들이 있습니다. 바로 … 더 읽기

JMX(Java Management Extensions)를 활용한 JVM 실시간 모니터링 구축

지금까지의 시리즈를 통해 JVM의 메모리 구조부터 가비지 컬렉션(GC), 그리고 멀티 스레드 제어와 비동기 프로그래밍까지 ‘어떻게 성능을 최적화할 것인가’에 대한 내부 이론을 탄탄하게 다졌습니다. 하지만 실제 서비스가 라이브로 배포(Production)되고 나면, 이론만으로는 해결할 수 없는 수많은 변수와 병목 현상이 발생합니다. 서버가 다운되기 전에 미리 메모리 누수를 감지하고, 현재 몇 개의 스레드가 활성화되어 있는지 실시간으로 관찰하려면 애플리케이션의 상태를 … 더 읽기

비동기 프로그래밍(CompletableFuture)을 활용한 서버 응답 속도 극대화

지난 포스팅에서는 여러 스레드가 공유 자원에 접근할 때 발생하는 병목을 해결하는 ConcurrentHashMap에 대해 알아보았습니다. 동시성 제어를 통해 스레드 간의 충돌을 막았다면, 이제는 ‘스레드가 아무 일도 하지 않고 버려지는 시간’을 최소화하여 서버의 전체 응답 속도를 극대화할 차례입니다. 엔터프라이즈 Java 애플리케이션 성능 저하의 가장 큰 주범은 외부 시스템(데이터베이스, 외부 API 등)과의 통신 시 발생하는 대기 시간입니다. 이번 … 더 읽기

ConcurrentHashMap과 동기화(Synchronization) 메커니즘의 성능 차이

지난 포스팅에서는 시스템 자원 낭비를 막고 서버의 안정성을 지키는 스레드 풀(Thread Pool)의 적정 사이즈 산정법에 대해 알아보았습니다. 여러 개의 스레드를 효율적으로 띄우는 것까지 성공했다면, 그다음 직면하게 되는 문제는 ‘여러 스레드가 동시에 동일한 데이터(공유 자원)에 접근할 때 어떻게 안전하게 처리할 것인가’입니다. Java에서 Key-Value 형태로 데이터를 저장할 때 가장 많이 사용하는 자료구조는 HashMap입니다. 하지만 HashMap은 스레드 안전성(Thread-safe)을 … 더 읽기

시스템 자원 낭비를 막는 스레드 풀(Thread Pool) 적정 사이즈 산정법

지난 포스팅에서는 무분별한 락(Lock) 사용이 불러오는 참사인 교착 상태(Deadlock)에 대해 알아보았습니다. 동시성 제어의 또 다른 핵심은 ‘스레드를 얼마나 많이 만들 것인가’입니다. 사용자의 요청이 들어올 때마다 새로운 스레드를 무한정 생성한다면 어떻게 될까요? 스레드 생성과 소멸에 드는 CPU 오버헤드는 엄청나며, 결국 메모리 고갈(OutOfMemory)이나 잦은 컨텍스트 스위칭(Context Switching)으로 인해 서버는 완전히 뻗어버리고 맙니다. 이를 방지하기 위해 미리 일정 … 더 읽기

교착 상태(Deadlock) 발생 원인과 스레드 덤프를 통한 해결 전략

지난 포스팅에서는 서버의 상태를 엑스레이처럼 들여다볼 수 있는 ‘스레드 덤프(Thread Dump)’의 생성 방법과 주요 상태(State)에 대해 알아보았습니다. 이번 글에서는 멀티 스레드 프로그래밍 환경에서 개발자들이 겪을 수 있는 최악의 악몽이자, 스레드 덤프 분석을 통해 반드시 찾아내야 하는 치명적인 오류인 교착 상태(Deadlock, 데드락)에 대해 완벽하게 해부해 보겠습니다. 서버가 다운된 것도 아니고 CPU 사용량이 치솟지도 않았는데 애플리케이션이 먹통이 … 더 읽기

Java 스레드(Thread) 덤프 생성 방법과 상태(State) 분석 가이드

서버를 운영하다 보면 OutOfMemoryError처럼 명확한 에러 로그를 남기며 서버가 죽는 경우도 있지만, 에러도 없이 CPU 사용률이 100%로 치솟거나 사용자의 요청에 서버가 먹통(Timeout)이 되는 현상이 더 자주 발생합니다. 이럴 때 개발자가 서버의 내부 상황을 들여다보기 위해 촬영하는 ‘X-ray 사진’이 바로 스레드 덤프(Thread Dump)입니다. 엔터프라이즈 환경에서 성능 튜닝과 장애 분석을 마스터하려면 스레드 덤프를 추출하고 해석하는 능력이 필수적입니다. … 더 읽기

JIT(Just-In-Time) 컴파일러의 동작 방식과 코드 최적화 기법

이전 포스팅에서는 Java의 클래스로더가 어떻게 코드를 메모리에 동적으로 적재하는지 알아보았습니다. 메모리에 올라간 바이트코드(Bytecode)는 이제 실행 엔진을 통해 실제 컴퓨터가 이해할 수 있는 기계어로 번역되어 실행됩니다. 과거 Java는 C나 C++ 같은 컴파일 언어에 비해 속도가 느리다는 인식이 강했습니다. 하지만 오늘날 엔터프라이즈 환경에서 Java가 막강한 성능을 자랑할 수 있는 이유는 바로 실행 엔진의 핵심인 JIT(Just-In-Time) 컴파일러 덕분입니다. … 더 읽기

GC 로그 분석을 통한 Java 애플리케이션 병목 현상 진단 방법

지금까지 우리는 JVM 힙 메모리의 구조부터 ZGC와 같은 최신 가비지 컬렉터까지, 메모리 관리의 핵심 이론들을 깊이 있게 살펴보았습니다. 하지만 아무리 뛰어난 GC 알고리즘을 선택하더라도 완벽한 시스템은 없습니다. 실무에서 서버에 원인을 알 수 없는 지연(Latency) 현상이나 메모리 부족(OOM) 에러가 발생했을 때, 가장 먼저 열어봐야 할 ‘항공기의 블랙박스’가 바로 GC 로그(Log)입니다. 이번 포스팅에서는 Java 애플리케이션 운영 시 … 더 읽기

ZGC 및 Shenandoah GC: 차세대 초저지연 가비지 컬렉터 집중 분석

지난 포스팅에서 다룬 G1 GC는 메모리를 리전(Region)으로 나누어 효율성을 극대화한 훌륭한 가비지 컬렉터입니다. 하지만 최근 엔터프라이즈 환경에서 다루는 데이터의 규모가 폭발적으로 증가하면서, 수십 기가바이트(GB)를 넘어 테라바이트(TB) 단위의 거대한 힙(Heap) 메모리를 사용하는 시스템들이 등장하기 시작했습니다. 힙 사이즈가 테라바이트급으로 커지면 G1 GC조차도 압축(Compaction) 작업 시 발생하는 Stop-The-World(STW) 시간을 수백 밀리초 이내로 방어하기 어려워집니다. 금융 시스템의 초단타 매매나 … 더 읽기