CS & Algorithm/CS

[CS] GC와 Spring MVC 요청 처리 과정

eunkonge 2026. 9. 3. 18:17 댓글 0

1. Java Garbage Collection

1. Garbage Collection이란?

Java에서 객체는 주로 Heap 영역에 생성된다.

User user = new User();

 

new User()로 생성된 객체는 Heap 영역에 저장되고 변수 user는 해당 객체를 참조한다.
프로그램이 실행되다 보면 더 이상 사용하지 않는 객체가 발생한다.

User user = new User();
user = null;

 

기존에 생성된 User 객체를 참조하는 곳이 없다면 해당 객체는 더 이상 사용할 수 없는 객체가 된다.

 

Garbage Collection(GC) 은 이처럼 더 이상 참조되지 않아 사용할 수 없는 객체를 찾아 Heap 메모리에서 제거하여 메모리를 자동으로 관리하는 기능.

 

자바에서는 GC가 작업을 수행하기 때문에 개발자가 C/C++ 처럼 메모리를 해체할 필요가 없다.

2. GC는 객체가 필요 없는지 어떻게 판단할까?

단순히 객체를 참조하는 변수가 있는지만 확인하는 것이 아니라 GC Root에 해당 객체까지 도달할 수 있는지(Reachability)를 기준으로 판단한다.

 

대표적인 GC Root에는 다음과 같은 것들이 있다.

  • 실행 중인 스레드의 Stack에서 참조하는 객체
  • Static 필드에서 참조하는 객체
  • JNI에서 참조하는 객체
    예를 들어 다음과 같은 참조 관계가 있다고 하자.

 

Object A, Object B 는 GC Root에서 도달할 수 있기 때문에 사용 중인 객체로 판단한다.

 

반면 Object C는 GC Root에서 도달할 수 없으므로 GC 대상이 될 수 있다.

즉,

GC는 GC Root를 기준으로 객체의 Reachability를 확인하고, 더 이상 도달할 수 없는 객체의 메모리를 회수한다.

3. Heap을 왜 세대(Generation)로 나눌까?

Java의 Heap은 개념적으로 객체의 생존 기간에 따라 다음과 같이 나누어 관리할 수 있다.


이렇게 나누는 이유는 대부분의 객체는 생성된 후 오래 살아남지 못한다는 특성을 활용하기 위해서다.

이를 Weak Generational Hypothesis 라고 한다.

 

예를 들어 HTTP 요청을 처리하면서 생성되는 DTO, 임시 문자열, 계산용 객체 등은 요청 처리가 끝나면 금방 필요 없어지는 경우가 많다.
반대로 캐시 데이터처럼 오랫동안 참조되는 객체도 존재한다.

 

따라서 모든 객체를 매번 동일한 방식으로 검사하는 것보다 수명이 짧은 객체와 오래 살아남는 객체를 구분하여 관리하는 것이 GC 효율을 높일 수 있다.

4. Young Generation

새롭게 생성된 객체는 일반적으로 Young Generation에서 관리된다.
Young Generation은 다시 다음과 같이 나눌 수 있다.

 

대부분의 객체는 처음 Eden 영역에 생성된다.

User user = new User();

 

Eden 영역이 가득 차면 Young 영역을 대상으로 GC가 수행되고 살아남은 객체들은 Survivor 영역으로 이동한다.

이러한 Young 영역의 수집을 일반적으로 Minor GC라고 부른다.

 

이후 GC가 반복되어도 계속 살아남는 객체는 일정 조건을 만족하면 Old Generation으로 이동한다.

 

이 과정을 Promotion이라고 한다.

5. Old Generation

Young Generation에서 여러 번의 GC를 거치고도 살아남은 객체는 Old Generation에서 관리될 수 있다.

 

즉, 상대적으로 수명이 긴 객체를 관리하는 영역이다.

 

Old 영역까지 포함하여 더 넓은 Heap 영역을 대상으로 수행되는 GC는 Young 영역 수집보다 일반적으로 비용이 크다.

 

GC 과정에서는 애플리케이션 스레드가 일시적으로 멈추는 Stop-The-World(STW) 구간이 발생할 수 있기 때문에 GC가 너무 자주 또는 오래 발생하면 애플리케이션 응답 시간에도 영향을 줄 수 있다.

 

단, 실제 Heap 구조와 GC 동작 방식은 사용하는 Garbage Collector(G1, ZGC 등)에 따라 달라질 수 있다. Young/Old는 객체의 세대별 관리 개념으로 이해하는 것이 좋다.

6. Young / Old로 나누는 이유

만약 Heap 전체를 매번 탐색한다면 많은 객체를 반복해서 검사해야 하므로 비용이 커질 수 있다.

 

하지만 실제 애플리케이션에서는 생성된 객체 상당수가 빠르게 사용되지 않게 된다.

 

따라서


하는 방식으로 GC 비용을 효율적으로 관리할 수 있다.

2. Spring MVC 요청 처리 과정

1. Spring MVC란?

Spring MVC는 Spring에서 웹 애플리케이션을 개발하기 위한 MVC 기반 웹 프레임워크이다.

 

Spring MVC에서는 클라이언트의 HTTP 요청을 여러 Controller가 직접 받는 것이 아니라 DispatcherServlet이 요청을 먼저 받아 적절한 Controller로 전달한다.

 

DispatcherServlet은 Spring MVC의 Front Controller 역할을 한다.

2. 전체 요청 처리 과정

REST API를 예로 들어보자.

@RestController 
@RequestMapping("/users") 
public class UserController { 

    @GetMapping("/{id}") 
    public UserResponse getUser(@PathVariable Long id) { 
        return userService.getUser(id); 
    } 
}

 

클라이언트과 다음과 같은 요청을 보냈다고 하자.

GET /users/1

 

전체적인 처리 과정은 다음과 같다.

3. DispatcherServlet이 요청을 받는다.

DispatcherServlet은 요청을 직접 처리하는 것이 아니라 요청을 처리할 적절한 Handler를 찾고, 필요한 Spring MVC 컴포넌트들을 연결하는 역할을 한다.

4. HandlerMapping이 Controller를 찾는다.

DispatcherServlet은 HandlerMapping에게 해당 요청을 처리할 Handler를 요청한다.

예를 들어

GET /users/1

 

이라는 요청이 들어왔다면 HandlerMapping은 등록되어 있는 요청 매핑 정보를 확인한다.

@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id)

 

그리고 해당 요청을 처리할 Controller 메서드를 찾아 DispatcherServlet에게 알려준다.

5. HandlerAdapter가 Controller를 호출한다.

찾은 Handler를 실행할 수 있는 HandlerAdapter를 통해 Controller를 호출한다.

HandlerAdapter는 Controller 메서드를 호출하기 위해 필요한 작업을 처리한다.

 

예를 들어

@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id)

 

에서 HTTP 요청의 /users/1에 포함된 1을 Long id에 전달할 수 있도록 처리한 뒤 Controller 메서드를 실행한다.

 

이를 통해 DispatcherServlet은 Controller의 구체적인 호출 방식에 직접 의존하지 않고 HandlerAdapter라는 공통 인터페이스를 통해 Handler를 실행할 수 있다.

6. Controller가 요청을 처리한다.

HandlerAdapter를 통해 Controller 메서드가 호출된다.

@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {

    return userService.getUser(id);
}

 

Controller는 필요한 비즈니스 로직을 Service에 위임하고 결과를 반환한다. 그리고 결과가 다시 Controller까지 반환된다.

7. HttpMessageConverter가 응답을 변환한다.

@RestController에서는 Controller가 Java 객체를 반환하면 해당 객체를 그대로 HTTP로 전송할 수 없다.

 

예를 들어

return new UserResponse(1L, "은콩이");

 

와 같이 Java 객체가 반환되었다고 하자.

 

Spring MVC에서는 HttpMessageConverter를 사용하여 Java 객체를 HTTP Response Body에 사용할 수 있는 형태로 변환한다.

 

일반적인 JSON API에서는 Jackson을 사용하는 JSON 메시지 컨버터를 통해 다음과 같이 변환된다.

Java Object

UserResponse
 ├─ id = 1
 └─ name = "은콩이"

        ↓

HttpMessageConverter

        ↓

JSON

{
    "id": 1,
    "name": "은콩이"
}

 

이 결과가 HTTP Response Body에 담겨 클라이언트에게 전달된다.

8. DispatcherServlet의 역할

Spring MVC 요청 처리 과정에서 가장 중요한 것은 DispatcherServlet이 전체 요청 처리 흐름의 중심에 있다는 것이다.

DispatcherServlet 자체가 모든 작업을 수행하는 것이 아니라 각 역할을 담당하는 컴포넌트에게 작업을 위임한다.

9. 일반 MVC와 REST API의 차이

@Controller를 사용하여 화면을 반환하는 전통적인 Spring MVC에서는 Controller가 View 이름을 반환할 수 있다.

@Controller
public class UserController {

    @GetMapping("/users")
    public String users() {
        return "users";
    }
}

 

이 경우에는 ViewResolver가 View를 찾아 렌더링하는 과정이 포함된다.

 

반면 우리가 Spring Boot 백엔드에서 자주 사용하는 @RestController에서는 주로 객체를 반환하고,

Controller
    ↓
Java Object
    ↓
HttpMessageConverter
    ↓
JSON

 

과 같은 방식으로 응답을 생성한다.

많이 읽은 글