diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234.md" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234.md" new file mode 100644 index 0000000..faa3e98 --- /dev/null +++ "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234.md" @@ -0,0 +1,271 @@ +# 12장 자바 메모리 모델과 스레드 + +12장을 먼저 읽게 된 이유는 현재 가장 관심있는 부분이 메모리 모델과 스레드 관련 작업이기 때문입니다. 해당 내용을 읽고, 새로 알게 된 내용에 대해서 작성해보도록 하겠습니다. + +## 12.1 들어가며 +멀티태스킹은 컴퓨터 연산에서 없어서는 안될 작업이다. 유튜브 영상을 보면서 댓글을 읽어야 하고, 댓글을 읽으면서 반응도 추가해야하고, 다른 관련 영상들에는 뭐가 있을까 구경을 해야하기 때문이다. + +이렇게 컴퓨터가 여러 작업을 동시에 수행할 수 있는 이유는 **연산 능력**이 매우 뛰어나다는 이유 뿐 아니라, 저장 및 통신 성능(디스크 I/O, 네트워크 통신 / DB 접근 등)의 격차가 매우 크기 때문이다. + +이러한 흐름 속에서 서비스에 대한 초당 트랜잭션 수(= TPS)가 중요한 지표이고, 이 지표는 프로그램의 동시성 기능과 매우 밀접하다. 프로그램이 더 많은 스레드를 동시에 운용할 수 있게 되면서 작업 효율이 높아짐과 동시에 같은 데이터를 두고, 스레드끼리 경합을 벌이는 교착 상태에 빠지는 상황으로 동시성이 크게 감소할 수 있다는 양면성이 존재한다. + +이러한 동시성 프로그래밍 기법은 매우 어려운 주제이기 때문에 JVM이 이를 어떻게 처리할 지에 알아가는 것이 중요한 포인트가 될 것 같다. + +## 12.2 하드웨어에서의 효율과 일관성 + +동시성 문제가 어려운 이유는 단순히 프로세서의 컴퓨팅(연산)만으로 이루어질 수 없다는 데 있다. 프로세서는 데이터를 읽고, 작업 결과를 저장해야하므로 적어도 메모리는 반드시 필요하다. 그러나, 메모리는 너무 느리다. 프로세서와 메모리 사이에 "캐시"라는 계층을 둠으로써 프로세서와 메모리의 속도 격차를 원만하게 해결할 수 있다. + + + +그렇다면, 여러 프로세서가 병렬적으로 작업을 할 때, 각 프로세서마다 캐시 데이터가 다를 수 있다는 생각을 할 수 있다. 즉, "캐시 일관성"문제가 발생한다. + +일관성 문제를 해결하기 위해서는 프로세서가 캐시를 이용할 때, 정해진 프로토콜을 따라야 한다. 여기서 등장하는 개념이 바로 "메모리 모델"이다. **메모리 모델**은 특정 프로토콜을 이용하여 특정 메모리나 캐시를 읽고 쓰는 절차를 말한다. JVM(자바 가상 머신)은 자체 메모리 모델을 가진다. + +프로세서는 "비순차 실행 최적화(out-of-order execution optimization)"로도 프로세서의 컴퓨팅 능력을 더 이끌어 낼 수 있다. 실제로 알고리즘 문제를 풀다보면 JVM 내부에서 최적화를 통해 명령어 순서를 입력 코드에 기술된 명령어 순서와 다르게 해석할 수 있다. 물론, 결과는 같도록 재구성한다. 프로세서 뿐 아니라, JVM 내에서도 JIT(Just In Time) 컴파일러가 명령어 재정렬이라는 최적화를 수행한다. + +## 12.3 자바 메모리 모델 + +앞서 본 것처럼 하드웨어마다, 운영 체제마다 메모리 모델이 다르다. 그렇다면 "Write Once, Run Anywhere"를 외치는 자바 입장에서는 곤란하다. 같은 코드가 플랫폼마다 다르게 동작하면 안 되기 때문이다. 그래서 **《자바 가상 머신 명세》는 자체 자바 메모리 모델(JMM)을 따로 정의**해서, 어떤 플랫폼에서 돌리든 자바 프로그램이 메모리를 일관된 방식으로 사용할 수 있도록 보호한다. + +반면 C/C++ 같은 언어는 하드웨어와 OS의 메모리 모델을 직접 사용한다. 그래서 한 플랫폼에서는 멀쩡히 돌던 프로그램이 다른 플랫폼에서는 동작하지 않아 플랫폼별로 맞춤 제작해야 하는 경우가 많다. 자바 메모리 모델이 이 골치 아픈 부분을 대신 막아주는 셈이다. 참고로 이 모델은 오랜 검증과 개정을 거쳐 **JDK 5에 와서야 마침내 완성**되었다. + +### 12.3.1 메인 메모리와 작업 메모리 + +자바 메모리 모델의 핵심 목적은 **변수에 접근하는 규칙을 정하는 것**이다. 그런데 여기서 말하는 "변수"는 우리가 평소에 생각하는 변수와 살짝 다르다. + +- 포함되는 것: **인스턴스 필드, 정적 필드, 배열 객체의 원소** (여러 스레드가 공유할 수 있는 것들) +- 제외되는 것: **지역 변수, 메서드 매개 변수** (스레드별 고유 공간이라 경합 자체가 없음) + +이 모델에서 메모리는 두 종류로 나뉜다. + +- **메인 메모리(Main Memory)**: 모든 변수가 저장되는 공간 +- **작업 메모리(Working Memory)**: 스레드마다 하나씩 갖는, 프로세서의 캐시와 비슷한 역할을 하는 공간 + +여기서 중요한 규칙이 몇 가지 있다. + +1. 스레드가 변수를 읽고 쓰는 모든 연산은 **작업 메모리에서만** 수행된다. 메인 메모리의 데이터를 직접 읽고 쓸 수 없다. +2. 스레드끼리는 서로의 작업 메모리에 **직접 접근할 수 없다**. 반드시 메인 메모리를 거쳐 값을 전송해야 한다. + +즉, 작업 메모리에는 해당 스레드가 사용하는 변수의 **메인 메모리 복사본**이 담겨 있는 것이다. 이 구조 때문에 "한 스레드가 값을 바꿨는데 다른 스레드는 옛날 값을 보는" 가시성 문제가 자연스럽게 생긴다. + +> 비유하자면 메인 메모리는 자바 힙 중 객체 인스턴스 데이터, 작업 메모리는 스택 영역의 일부에 대응한다. 더 하드웨어 관점으로 내려가면 메인 메모리 = 하드웨어 메모리, 작업 메모리 = 레지스터와 캐시에 대응한다. 가상 머신은 성능을 위해 작업 메모리를 되도록 레지스터나 캐시에 미리 올려둔다. + +### 12.3.2 메모리 간 상호 작용 + +메인 메모리와 작업 메모리 사이의 동기화를 자바 메모리 모델은 **8가지 원자적 연산**으로 정의했다. + +| 연산 | 대상 | 설명 | +| --- | --- | --- | +| `lock`(잠금) | 메인 메모리 | 변수를 특정 스레드 전용으로 만든다 | +| `unlock`(잠금 해제) | 메인 메모리 | 잠긴 변수를 해제한다 | +| `read`(읽기) | 메인 메모리 | 변숫값을 작업 메모리로 전송한다 | +| `load`(적재) | 작업 메모리 | 읽어온 값을 작업 메모리 변수에 넣는다 | +| `use`(사용) | 작업 메모리 | 변숫값을 실행 엔진으로 전달한다 | +| `assign`(할당) | 작업 메모리 | 실행 엔진에서 받은 값을 변수에 할당한다 | +| `store`(저장) | 작업 메모리 | 변숫값을 메인 메모리로 전송한다 | +| `write`(쓰기) | 메인 메모리 | 전송된 값을 메인 메모리 변수에 기록한다 | + +여기서 새로 알게 된 포인트는 **`read`와 `load`, `store`와 `write`는 반드시 순서대로 수행되어야 한다**는 것이다. 단, "순서대로"일 뿐 "바로 이어서"일 필요는 없다. 즉 `read a → read b → load b → load a` 처럼 중간에 다른 연산이 끼어들 수 있다. 이 미묘한 틈이 바로 동시성 버그가 숨는 자리다. + +### 12.3.3 volatile 변수용 특별 규칙 + +`volatile`은 자바 가상 머신이 제공하는 **가장 가벼운 동기화 메커니즘**이다. 그런데 제대로 이해하기가 까다로워서 많은 개발자가 그냥 `synchronized`를 쓰는 편이다. `volatile`로 선언하면 변수는 두 가지 특성을 갖는다. + +**① 가시성(visibility) 보장** +모든 스레드에서 이 변수를 "투명하게" 볼 수 있다. 한 스레드가 값을 수정하면 다른 스레드들도 새로운 값을 즉시 알게 된다. 일반 변수는 A가 수정 → 메인 메모리에 기록(write back) → B가 다시 읽기 라는 과정을 거쳐야 하지만, `volatile`은 이 과정이 즉시 강제된다. + +**② 명령어 재정렬 최적화 금지** +앞서 본 비순차 실행/명령어 재정렬을 막아준다. + +다만 여기서 가장 중요한 함정이 있다. **`volatile`은 가시성만 보장할 뿐, 연산의 원자성은 보장하지 않는다.** 그래서 멀티스레드 환경에서 완벽하게 안전하지는 않다. + +```java +public class VolatileTest { + public static volatile int race = 0; + + public static void increase() { + race++; // 한 줄처럼 보이지만 원자적이지 않다 + } + // 20개 스레드가 각각 10000번 increase() 호출 + // 기대값: 200,000 → 실제: 매번 200,000보다 작음 +} +``` + +왜 그럴까? `increase()`를 `javap`로 디컴파일하면 `race++`가 **명령어 4개**로 이루어진 걸 볼 수 있다. + +``` +getstatic #7 // race 값을 피연산자 스택으로 가져옴 (이때 volatile로 최신값 확인) +iconst_1 +iadd // 이 사이에 다른 스레드가 race를 바꾸면… +putstatic #7 // 변경 전 값에 기반한 결과를 메인 메모리에 써버림 +return +``` + +`getstatic`으로 최신값을 가져온 직후, `iadd`를 수행하는 사이에 다른 스레드가 `race`를 바꾸면, 피연산자 스택의 값은 이미 낡은 값이 된다. **즉, "읽기 → 연산 → 쓰기"가 하나로 묶이지 않기 때문에 `volatile`만으로는 부족하다.** 이 경우엔 `synchronized`나 `java.util.concurrent`의 원자적 클래스(`AtomicInteger` 등), 락을 함께 써야 한다. + +> **`volatile`이 정말 적합한 시나리오** +> - 연산 결과가 변수의 현재 값과 무관하거나, 값을 수정하는 스레드가 하나뿐일 때 +> - 다른 상태 변수와 얽힌 불변성 제약이 없을 때 +> 대표 예가 `shutdownRequested = true` 같은 **상태 플래그**다. 한 스레드가 `true`로 바꾸면 나머지 스레드가 즉시 종료되도록 보장할 수 있다. + +**② 명령어 재정렬 금지가 빛나는 예 — DCL 싱글턴** + +```java +public class Singleton { + private volatile static Singleton instance; // volatile이 핵심 + + public static Singleton getInstance() { + if (instance == null) { + synchronized (Singleton.class) { + if (instance == null) { + instance = new Singleton(); + } + } + } + return instance; + } +} +``` + +`instance = new Singleton()`은 실제로는 "메모리 할당 → 생성자 호출 → 참조 대입"의 여러 단계인데, 재정렬이 일어나면 "참조 대입"이 "생성자 호출"보다 먼저 실행될 수 있다. 그러면 다른 스레드가 **아직 초기화되지 않은 객체**를 보게 된다. `volatile`을 붙이면 어셈블리에 `lock addl $0x0,(%esp)` 같은 **메모리 장벽(memory barrier)** 명령이 추가되어 장벽 뒤의 명령을 앞으로 재정렬하지 못하게 막는다. (이 때문에 JDK 5 이전에는 DCL이 제대로 동작하지 않았다.) + +**성능 정리** +- `lock` 방식보다 성능이 좋다. +- 읽기 성능은 일반 변수와 거의 같다. +- 쓰기는 메모리 장벽 명령어가 끼어들어 더 느릴 수 있다. (그래도 대부분 상황에서 락보다는 전체 부하가 적다.) + +### 12.3.4 long과 double 변수용 특별 규칙 + +자바 메모리 모델은 8가지 기본 연산이 모두 원자적이어야 한다고 규정하지만, **64비트 데이터 타입인 `long`과 `double`에는 좀 더 느슨한 규칙**이 적용된다. `volatile`로 지정되지 않은 64비트 데이터의 읽기/쓰기를 **32비트 연산 2개로 나눠 처리**할 수 있도록 허용한 것이다. 이를 "long과 double 변수의 비원자적 처리"라고 한다. + +이론상으론 "반만 수정된" 값을 읽을 위험이 있지만, **실무에서는 거의 일어나지 않는다.** 현재 주류 상용 64비트 가상 머신에서는 비원자적 접근이 관측되지 않았고, JDK 9부터 핫스팟은 `-XX:+AlwaysAtomicAccesses` 매개 변수로 모든 데이터 타입을 원자적으로 접근하게 할 수 있다. 결론적으로 **스레드 경합이 뻔한 상황이 아니라면 단지 `long`/`double`이라는 이유로 굳이 `volatile`을 붙일 필요는 없다.** + +### 12.3.5 원자성, 가시성, 실행 순서 + +자바 메모리 모델이 동시성 처리에서 다루는 세 가지 핵심 특성을 정리해보면 다음과 같다. (이 셋을 분리해서 보는 시각이 개인적으로 가장 도움이 됐다.) + +**원자성(Atomicity)** +- 기본 데이터 타입의 `read/load/use/assign/store/write`는 대체로 원자적이다. +- 더 넓은 범위가 필요하면 `lock`/`unlock`을 쓰는데, 자바는 이를 직접 노출하지 않고 한 단계 추상화된 `monitorenter`/`monitorexit` 바이트코드로 제공한다. 이게 자바 코드의 **`synchronized` 블록**에 해당한다. + +**가시성(Visibility)** +- 한 스레드의 수정 결과를 다른 스레드가 즉시 알 수 있는 성질. +- 가시성을 보장하는 키워드가 **세 개** 있다: `volatile`, `synchronized`, `final`. + - `synchronized`: 잠금 해제 전에 변숫값을 메인 메모리에 동기화하므로 가시성 확보. + - `final`: 생성자에서 초기화가 완료된(this가 탈출하지 않은) `final` 필드는 다른 스레드가 동기화 없이도 올바르게 본다. + +**실행 순서(Ordering)** +- 현재 스레드 안에서 보면 모든 연산이 순차적이다(within-thread as-if-serial). 하지만 **다른 스레드에서 보면 순서가 달라 보일 수 있다.** 이게 "명령어 재정렬"과 "메인↔작업 메모리 동기화 지연" 현상이다. +- 순서를 보장하는 키워드는 `volatile`(재정렬 금지)과 `synchronized`(한 번에 한 스레드만 진입)다. + +### 12.3.6 선 발생 원칙 (happens-before) + +만약 모든 실행 순서를 `volatile`과 `synchronized`로만 처리한다면 코드가 매우 장황해진다. 다행히 자바에는 **선 발생 원칙(happens-before)** 이 있어서, 몇 가지 규칙만으로 두 작업의 충돌 가능성을 판단할 수 있다. + +"A가 B보다 선 발생한다"는 것은 **B가 수행되기 전에 A의 영향(변수 변경, 메서드 호출 결과 등)을 B에서 관찰할 수 있다**는 뜻이다. 동기화 장치 없이도 자연스럽게 보장되는 대표 규칙은 다음과 같다. + +- **프로그램 순서 규칙**: 한 스레드 안에서는 제어 흐름 순서에 따라 앞 연산이 뒤 연산보다 선 발생한다. +- **모니터 락 규칙**: 잠금 해제 연산은 같은 락에 대한 잠금 연산보다 선 발생한다. +- **휘발성 변수 규칙**: `volatile` 변수의 쓰기는 같은 변수의 읽기보다 선 발생한다. +- **스레드 시작 규칙**: `Thread.start()`는 해당 스레드의 어떤 작업보다도 선 발생한다. +- **스레드 종료 규칙**: 스레드의 모든 작업은 해당 스레드의 종료 감지(`join()`, `isAlive()`)보다 선 발생한다. +- **스레드 인터럽트 규칙**: `interrupt()` 호출은 인터럽트 이벤트 감지보다 선 발생한다. +- **종료자 규칙**: 객체 초기화 완료는 `finalize()` 시작보다 선 발생한다. +- **전이성**: A → B, B → C 이면 A → C. + +여기서 **가장 핵심적인 교훈**은 이것이다. + +> **시간 순서(time order)와 선 발생(happens-before)은 인과 관계가 없다.** +> 시간상 먼저 실행됐다고 선 발생하는 것도 아니고, 선 발생한다고 시간상 먼저인 것도 아니다. 따라서 **동시성 안전 문제는 시간 순서가 아니라 선 발생 원칙으로 판단해야 한다.** + +예를 들어 평범한 게터/세터(`getValue()`/`setValue()`)는 어떤 선 발생 규칙도 적용되지 않으므로, A가 시간상 먼저 `setValue(1)`을 호출하더라도 B가 `getValue()`로 1을 얻는다는 보장이 없다. 이때는 세터를 `synchronized`로 만들거나 `value`를 `volatile`로 선언해서 선 발생 관계를 만들어줘야 한다. + +## 12.4 자바와 스레드 + +동시성이라고 해서 반드시 멀티스레딩을 뜻하진 않지만(PHP는 멀티프로세스 동시성이 일반적이다), **자바에서 동시성은 기본적으로 스레드와 분리해 이야기할 수 없다.** + +### 12.4.1 스레드 구현 + +스레드는 **프로세스보다 가벼운 스케줄링 단위**다. 스레드 각각은 프로세스 자원(메모리 주소, 파일 I/O 등)을 공유하면서 독립적으로 스케줄링된다. 구현 방법은 크게 세 가지다. + +**① 커널 스레드 구현 (1:1)** + +운영 체제 커널이 직접 지원하는 스레드. 커널 스레드의 고수준 인터페이스인 **경량 프로세스(LWP, Light Weight Process)** 를 통해 프로그램이 사용한다. 강점은 안정성이지만, 생성·소멸·동기화가 모두 **시스템 호출**로 이루어져 사용자 모드 ↔ 커널 모드 전환 비용이 크다. + +**② 사용자 스레드 구현 (1:N)** + +온전히 사용자 공간에서 구현되는 스레드. 커널의 도움 없이 처리되므로 매우 빠르고 가볍지만, 블로킹 처리·멀티프로세서 매핑 같은 문제를 프로그램이 직접 해결해야 해서 구현이 복잡하다. 그래서 자바와 루비는 한때 썼다가 결국 포기했다. (단, 고 언어·얼랭 등 최근 언어들이 다시 부흥시키는 중.) + +**③ 하이브리드 구현 (M:N)** + +사용자 스레드와 경량 프로세스를 함께 쓰는 방식. 커널이 제공하는 스케줄링·매핑을 활용하면서도 동시성 규모를 키울 수 있다. + +**자바 스레드의 역사가 흥미로웠다.** +- JDK 1.2 이전 클래식 VM: 자바 스레드를 **그린 스레드(green thread)**, 즉 사용자 스레드로 구현. +- JDK 1.3부터: 운영 체제의 기본 스레드 모델(주로 1:1)을 기반으로 선회. 핫스팟에서 자바 스레드는 OS 커널 스레드에 직접 매핑된다. +- **JDK 21**: 가상 스레드(virtual thread) 정식 도입 → 12.5에서 본격적으로 다룬다. + +### 12.4.2 자바 스레드 스케줄링 + +스케줄링이란 시스템이 프로세서 사용 권한을 스레드에 할당하는 일이다. 방법은 두 가지다. + +- **협력적 스케줄링**: 스레드가 스스로 실행 시간을 제어한다. 구현이 쉽고 동기화 문제가 거의 없지만, 한 스레드가 양보를 안 하면 시스템 전체가 멈춘다(옛날 윈도우 3.x). 루아의 코루틴이 대표적. +- **선점형 스케줄링**: 시스템이 실행 시간을 할당한다. 한 스레드가 폭주해도 시스템이 강제로 회수할 수 있다. **자바는 이 방식을 쓴다.** + +자바는 `Thread.MIN_PRIORITY`부터 `Thread.MAX_PRIORITY`까지 **10단계 우선순위**를 제공한다. 다만 이건 운영 체제에 대한 "권고"일 뿐 신뢰할 만한 조율 수단이 아니다. 윈도우는 우선순위 단계가 7개뿐이라 자바의 1·2, 3·4, 6·7, 8·9는 실제로 차이가 없고, 우선순위 부스팅 같은 OS 기능 때문에 결과가 또 달라진다. **즉, 우선순위에 동작을 의존하면 안 된다.** + +### 12.4.3 상태 전이 + +자바 스레드는 어느 시점이든 다음 **여섯 가지 상태** 중 하나에 놓인다. + +| 상태 | 설명 | +| --- | --- | +| **신규(New)** | 생성됐지만 아직 시작 전 | +| **실행 중(Runnable)** | OS의 running과 ready를 합친 상태 (실행 중이거나 실행 시간 할당 대기) | +| **무기한 대기(Waiting)** | 다른 스레드가 명시적으로 깨워줘야 함 (`Object.wait()`, `Thread.join()`, `LockSupport.park()`) | +| **시간 제한 대기(Timed Waiting)** | 일정 시간 지나면 자동으로 깨어남 (`Thread.sleep()`, 타임아웃 있는 `wait()`/`join()`) | +| **블록(Blocked)** | 배타적 락 획득을 기다림 (동기화 영역 진입 대기) — '대기'와 다른 점은 **락 획득**을 기다린다는 것 | +| **종료(Terminated)** | 실행을 마침 | + + + +> **대기 vs 블록**을 헷갈렸었는데, **블록은 "다른 스레드가 락을 풀어줄 때까지", 대기는 "다른 스레드가 깨워줄 때까지"** 기다린다는 점이 다르다. + +## 12.5 자바와 가상 스레드 + +자바는 OS별 스레드 모델 차이를 숨기는 통합 인터페이스를 제공했고, 이는 탄생 초기 큰 장점이었다. 서블릿으로 HTTP 요청 하나를 스레드 하나에 매핑하는 "1:1 서비스"가 대표적이다. 하지만 이 1:1 모델이 **요즘 시대에는 오히려 족쇄가 되기도 한다.** (이 절이 백엔드 입장에서 가장 와닿았다.) + +### 12.5.1 커널 스레드의 한계 + +오늘날 웹 서비스는 마이크로서비스로 세분화되면서, 여러 외부 서비스에 요청을 분산해 응답을 취합하는 일이 흔하다. 각 서비스가 매우 빠르게 끝나야 전체 응답이 합리적인 시간 안에 완성된다. + +문제는 자바 스레드가 OS 커널 스레드에 1:1로 매핑되다 보니 **전환·스케줄링 비용이 크고, 동시에 띄울 수 있는 스레드 수도 제한된다**는 점이다. 과거 모놀리식에서는 요청당 처리 시간이 길어서 스레드 전환 비용이 묻혔지만, 이제는 요청당 실행 시간이 매우 짧아져서 **사용자 스레드 전환 부하가 계산 자체의 부하에 근접**할 만큼 비효율이 커졌다. + +> 기존 자바 웹 서버의 스레드 풀 용량은 보통 수십~200개 수준이다. (예: 톰캣 기본 최대 200개) 여기에 수백만 요청을 쏟아부으면 전환 손실이 상당하다. 그래서 사람들이 옛날 **그린 스레드**가 주던 이점을 그리워하게 됐다. + +### 12.5.2 코루틴의 귀환 + +해법의 실마리는 **코루틴(coroutine)** 이다. 핵심은 문맥 전환(레지스터·캐시 데이터 저장·복원) 비용을 줄이는 것. + +- **스택풀 코루틴(stackful)**: 콜 스택을 완벽히 보관·복원. 과거 사용자 스레드가 여기 해당하며, 협력적 스케줄링이라 "코루틴"으로 불렸다. +- **스택리스 코루틴(stackless)**: 상태를 클로저에 저장하는 유한 상태 머신. 복원 비용은 더 싸지만 기능은 제한적. `await`, `async`, `yield` 같은 키워드가 여기 속한다. + +코루틴의 가장 큰 장점은 **가볍다**는 것이다. 핫스팟 스레드 스택은 보통 1MB + 커널 데이터 구조용 16KB가 추가로 드는데, 코루틴 스택은 수백 바이트~수 KB 수준이다. 그래서 스레드 풀이 200개일 때, 코루틴은 **수십만 개가 공존**할 수 있다. + +### 12.5.3 가상 스레드: 자바의 해법 + +스택풀 코루틴을 자바가 구현한 것이 **파이버(Fiber)** → 지금의 **가상 스레드**다. + +- 2017년 OpenJDK가 **룸 프로젝트(Project Loom)** 출범 → **JDK 21에 정식 반영**. +- 초기 이름은 파이버였으나 혼동을 피해 "가상 스레드"로 변경. +- 기존 스레드 모델과 **공존**한다(대체가 아님). 가상 스레드는 운영 체제가 아니라 **자바 가상 머신이 직접 스케줄링**한다. + + + +가상 스레드는 **플랫폼 스레드(= 기존 자바 스레드)와 N:M 관계**다. 가상 스레드 하나가 블록되면 플랫폼 스레드는 연결된 다른 가상 스레드 작업을 이어서 진행한다. 즉 **커널 스레드는 문맥 전환 없이 쉬지 않고 애플리케이션 코드를 실행**하는 것이다. 게다가 기존 스레드 API를 최소한만 수정하도록 설계해서, **기존 코드를 큰 수정 없이 가상 스레드로 옮길 수 있다.** + +**벤치마크가 인상적이었다.** 마리아DB(MariaDB) JDBC 커넥터에 가상 스레드를 적용했더니, 네 가지 시나리오 모두에서 플랫폼 스레드 대비 **4.3~5.3배 빠른 성능**을 보였다(논블로킹 기반 R2DBC보다도 빨랐다). + +> 단, 핵심은 **가상 스레드의 이점은 I/O 작업이 많아서 스레드 전환이 잦은 상황에서 드러난다**는 점이다. CPU 바운드 연산만 하는 작업이라면 별 차이가 없을 수 있다. DB 연결처럼 I/O 비중이 큰 백엔드 작업에 특히 효과적이라는 뜻이라, 실무에서 바로 떠올려볼 만하다. + +가상 스레드를 쓰는 코드는 실행 대상인 **후속문(continuation)** 과 **스케줄러**로 나뉜다. 스케줄러를 분리한 덕분에 스케줄링 방식을 직접 제어할 수 있고, 룸의 기본 스케줄러는 JDK 7에 추가된 `ForkJoinPool`이다. JDK 21 이상이면 가상 스레드를 바로 쓸 수 있고, 그 이전 JDK에서 코루틴을 쓰고 싶다면 가상 머신에 의존하지 않는 **퀘이사(Quasar)** 라이브러리를 쓸 수도 있다. diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\260\200\354\203\201\354\212\244\353\240\210\353\223\234.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\260\200\354\203\201\354\212\244\353\240\210\353\223\234.png" new file mode 100644 index 0000000..338982d Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\260\200\354\203\201\354\212\244\353\240\210\353\223\234.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\262\275\353\237\211\355\224\204\353\241\234\354\204\270\354\212\244\354\231\200\354\273\244\353\204\220\354\212\244\353\240\210\353\223\234.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\262\275\353\237\211\355\224\204\353\241\234\354\204\270\354\212\244\354\231\200\354\273\244\353\204\220\354\212\244\353\240\210\353\223\234.png" new file mode 100644 index 0000000..478be57 Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\352\262\275\353\237\211\355\224\204\353\241\234\354\204\270\354\212\244\354\231\200\354\273\244\353\204\220\354\212\244\353\240\210\353\223\234.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\202\254\354\232\251\354\236\220\354\212\244\353\240\210\353\223\234\352\264\200\352\263\204.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\202\254\354\232\251\354\236\220\354\212\244\353\240\210\353\223\234\352\264\200\352\263\204.png" new file mode 100644 index 0000000..61d3f5d Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\202\254\354\232\251\354\236\220\354\212\244\353\240\210\353\223\234\352\264\200\352\263\204.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\212\244\353\240\210\353\223\234\354\203\201\355\203\234\354\240\204\354\235\264.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\212\244\353\240\210\353\223\234\354\203\201\355\203\234\354\240\204\354\235\264.png" new file mode 100644 index 0000000..2913f37 Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\212\244\353\240\210\353\223\234\354\203\201\355\203\234\354\240\204\354\235\264.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\272\220\354\213\234 \354\235\274\352\264\200\354\204\261.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\272\220\354\213\234 \354\235\274\352\264\200\354\204\261.png" new file mode 100644 index 0000000..3aa1d5a Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\354\272\220\354\213\234 \354\235\274\352\264\200\354\204\261.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\355\225\230\354\235\264\353\270\214\353\246\254\353\223\234 \352\264\200\352\263\204.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\355\225\230\354\235\264\353\270\214\353\246\254\353\223\234 \352\264\200\352\263\204.png" new file mode 100644 index 0000000..dc1183c Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\352\271\200\354\232\251\353\262\224/12\354\236\245_\354\236\220\353\260\224_\353\251\224\353\252\250\353\246\254_\353\252\250\353\215\270\352\263\274_\354\212\244\353\240\210\353\223\234/img/\355\225\230\354\235\264\353\270\214\353\246\254\353\223\234 \352\264\200\352\263\204.png" differ diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260.md" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260.md" new file mode 100644 index 0000000..59998c2 --- /dev/null +++ "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260.md" @@ -0,0 +1,185 @@ +# 1장 자바와 친해지기 + +사실 저는 자바의 "자"도 모르는 사람이었습니다. 이번에 한 번 열심히 알아가보겠습니다. + +## 1.1 들어가며 + +자바는 많은 장점이 있습니다. + +- 엄격한 구조를 갖춘 객체지향 프로그래밍 +- 하드웨어 플랫폼이라는 족쇄를 제거하여, "Write once, run anywhere"를 실현 +- 메모리 관리 시스템이 있어 메모리 누수 문제와 엉뚱한 메모리를 가리키는 문제를 "대부분" 회피 +- 런타임에 핫코드 감지, 컴파일하고 최적화하여 자바 애플리케이션이 최상의 성능을 내도록 함 +- 표준 API가 풍부하고 많은 기업과 오픈 소스 커뮤니티에서 제공하는 다양한 기능의 서드 파티 라이브러리 활용 + +이러한 장점을 활용하는 수준이 아니라, 기술적 특성이 어떻게 활용되고 구현되었는지 이해를 해봅시다. + +## 1.2 자바 기술 시스템 + +자바 기술 시스템은 다음 요소를 포함합니다. + +- 자바 프로그래밍 언어 +- (다양한 하드웨어 플랫폼용) 자바 가상 머신 구현 +- 클래스 파일 포맷 +- 자바 클래스 라이브러리 API(표준 API) +- 서드 파티 클래스 라이브러리 + +자바 프로그래밍 언어, 자바 가상 머신, 자바 클래스 라이브러리를 묶어 JDK라고 합니다. JDK는 자바 개발에 필요한 최소한의 환경입니다. + +자바 SE API와 자바 가상 머신, 배포 기술을 묶어 JRE라고 합니다. + +![alt text](image.png) + +책에 있는 그림은 JDK 7시절이라는데 해당 이미지는 언제 버전인지는 잘 모르겠습니다. 전체적인 구성을 그리는데만 사용하겠습니다. + +자바가 활용되는 분야 또는 기술이 집중하는 핵심 비즈니스로 나누면 다음과 같이 나눌 수 있습니다. + +- 자바 카드 : 스마트 카드와 같은 소형 기기 및 변조 방지 보안 칩등에서 실행되는 자바 플랫폼 +- 자바 ME : 휴대 전화, PDA같은 모바일 기기에서 실행되는 자바 프로그램용 플랫폼 +- 자바 SE : 데스크톱 애플리케이션용 자바 플랫폼 +- 자바 EE : ERP, MIS, CRM 애플리케이션과 같은 다중 계층 구조로 이루어진 기업 규모 애플리케이션용 자바 플랫폼 JDK 10부터는 관리 주체가 이클랩스 재단으로 바뀌면서 자카르타 EE로 개명 + +현 시점에서는 나누는 게 크게 의미 없습니다. 자바 SE를 제외하고는 힘을 잃어 가고 있기 때문입니다. + +## 1.3 자바의 과거와 현재 + +자바도 나이가 꽤 있는 언어입니다. 변천사 정도는 가볍게 알아두면 좋아보이네요. + +썬에서 오라클로 옮겨가는 과정, 오라클이 구글 고소하는 썰, JDK별 기능을 기술해뒀으니 읽어보시는 것을 추천드립니다. + +## 1.4 자바 가상 머신 제품군 + +자바 가상 머신의 개발 궤적과 역사적으로 중요한 변화 과정에 대해 설명합니다. + +### C, C++보다 느린이유 + +JDK 1.0에 포함된 가상 머신을 클래식 VM이라고 합니다. 이 가상 머신은 자바 코드를 순전히 인터프리터 방식으로 실행합니다. +JIT 컴파일러를 사용하려면 플러그인을 추가하면 되는데, 플러그인하는 순간 가상 머신의 실행 시스템 전체가 JIT 컴파일러에 넘어갑니다. 즉, 인터프리터가 더 이상 동작하지 않는겁니다. + +당시 인터프리터와 컴파일러는 함께 구동하지 않았기에 컴파일러를 사용하기 시작하면 실행 빈도 등 컴파일러에 따른 득실과 상관없이 '코드 전체'를 컴파일 해야 했습니다. 그래서 자칫하면 프로그램 응답 속도가 너무 느려져 오래 걸리는 최적화 기법은 적용할 수 없었습니다. + +JIT 컴파일러로 네이티브 코드를 만들어 냈음에도 C, C++ 프로그램보다 실행 효율이 나쁠 수 밖에 없었습니다. + +JIT(Just-In-Time) 컴파일러와 AOT(Ahead-Of-Time) 컴파일러 차이에 대해서도 공부하면 좋아보입니다. + +### 이그잭트 VM + +이런 문제를 개선하기 위해 JDK 1.2에서 이그잭트 VM을 만들어냈습니다. 핫스팟 검출, 2단계 JIT 컴파일러, 컴파일러와 인터프리터 혼합 모드 등을 갖추고 있습니다. + +이그잭트 VM은 정확한 메모리 관리(Exact memory management)에서 따왔습니다. 가상 머신이 메모리의 특정 위치에 있는 데이터의 구체적인 자료형을 알 수 있다는 의미입니다. + +이 덕분에 핸들에 기초한 클래식 VM의 객체 검색 방식에서 벗어날 수 있었고, 성능이 크게 개선되었습니다. + +하지만 핫스팟 VM에 밀려 크게 주목받진 못했습니다. + +### 핫스팟 VM + +핫스팟 VM을 처음들어보는 자바 개발자는 없을 것이라는데, 저는 처음 들어봤습니다 ㅋㅋ;; + +OpenJDK의 기본 가상 머신이자 가장 널리 사용되는 JVM이라고 합니다. + +핫 코드 감지가 들어가 있어 이름도 핫스팟 VM이라고 합니다. 핫 코드 감지는 '컴파일했을 때 효과를 가장 크게 볼 수 있는 코드 영역'을 런타임에 알아내어 JIT 컴파일러에 전달해줍니다. 그러면 JIT 컴파일러가 해당 코드를 메서드 단위로 컴파일합니다. 메서드가 자주 호출되거나 메서드 안에 시간을 많이 잡아 먹는 for문이 있다면 JIT 컴파일을 수행해 스택을 치환합니다. 이처럼 런타임에 스택을 치환하는 기숭를 온스택 치환(OSR)이라고 합니다. + +이런식으로 컴파일러와 인터프리터가 조화롭게 협력해 프로그램 응답 속도와 실행 성능 사이의 균형을 잡아줍니다. + +### 임베디드 VM + +모바일과 임베디드 시장에 특화한 JVM입니다. 크게 쇠퇴한 부류입니다. + +### 이인자 : BEA JRockit과 IBM J9 VM + +앞서 소개한 JVM은 썬과 오라클에서 만든 JVM인데, 썬과 오라클 외 조직에서도 만든 JVM이 있습니다. BEA의 JRockit과 IBM의 J9 VM입니다. + +--- + +그외에도 매우많은 JVM이 있지만, 사장된 JVM이 많아 따로 적진 않겠습니다. + +## 1.5 자바 기술의 미래 + +자바가 미래를 어떻게 대비하고 있는지 알아보겠습니다. + +### 1.5.1 언어 독립 + +자바를 대체할 언어들이 많이 등장하고 있습니다. 코틀린, Golang, JS, Python 등이 자주 언급됩니다. 2020년대에 들어서 자바는 살짝 주춤하는 상태입니다. + +모든 분야를 지배할 수 있는 언어는 없습니다. + +2018년 4월 오라클은 그랄VM이라는 새로운 기술을 "어디서든 더 빠르게 실행한다(Run programs faster anywhere)" 라는 구호와 함께 발표했습니다. + +그랄VM은 핫스팟 가상 머신 위에 구축된 크로스 언어 풀스택 가상 머신입니다. 자바, 코틀린, 스칼라, 그루비같은 자바 가상 머신 언어들은 물론 LLVM 기반 컴파일러를 사용하는 C, C++, 러스트. 그 외 JS, 루비, Python, R까지도 지원합니다. 그랄VM에서는 추가 비용없이 이 언어들을 혼합해 사용할 수 있습니다. + +그랄 VM은 기본적으로 각종 언어의 소스코드나 컴파일된 중간 형식을 인터프리터를 통해 그랄VM이 이해할 수 있는 중간 표현(IR)으로 변환하는 식으로 작동합니다. + +그랄VM은 JVM으로 활용할 수 있습니다. 핫스팟을 기반으로 탄생했으며, 자바 SE와 완벽히 호환됩니다. JIT 컴파일러에 차이가 있으며, 실행 효율과 컴파일 품질 모두 표준 핫스팟보다 나은 것으로 평가됩니다. + +### 1.5.2 차세대 JIT 컴파일러 + +서버용 제품처럼 장기간 운용되는 애플리케이션에서는 자주 실행되는 핫 코드를 탐지하여 네이티브 코드로 컴파일합니다. 이런 유형의 자바 애플리케이션은 JIT 컴파일러의 출력 품질이 실행 효율을 크게 좌우할 수 밖에 없습니다. + +핫스팟은 기본적으로 JIT 컴파일러를 2개 내장하고 있습니다. 하나는 컴파일 속도가 빠른 대신 최적화를 적게 하는 클라이언트 컴파일러(C1 컴파일러)이고, 다른 하나는 컴파일 속도는 느리지만 더 많은 최적화를 적용하는 서버 컴파일러(C2 컴파일러)입니다. + +JDK 10부터는 그랄 컴파일러가 추가되었습니다. 그랄VM 프로젝트의 일환으로 만들어진 기술이빈다. C2 컴파일러를 대체할 목적으로 핫스팟에 도입되었습니다. JDK 16부터는 개발과 관리 효율을 높이고자 그랄 컴파일러를 JDK에서 독립시켜 그랄VM으로 터전을 옮겼습니다. + +### 1.5.3 네이티브를 향한 발걸음 + +장시간 실행할 필요가 없거나 크기가 작은 애플리케이션의 경우 자바로 개발하면 본질적인 단점이 몇 가지 있습니다. "Hello World"를 출력하려해도 100MB가 넘는 JRE가 필요하다는 점도 있지만, 애플리케이션 아키텍처가 거대한 단일 아키텍처에서 작은 마이크로서비스 아키텍처로 옮겨가고 있다는 점입니다. + +MSA에서는 분할된 서비스 각각이 더 이상 수십에서 수백GB의 메모리를 쓸 일이 없습니다. 서버리스 아키텍처에서는 이러한 모순이 더 드러납니다. 함수는 서비스보다도 크기가 작고 실행 시간이 짧습니다. + +이러한 시대의 흐름을 따라가기 위해 자바는 애플리케이션 클래스 데이터 공유(AppCDS, 로딩한 클래스 정보를 캐시해 두어 다음번 구동 시간을 줄이는 기술)와 노옵(no-op) 가비지 컬렉터 엡실론(메모리를 할당만 해주고 회수는 하지 않는 컬렉터로, 간단한 작업을 빠르게 처리한 후 즉시 종료하는 애플리케이션에 적합) 등의 기술을 포함시켰습니다. + +AOT 컴파일러를 도입하려는 움직임도 있었습니다. 하지만 자바의 철학인 WORA와 반대되기도 합니다. JDK 9 ~15에 jaotc라는 이름으로 도입되긴 했지만 성능은 좋지 않았다고 합니다. + +기대에 부응하기위해 서브스트레이트 VM이 등장했습니다. 사전 컴파일된 네이티브 코드를 핫스팟 가상 머신 없이 실행하는 기술로, 독자적인 예외 처리, 스레드 관리, 메모리 관리, 자바 네이티브 인터페이스 접근 메커니즘 등을 갖춘 작은 런타임 환경입니다. + +그랄VM은 서브스트레이트 VM과 사용자 프로그램을 하나로 묶어 프로그램으로부터 도달 가능한 코드만 추려 네이티브 이미지에 담습니다. 또한 이 과정에서 초기화까지 수행하여 최종 실행 파일이 생성되면 초기화된 힙 스냅숏을 저장합니다. 이런 식으로 JVM이 수행하던 초기화 과정을 건너뛰고 프로그램을 곧바로 실행하여 초기 구동 시간을 획기적으로 줄였습니다. + +이 방식이 가능하려면 프로그램이 완결된 형태여야 합니다. 컴파일러가 찾을 수 없는 코드나 클래스 라이브러리를 동적으로 읽어들일 수 없다는 뜻입니다. + +서브스트레이트 VM은 메모리 사용량도 크게 줄였습니다. + +### 1.5.4 유연한 뚱뚱이..?? + +핫스팟은 다양한 분야의 애플리케이션을 지원하도록 만들어진 JVM입니다. 너무 많은 것을 요구당하고, 이를 수용하기 위해 지속적으로 모듈화하고 확장성을 키우기 위해 리팩토링하고 있다고 합니다. + +핫스팟 JVM은 컴파일을 할 때 원하는 기능을 지정하여 맞춤형 가상 머신을 만들 수 있습니다. + +JDK 1.4에서는 가상 머신 내부 정보를 알려주기 위해 가상 머신 명세에 정의되지 않은 프로파일러 인터페이스(JVMPI)와 디버그 인터페이스(JVMDI)를 제공합니다. + +JDK 5는 모든 형태의 자바 가상 머신 관련 도구를 프로그래밍 인터페이스를 모아 추상화한 고수준 인터페이스 JVMTI를 도입했습니다. + +JDK 9에서는 자바 언어 수준의 컴파일러 인터페이스인 JVMCI가 도입되어 가상 머신 외부에서 JIT 컴파일러를 추가하거나 교체할 수 있게 되었습니다. + +JDK 10에서는 가비지 컬렉터 인터페이스를 리팩토링하여 내부 컬렉터들이 일관되게 동작하도록 했습니다. 이 작업으로 인해 JDK 12에서 오라클이 아닌 회사에서 개발한 셰넌도어 컬렉터를 핫스팟에 추가할 수 있었고, JDK 14에서는 CMS 컬렉터를 제거할 수 있었습니다. + +연이은 리팩토링과 개방을 거쳐 핫스팟 가상 머신은 시간의 침식으로부터 점점 자유로워지고 있습니다. + +개방성과 확장성이 좋아지면서 바깥 세상과 연동하기 쉬운 플랫폼이 되었습니다. + +### 1.5.5 언어 문법의 지속적인 개선 + +상대적으로 가장 덜 중요한 개선이라고 합니다. 인간 친화적이지 않은 JS는 큰 성공을 했고, 자바보다 문법이 우아한 C#은 아직도 자바를 못따라오고 있습니다. 언어 문법이 성공을 가르는 제 1 원칙은 아니라는 이야기 입니다. + +그래도 언어가 제공하는 기능과 문법은 생산성과 개발 효율에 많은 영향을 주는 요인이긴 합니다. + +책에 있는 것만 간단하게 정리해보았습니다. + +- JDK 10 : 지역 변수 타입 추론(var 키워드) +- JDK 11 : 람다식 매개 변수로 사용할 수 있도록 지역 변수 구문 개선 +- JDK 14 : switch문을 표현식으로 사용할 수 있는 문법 추가 +- JDK 15 : text block 추가 +- JDK 16 : 패턴 매칭 능력을 부여해 instanceof 연산자 표현력 강화 / record 타입 추가 +- JDK 17 : 자신을 확장하거나 구현할 수 있는 클래스와 인터페이스를 제한하는 봉인된 클래스 / 봉인 인터페이스 타입 추가 +- JDK 21 : 레코드 클래스로부터 데이터를 가져올 때 패턴 매칭 제공 / switch문, 표현식의 패턴 매칭 능력 개선 + +진행중인 개선안들도 있습니다. 미리 보기 형태로 JDK에 포함되어 원하면 사용해볼 수 있습니다. + +편의 문법 외의 언어 기능도 다방면에서 지속적으로 개선되고 있습니다. + +- 발할라 프로젝트 : 값 타입과 원시 타입을 일반화한 제네릭 타입을 제공하고, 불변 타입과 비참조 타입을 명시적으로 선언할 수 있게 합니다. +- 파나마 프로젝트 : JVM과 네이티브 코드의 경계를 허무는 작업입니다. 자바 코드는 JNI를 이용해 네이티브 코드를 호출할 수 있으나, 이용하기 번거롭고 빈번하게 호출시 성능 부하도 큽니다. 파나마 프로젝트는 자바 코드와 네이티브 코드가 어우러지는 방법을 제공합니다. + - 외부 함수 및 메모리 API : 힙 바깥 메모리를 사용하기 위한 API. + - 벡터 API : 하드웨어로 가속되는 벡터 계산 기능 제공 + - Jextract : 네이티브 라이브러리 헤더와 자바를 바인딩해 주는 도구 + +자바는 6개월마다 새로운 버전의 JDK를 배포하면서 다른 언어의 기능을 흡수할 것입니다. diff --git "a/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/image.png" "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/image.png" new file mode 100644 index 0000000..a5e7d2c Binary files /dev/null and "b/JVM \353\260\221\353\260\224\353\213\245\352\271\214\354\247\200 \355\214\214\355\227\244\354\271\230\352\270\260/\354\206\214\355\203\234\355\230\270/1\354\236\245_\354\236\220\353\260\224\354\231\200_\354\271\234\355\225\264\354\247\200\352\270\260/image.png" differ