JVM — 아키텍처·GC·String Pool
JVM — 아키텍처·GC·String Pool
🎯 이 lesson 을 읽고 나면
이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.
- ▸✅ JVM 메모리 구조 (Heap / Stack / Metaspace) + GC 알고리즘
- ▸✅ 운영 JVM 옵션 (-Xms/-Xmx/-XX:+UseG1GC) 5종 추천
- ▸✅ OOM 발생 시 HeapDump 분석 워크플로 설명
학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.
JVM 이 뭐냐 — *한 번 짜면 어디서나 실행*
핵심 한 줄
JVM (Java Virtual Machine) = Java 코드를 실행하는 가상 머신. "Write Once, Run Anywhere" — 한 번 작성한 Java 코드가 Windows·macOS·Linux 어디서나 똑같이 실행됩니다.
어떻게 가능한가
Java 코드는 바로 실행되지 않습니다. 두 단계를 거칩니다:
1. 컴파일: .java 파일 → .class 파일 (바이트코드). OS 와 무관한 중간 언어.
2. 실행: JVM 이 .class 를 읽어서 각 OS 의 네이티브 명령 으로 변환·실행.
이 바이트코드 + JVM 조합이 Java 의 핵심입니다. 작성 시점 에 윈도우·맥을 신경 쓸 필요 없고, 실행 시점 에 그 OS 용 JVM 이 알아서 처리합니다.
JVM 메모리 — 어디에 뭐가 저장되나
JVM 은 메모리를 여러 영역으로 나눠 관리합니다. 중요한 4 가지:
- ▸Heap — 모든 객체와 배열. 가장 큰 영역. GC 의 무대.
- ▸Stack — 메서드 호출 프레임 과 지역 변수. 스레드마다 별도.
- ▸Method Area — 클래스 메타데이터 (필드·메서드 정보). 전체 스레드 공유.
- ▸PC Register — 현재 실행 중인 명령 위치. 스레드별.
x 같은 원시 값은 스택 에. new 로 만든 객체는 힙 에. 그 객체를 가리키는 참조 만 스택에. 이 구분이 Java 메모리의 기본 그림 입니다.
JIT 컴파일러 — 자주 쓰는 코드는 더 빨리
처음엔 JVM 이 바이트코드를 해석 (interpret) 합니다. 한 줄씩 읽어서 실행. 느립니다.
하지만 어떤 메서드가 자주 호출되는지 추적하다가, 임계값을 넘으면 그 메서드를 네이티브 코드로 컴파일 해 둡니다. 이걸 JIT (Just-In-Time) 라고 합니다.
JIT 가 프로파일링 기반으로 최적화하기 때문에, 오래 실행된 JVM 이 더 빠릅니다 (warm-up). 이게 Java 서버가 처음엔 느리다가 점점 빨라지는 이유입니다.
C++ 같이 미리 컴파일 된 코드도 빠르지만, JIT 는 실제 실행 패턴 을 보고 최적화해서 어떤 경우엔 C++ 보다 빠르기도 합니다.
한 번 정리
JVM 은 단순한 번역기 가 아닙니다. 메모리 관리·최적화·GC 까지 다 알아서 합니다. Java 개발자가 메모리 할당·해제를 직접 하지 않아도 되는 이유입니다.
Garbage Collection — *자동 메모리 청소*
GC 가 하는 일
C·C++ 에서는 free() 로 직접 메모리를 해제 해야 합니다. 깜빡하면 메모리 누수, 두 번 해제하면 프로그램 충돌.
Java 는 Garbage Collector 가 자동으로 처리합니다. 더 이상 참조되지 않는 객체 를 찾아내서 메모리에서 회수합니다. 개발자는 생성만 하고 나머지는 잊으면 됩니다.
세대 가설 — 대부분 객체는 금방 죽는다
JVM 의 GC 가 빠른 이유는 세대 가설 입니다.
> 대부분의 객체는 짧게 살고 죽는다. 메서드 안에서 만든 new·String.split() 결과 등 — 메서드 끝나면 더 이상 안 쓰이죠.
이 가설을 활용해 Heap 을 두 영역으로 나눕니다:
- ▸Young Generation — 새로 태어난 객체. 대부분 여기서 죽음.
- ▸Old Generation — Young 에서 살아남은 객체. 오래 살아남을 가능성 큼.
새 객체는 Eden 에 들어갑니다. Minor GC 때 살아남으면 Survivor (S0) 로, 또 살아남으면 S1 로 — 여러 번 살아남으면 Old Generation 으로 승격됩니다.
Minor GC vs Full GC
- ▸Minor GC: Young 영역만 청소. 자주·빠름 (수십 ms).
- ▸Full GC: Old 까지 청소. 드물지만 느림 (수백 ms ~ 수초). 모든 스레드 정지 (Stop-The-World).
운영 환경에서 Full GC 가 자주 발생하면 큰 문제입니다. 응답 시간이 갑자기 튀고, 사용자는 느려졌다 고 느끼죠. Old Gen 이 자주 차거나, 메모리 누수가 있다는 신호일 수 있습니다.
GC 알고리즘 — 선택 가능
JVM 은 여러 GC 알고리즘을 제공합니다. 어떤 걸 쓸지 튜닝 으로 결정합니다.
- ▸Serial GC — 단일 스레드. 작은 앱·임베디드.
- ▸Parallel GC — 다중 스레드. 처리량 우선 (배치).
- ▸G1 GC — Java 9+ 기본. 예측 가능한 일시정지. 대부분 좋음.
- ▸ZGC — Java 11+. 일시정지 1ms 미만. 대용량·저지연.
- ▸Shenandoah — ZGC 와 유사. RedHat 주도.
-XX:+UseG1GC 같은 JVM 옵션으로 선택합니다. 작은 앱은 G1, 큰 앱·저지연은 ZGC 가 기본 가이드라인입니다.
한 번 정리
GC 는 자동 메모리 관리 라는 큰 편의를 줍니다. 하지만 공짜는 아닙니다 — Full GC 가 자주 발생하면 응답 지연. 메모리 사용 패턴을 잘 설계하고, JVM 옵션을 튜닝하는 게 실무의 핵심 역량입니다.
String — *불변 객체의 대표*
String 은 변하지 않는다
Java 의 String 은 불변 (Immutable) 객체입니다. 한 번 만들어지면 그 내용을 바꿀 수 없습니다.
concat·replace·toUpperCase 등 모든 메서드는 새 String 을 만들어 반환 합니다. 원본은 손대지 않죠.
왜 불변인가
불변에는 큰 이득이 있습니다:
- ▸스레드 안전: 여러 스레드가 동시 접근해도 수정될 일이 없음
- ▸HashMap 키 안전: 키로 쓸 수 있음 (해시값이 변하지 않음)
- ▸보안: 파일 경로·URL 을 함부로 못 바꿈
- ▸String Pool 최적화: 같은 값은 공유 가능
String Pool — 같은 글자는 공유
JVM 은 String 을 위한 특별한 공간 — String Pool — 을 둡니다. 같은 글자의 String 은 하나만 저장 하고 공유 합니다.
여기서 Java 에서 가장 헷갈리는 것이 나옵니다.
== vs equals():
- ▸
==는 참조 비교 (같은 객체인가?) - ▸
equals()는 값 비교 (같은 내용인가?)
String 비교는 항상 equals() 입니다. == 로 비교하면 우연히 맞을 수도, 틀릴 수도 있습니다.
StringBuilder — 문자열 연결의 함정
result += i 는 새 String 을 만들어 result 에 다시 할당합니다. 1000번 반복하면 1000개의 임시 객체가 생기죠. GC 가 미친듯이 돕니다.
StringBuilder 를 쓰면 내부 버퍼 에 누적하다가 마지막에 한 번만 String 으로 변환합니다.
> 💡 단순한 a + b + c 는 컴파일러가 자동으로 StringBuilder 로 변환합니다. 반복문 안에서만 직접 써야 합니다.
Text Block (Java 15+) — 여러 줄 문자열
들여쓰기·줄바꿈을 그대로 유지하면서 깔끔하게 쓸 수 있습니다. JSON·SQL·HTML 작성에 극적으로 편함.
한 번 정리
String 은 불변 입니다. 비교는 equals(), 반복 연결은 StringBuilder, 여러 줄은 Text Block. 이 셋만 알아도 일상 코드의 90% 는 안전합니다.
Java 17~21 최신 기능 — *코드가 짧아진다*
record (Java 14·16 정식) — 불변 데이터 클래스
데이터만 담는 단순 클래스를 매번 생성자·getter·equals·hashCode·toString 다 쓰려면 코드가 50줄 입니다. record 는 한 줄 로 끝납니다.
이 한 줄이 다음을 자동 생성합니다:
- ▸생성자
new User(1L, "홍", "[email protected]") - ▸getter
u.id()·u.name()(getId()아님 — 약간 다름) - ▸
equals()·hashCode()·toString()
DTO·이벤트·VO 같은 단순 데이터 객체 에 환상적입니다. Lombok 의 @Value 와 같은 역할을 언어 차원에서 지원하는 거죠.
sealed class (Java 17) — 허용된 자식만
Shape 를 구현할 수 있는 클래스를 Circle·Square·Triangle 로 제한 합니다. 다른 곳에서 Shape 를 구현하려 하면 컴파일 에러.
왜 유용한가? switch 패턴 매칭 과 결합하면 모든 경우를 컴파일러가 검증 해줍니다. 새 도형이 추가되면 모든 switch 를 업데이트 하지 않으면 컴파일 안 됨 — 빠뜨리는 실수가 사라집니다.
switch 표현식 (Java 14) — 값을 리턴
옛 switch 는 문장 이라 값을 리턴하지 못했습니다. 모던 switch 는 표현식 이라 가능:
-> 화살표 + break 불필요 + 값 리턴 + 여러 case 묶기. 옛 switch 의 fall-through 함정 도 사라졌습니다.
Pattern Matching (Java 21) — if-instanceof 안녕
instanceof 와 변수 선언 을 한 줄에. switch 와 결합하면 더 강력합니다:
sealed class 와 결합하면 모든 경우 강제 까지 더해집니다.
Optional (Java 8) — null 의 명시적 표현
User getUser() 를 호출했는데 null 일 수도 있다 는 걸 누가 알까요? 모릅니다. 그래서 NullPointerException 폭발.
리턴 타입에 Optional 이 있으면 반드시 비어있을 수 있음을 인지 하게 됩니다.
> 💡 Optional 은 리턴 타입에만 쓰세요. 필드·매개변수 에 쓰는 건 안티 패턴입니다.
Virtual Thread (Java 21) — 동시성 혁명
위 Collections + Functional lesson 에서 다룬 그 기능. 1만+ 동시 작업을 OS 스레드 부담 없이 처리합니다. I/O 대기 시 자동 yield. Java 가 Go·Kotlin 수준의 동시성을 갖게 된 사건입니다.
한 번 정리
Java 17~21 의 신기능들은 코드를 짧게 + 컴파일러가 더 많이 검증 하게 만듭니다. record·sealed·switch 패턴·Optional·Virtual Thread — 새 프로젝트라면 적극 사용 권장. 옛 프로젝트는 점진 도입.
🎮 JVM 메모리·GC 시각화
public class Hello {
public static void main(String[] args) {
System.out.println("안녕, Java!");
}
}
GC 튜닝 옵션 — 실무에서 자주 쓰는 5가지
GC 옵션이 왜 필요한가
기본 설정의 JVM 은 작은 메모리 + 일반 워크로드 기준입니다. 실제 서비스는:
- ▸메모리 1GB → 8GB 로 키워야 하고
- ▸응답 지연이 중요한 경우 Stop-the-world 시간을 줄여야 합니다
이걸 JVM 옵션 (-X, -XX) 으로 조정합니다.
가장 자주 쓰는 5개
1. -Xms / -Xmx — 힙 메모리 크기
- ▸
-Xms= 시작 힙 크기 (initial) - ▸
-Xmx= 최대 힙 크기 (maximum)
실무 팁: -Xms == -Xmx 로 같게 설정하세요. 동적 확장 비용을 피해 예측 가능한 성능. AWS·k8s 환경에서 사실상 표준.
2. -XX:+UseG1GC — G1 가비지 컬렉터
Java 9+ 기본값 (Java 17 도 G1). 4GB 이상 힙 에서 응답 지연이 짧음. 명시적으로 적어두면 명확함 차원에서 좋습니다.
Java 11+ ZGC, Java 15+ Shenandoah 가 더 짧은 pause time 을 제공하지만 Heap 16GB+ 가 아니면 G1 으로 충분.
3. -XX:MaxGCPauseMillis=200 — 최대 GC 멈춤 시간 목표
JVM 에 "GC 한 번에 200ms 넘기지 마" 라는 힌트. 보장이 아닌 목표. 응답 지연 SLA 가 있는 서비스에 필수.
4. -XX:+HeapDumpOnOutOfMemoryError — OOM 시 덤프 자동 생성
OOM 터지면 자동으로 힙 덤프 를 떨굽니다. 사후 분석용 — 프로덕션 필수 옵션.
5. -XX:+PrintGCDetails (Java 8) / -Xlog:gc* (Java 9+) — GC 로그
GC 가 언제·얼마나·왜 일어났는지 기록. 부하 테스트 시 필수.
실무 표준 조합 (Spring Boot 운영)
이 옵션을 모두 외울 필요는 없습니다. 다만 옵션이 존재한다는 사실 과 왜 쓰는지 만 알면 됩니다 — 면접에서 "GC 튜닝 해보셨어요?" 라고 물으면 이 5개를 언급할 수 있어야 합니다.
☕ 직접 실행 — String Pool · == vs equals
🤖 AI 에게 이렇게 요청해보세요
이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.
- ▸"이 Spring Boot 앱의 운영 JVM 옵션 (Xms/Xmx/G1GC/HeapDump) 추천해줘"
- ▸"이 코드의 OutOfMemoryError 원인을 힙 덤프 분석 관점에서 진단해줘"
- ▸"GC 로그 옵션을 Java 17 형식으로 추가해줘"
왜 이게 토큰을 줄이나
개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.