HW부터 JVM + Spring까지 — 서버 실행 구조

한 문장 요약

NIC → OS TCP 스택 → Tomcat/NIO 커넥터 → JVM 스레드 → Spring DispatcherServlet → 애플리케이션 코드 → 응답


전체 실행 스택 (HTTP 요청 기준)

┌─────────────────────────────────────────────────────┐
│  클라이언트 (브라우저 / 앱 / curl)                  │
│  GET /users HTTP/1.1                                │
└──────────────────────┬──────────────────────────────┘
                       │  TCP 패킷
                       ▼
┌─────────────────────────────────────────────────────┐
│  NIC (Network Interface Card)                       │
│  - 전기/광 신호를 디지털 패킷으로 변환              │
│  - DMA로 패킷을 RAM에 적재                          │
│  - 인터럽트 또는 polling으로 CPU에 수신 알림        │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  OS 커널 네트워크 스택                               │
│                                                     │
│  이더넷 → IP → TCP → 소켓 버퍼(fd)에 데이터 적재    │
│                                                     │
│  Java 서버는 socket fd를 epoll/kqueue/select로 감시 │
│  → 읽기 가능 이벤트 발생                            │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  JVM 프로세스 시작                                   │
│                                                     │
│  java -jar app.jar                                  │
│    ↓                                                │
│  OS 로더가 JVM 바이너리 실행                         │
│    ↓                                                │
│  JVM이 힙/메타스페이스/스레드 스택 등 런타임 준비    │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  클래스 로딩 + 바이트코드 검증                        │
│                                                     │
│  .class 파일 (Java 바이트코드)                       │
│    ↓ ClassLoader                                     │
│  Bootstrap → Platform → AppClassLoader              │
│    ↓                                                 │
│  Bytecode Verifier                                   │
│  - 타입 안정성 확인                                  │
│  - 스택 언더플로우/오버플로우 검사                   │
│  - 불법 메모리 접근 방지                             │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  JVM 실행 엔진                                        │
│                                                     │
│  1. 처음에는 인터프리터가 바이트코드 실행            │
│  2. 자주 실행되는 Hot Method 감지                    │
│  3. C1/C2 JIT 컴파일러가 기계어 생성                 │
│                                                     │
│  예시 바이트코드:                                    │
│  ┌──────────────────────────────────────────────┐   │
│  │  iload_1                                     │   │
│  │  iload_2                                     │   │
│  │  iadd                                        │   │
│  │  ireturn                                     │   │
│  └──────────────────────────────────────────────┘   │
│                                                     │
│  → [[how-javascript-runs|JS JIT]]와 비슷하게         │
│    반복 코드는 네이티브로 최적화                    │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  Spring Boot 부트스트랩                              │
│                                                     │
│  main() → SpringApplication.run()                   │
│      ↓                                              │
│  ApplicationContext 생성                            │
│      ↓                                              │
│  Bean 정의 스캔 (@Component, @Service, @Controller) │
│      ↓                                              │
│  의존성 주입(DI)으로 싱글턴 Bean 초기화             │
│      ↓                                              │
│  내장 Tomcat/Jetty/Undertow 서버 시작               │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  Tomcat / Servlet 컨테이너                           │
│                                                     │
│  Acceptor 스레드: 새 TCP 연결 수락                  │
│  Poller/NIO: 읽기 가능한 소켓 감시                  │
│  Worker 스레드: 요청을 하나 가져와 처리             │
│                                                     │
│  전통적인 Servlet 모델:                              │
│  요청 1개 ↔ 작업 스레드 1개                          │
│  DB나 외부 API를 기다리는 동안 해당 스레드는 점유    │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  Spring MVC 요청 처리 파이프라인                      │
│                                                     │
│  HTTP 요청                                           │
│    ↓                                                │
│  Filter (서블릿 레벨 전처리)                         │
│    ↓                                                │
│  DispatcherServlet                                  │
│    ↓                                                │
│  HandlerMapping  → 어떤 Controller 메서드인지 결정  │
│    ↓                                                │
│  HandlerAdapter  → 메서드 인자 바인딩               │
│    ↓                                                │
│  @Controller / @RestController 메서드 실행          │
│    ↓                                                │
│  @Service / Repository 호출                          │
│    ↓                                                │
│  HttpMessageConverter로 JSON 직렬화                 │
│    ↓                                                │
│  HttpServletResponse.write()                        │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  JVM 메모리 구조                                      │
│                                                     │
│  ┌──────────────────────────────────────────────┐   │
│  │  Heap                                         │   │
│  │  - Young Generation (Eden, Survivor)         │   │
│  │  - Old Generation                            │   │
│  │                                              │   │
│  │  Metaspace                                   │   │
│  │  - 클래스 메타데이터                         │   │
│  │                                              │   │
│  │  Thread Stack                                │   │
│  │  - 메서드 프레임, 지역변수, 리턴 주소         │   │
│  └──────────────────────────────────────────────┘   │
│                                                     │
│  GC가 사용 안 하는 객체를 회수                      │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  응답 전송                                           │
│                                                     │
│  Tomcat이 응답 바이트를 소켓에 write                │
│    ↓                                                │
│  커널 TCP 버퍼 → NIC → 클라이언트                   │
└─────────────────────────────────────────────────────┘

바이트코드에서 기계어까지

UserService.class
  ↓ ClassLoader
JVM Method Area / Metaspace 등록
  ↓ Interpreter
Java 바이트코드 한 줄씩 실행
  ↓ HotSpot 프로파일링
  - 호출 횟수
  - 분기 패턴
  - 타입 정보
  ↓ C1/C2 JIT 컴파일
네이티브 기계어 캐시
  ↓
CPU Fetch-Decode-Execute

핵심:
  Java는 "한 번 컴파일해서 어디서나 실행"이지만
  실제 실행 시점에는 JVM이 다시 해석/JIT 최적화를 수행한다.

Spring이 Controller를 호출하는 과정

GET /users
  ↓
Tomcat Worker Thread
  ↓
FilterChain
  ↓
DispatcherServlet
  ↓
RequestMappingHandlerMapping
  ↓
UsersController#getUsers()
  ↓
UserService#getUsers()
  ↓
UserRepository.findAll()
  ↓
Jackson ObjectMapper
  ↓
HTTP Response Body

Spring 핵심 포인트:

  • DispatcherServlet이 모든 요청의 중앙 진입점
  • HandlerMapping이 URL과 메서드를 연결
  • DI 컨테이너가 Controller/Service/Repository를 미리 생성

Node.js 서버와 비교하면

Node.js:
  이벤트 루프 1개가 많은 연결을 번갈아 처리
  I/O 대기 중에는 다른 콜백 실행

Spring MVC:
  요청당 작업 스레드 1개를 점유하는 모델이 기본
  코드 흐름은 직선적이고 디버깅이 쉬움

고부하 환경 차이:
  Node.js  → 적은 스레드 + 논블로킹 I/O
  Spring   → 더 많은 스레드 + 커넥션 풀 + 튜닝

WebFlux는 예외: Spring도 WebFlux + Netty 조합이면 이벤트 루프 기반으로 동작 가능. 하지만 가장 흔한 Spring Boot 기본 구조는 Tomcat + Servlet 스레드 모델.


핵심 요약

GET /users 요청
  ↓ NIC (패킷 수신)
  ↓ OS TCP 스택 (소켓 버퍼)
  ↓ Tomcat NIO 커넥터 (연결 수락 + 워커 할당)
  ↓ JVM 스레드에서 Spring DispatcherServlet 실행
  ↓ Controller → Service → Repository
  ↓ Jackson JSON 직렬화
  ↓ 소켓 write() → NIC → 클라이언트

동시에 JVM 내부에서는:
  .class 바이트코드 로딩
  ↓ 인터프리터 실행
  ↓ Hot Code는 JIT 컴파일
  ↓ GC가 힙 메모리 관리

핵심: Spring은 직접 CPU 위에서 실행되지 않는다.
      JVM이 바이트코드를 해석/JIT하고,
      Spring은 그 위에서 HTTP 요청 처리 구조를 제공한다.