92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

최신소식을
편하게 만나보세요.

JSP 생명주기

도입

JSP의 생명주기는 JSP 파일이 서버에서 Servlet 코드로 변환되고, 컴파일·로딩·초기화·요청 처리·소멸 과정을 거쳐 동적 응답을 생성하는 서버 사이드 실행 흐름이다.

JSP는 HTML 안에 Java 기반의 동적 처리를 섞어 서버에서 응답 페이지를 만들기 위한 기술입니다. 하지만 서버가 JSP 파일을 그대로 실행하는 것은 아닙니다.

JSP 컨테이너는 JSP 파일을 Servlet 형태의 Java 코드로 변환하고, 이를 컴파일한 뒤 Servlet 객체처럼 요청을 처리합니다. 즉 JSP의 실제 서버 사이드 실행 모델은 Servlet 생명주기와 매우 밀접하게 연결됩니다.

그래서 JSP 생명주기를 이해한다는 것은 단순히 jspInit(), _jspService(), jspDestroy() 세 메서드를 외우는 일이 아니라, JSP가 서버 안에서 언제 Servlet으로 바뀌고 언제 재사용되며 언제 제거되는지 이해하는 일에 가깝습니다.

필요성

JSP는 화면 템플릿처럼 보이지만 실제로는 서버에서 Servlet으로 변환되어 실행되므로, 생명주기를 모르면 초기화 위치·요청 처리 위치·자원 해제 위치를 잘못 잡기 쉽다

JSP 코드 안에 작성한 내용은 모두 같은 시점에 실행되지 않습니다. 선언부에 작성한 메서드와 필드는 Servlet 클래스의 멤버가 되고, 스크립틀릿이나 표현식은 요청마다 실행되는 서비스 메서드 쪽으로 들어갑니다.

이 차이를 모르면 한 번만 초기화해야 할 자원을 요청마다 만들거나, 요청별로 달라야 하는 데이터를 인스턴스 필드에 저장해 동시성 문제를 만들 수 있습니다.

즉 JSP 생명주기는 성능 최적화보다 먼저, 서버 사이드 실행 위치와 상태 범위를 정확히 구분하기 위해 필요합니다.

JSP 생명주기를 알아야 하는 이유
  • JSP가 실제로는 Servlet으로 변환되어 실행된다는 점을 이해할 수 있음
  • 최초 요청 시 느릴 수 있는 이유를 번역·컴파일 과정으로 설명할 수 있음
  • jspInit(), _jspService(), jspDestroy() 의 책임을 구분할 수 있음
  • 요청별 상태와 JSP 인스턴스 상태를 섞어 생기는 동시성 문제를 줄일 수 있음

정의

JSP 생명주기는 JSP 소스가 페이지 구현 클래스로 변환되고, Servlet 객체처럼 초기화·서비스·소멸되는 전체 과정을 뜻한다

JSP 컨테이너는 JSP 파일을 읽어 Servlet 기반의 구현 클래스를 생성합니다. 이 과정은 보통 translation, compilation, class loading, instantiation, initialization, request processing, destruction 으로 설명됩니다.

HTTP 기반 JSP 페이지는 HttpJspPage 인터페이스 관점에서 볼 수 있고, 요청 처리는 _jspService(HttpServletRequest request, HttpServletResponse response) 메서드에 대응됩니다.

다만 _jspService() 는 JSP 컨테이너가 JSP 본문으로부터 자동 생성하는 메서드입니다. JSP 작성자는 이 메서드를 직접 정의하지 않아야 하며, 초기화와 종료가 필요할 때는 jspInit(), jspDestroy() 를 사용합니다.

핵심 메시지

"JSP의 본질은 서버가 JSP 파일을 그대로 실행하는 것이 아니라

Servlet 구현 클래스로 변환한 뒤 Servlet 생명주기 위에서 요청을 처리한다는 데 있습니다."

핵심 원리

JSP는 최초 접근 또는 사전 컴파일 시점에 Servlet 코드로 변환되고, 이후 요청은 이미 생성된 JSP Servlet 인스턴스의 _jspService() 를 통해 반복 처리된다

JSP 요청이 들어오면 컨테이너는 해당 JSP가 이미 변환·컴파일되어 있는지 확인합니다. 아직 준비되지 않았거나 JSP가 변경되었다면 컨테이너는 JSP를 Java Servlet 소스로 변환하고 컴파일합니다.

컴파일된 클래스는 로딩되고 인스턴스화됩니다. 그 뒤 초기화 단계에서 jspInit() 이 호출되고, 실제 클라이언트 요청은 _jspService() 로 전달됩니다.

이후 같은 JSP에 대한 요청은 보통 이미 생성된 JSP Servlet 인스턴스를 재사용합니다. 서버가 종료되거나 애플리케이션이 언로드되거나 JSP 구현 클래스가 교체될 때는 jspDestroy() 가 호출될 수 있습니다.

JSP Server-Side Lifecycle
1) Client requests .jsp
2) JSP container checks translation/compilation status
3) JSP is translated into servlet source
4) Servlet source is compiled into class
5) Class is loaded and instantiated
6) jspInit() is invoked once
7) _jspService(request, response) handles each request
8) jspDestroy() is invoked before unload/destruction

생명주기 단계

JSP 생명주기는 번역, 컴파일, 로딩, 인스턴스 생성, 초기화, 요청 처리, 소멸 단계로 나누어 보면 가장 이해하기 쉽다
단계 서버 내부 동작 실무 해석
Translation JSP 파일을 Servlet Java 소스로 변환 JSP 문법 오류나 directive 오류가 드러나는 단계
Compilation 생성된 Java 소스를 class 파일로 컴파일 Java 코드 오류, import 오류, 타입 오류가 드러나는 단계
Class Loading 컴파일된 JSP Servlet 클래스를 로딩 컨테이너 classloader 영역에서 처리됨
Instantiation JSP Servlet 인스턴스 생성 요청마다 새로 만드는 것이 아니라 보통 재사용됨
Initialization jspInit() 호출 페이지 단위 초기화가 필요한 경우 사용
Request Processing _jspService() 호출 각 HTTP 요청마다 실행되는 핵심 처리 단계
Destruction jspDestroy() 호출 자원 정리나 종료 처리에 사용

핵심 메서드

jspInit(), _jspService(), jspDestroy() 는 JSP 생명주기의 핵심 메서드이며, 작성자가 직접 다룰 수 있는 범위와 컨테이너가 생성하는 범위가 다르다
메서드 호출 시점 작성자 정의 가능 여부 실무 해석
jspInit() JSP 페이지 초기화 시점 가능 페이지 단위 자원 초기화에 사용
_jspService() 각 요청 처리 시점 직접 정의하면 안 됨 JSP 본문이 변환되어 들어가는 요청 처리 메서드
jspDestroy() JSP 페이지 소멸 직전 가능 페이지 단위 자원 해제에 사용

핵심은 _jspService() 입니다. 이 메서드는 JSP 페이지의 body에 대응하고, 컨테이너가 자동으로 생성합니다. JSP 작성자는 요청마다 실행될 코드를 JSP 본문에 작성하는 것이지, _jspService() 를 직접 오버라이드하는 방식으로 작성하지 않습니다.

기본 구조

서버 관점에서 JSP 파일은 최종 실행 단위가 아니라, Servlet 구현 클래스를 생성하기 위한 입력 소스에 가깝다
example.jsp
  ↓ translation
Generated Servlet Java Source
  ↓ compilation
Generated Servlet Class
  ↓ loading / instantiation
JSP Servlet Instance
  ↓ initialization
jspInit()
  ↓ request
_jspService(request, response)
  ↓ unload
jspDestroy()

이 구조 때문에 JSP의 첫 요청이 상대적으로 느릴 수 있습니다. 최초 요청 시 번역과 컴파일이 함께 일어날 수 있기 때문입니다. 운영 환경에서는 컨테이너 설정이나 사전 컴파일 전략으로 이 비용을 줄이기도 합니다.

기본 구현

JSP에서 초기화와 종료 로직은 선언부에 jspInit(), jspDestroy() 를 정의해 표현할 수 있지만, 요청 처리 본문은 컨테이너가 _jspService() 로 변환한다
<%!
    public void jspInit() {
        System.out.println("JSP initialized");
    }

    public void jspDestroy() {
        System.out.println("JSP destroyed");
    }
%>

<html>
<body>
    <h1>Hello JSP</h1>
    <p>Request URI: <%= request.getRequestURI() %></p>
</body>
</html>
// 개념적으로 보면 JSP는 이런 Servlet 구조로 변환된다.
// 실제 생성 코드는 컨테이너 구현체마다 다를 수 있다.

public final class example_jsp extends HttpServlet implements HttpJspPage {

    public void jspInit() {
        System.out.println("JSP initialized");
    }

    public void jspDestroy() {
        System.out.println("JSP destroyed");
    }

    public void _jspService(HttpServletRequest request,
                            HttpServletResponse response)
            throws ServletException, IOException {

        JspWriter out = null;

        // JSP의 템플릿 텍스트, 표현식, 스크립틀릿 등이
        // 이 요청 처리 메서드 안으로 변환된다.
    }
}
실전 포인트
jspInit()jspDestroy() 는 JSP 작성자가 선언할 수 있지만, _jspService() 는 컨테이너가 자동 생성합니다. 요청마다 실행되는 코드를 직접 _jspService() 로 만들려고 하지 말고, JSP 본문·태그·EL·JSTL 구조로 작성해야 합니다.

패턴 1. Translation 과 Compilation

JSP의 첫 번째 핵심은 JSP 파일이 Java Servlet 소스로 바뀌고, 다시 class 파일로 컴파일된 뒤에야 실제 요청 처리에 사용된다는 점이다

Translation 단계에서는 JSP 지시자, 선언부, 스크립틀릿, 표현식, 정적 HTML, JSP 태그 등이 Servlet Java 소스 코드로 변환됩니다.

Compilation 단계에서는 생성된 Java 소스가 컴파일됩니다. 이때 Java 문법 오류, import 문제, 타입 문제 등이 발생할 수 있습니다.

즉 JSP 오류는 크게 JSP 문법을 해석하는 translation-time error 와 Java 컴파일 과정에서 나타나는 compile-time error 로 나눠 볼 수 있습니다.

JSP Error 관점
- Translation error
  - 잘못된 directive
  - 잘못된 JSP 문법
  - 태그 라이브러리 해석 문제

- Compilation error
  - Java 문법 오류
  - import 누락
  - 타입 불일치
  - 메서드/필드 참조 오류

패턴 2. 요청 처리는 _jspService 중심

JSP 본문에 있는 대부분의 출력과 동적 표현은 컨테이너가 생성한 _jspService() 안으로 들어가며, 이 메서드는 요청마다 호출된다

JSP에서 HTML 템플릿, EL, 표현식, 스크립틀릿은 결국 요청 처리 코드로 변환됩니다. 따라서 요청마다 달라지는 값은 이 단계에서 계산됩니다.

예를 들어 request, response, session, application, out 같은 implicit object는 _jspService() 안에서 요청 처리에 사용되는 객체로 이해할 수 있습니다.

즉 JSP 화면에서 매번 달라지는 출력은 대부분 _jspService() 실행 결과입니다.

패턴 3. jspInit 과 jspDestroy

jspInit() 은 페이지 인스턴스 초기화, jspDestroy() 는 페이지 인스턴스 소멸 직전 정리에 대응하며, 요청마다 실행되는 메서드가 아니다

jspInit() 은 JSP 페이지가 초기화될 때 호출됩니다. 따라서 요청마다 반복 생성할 필요가 없는 페이지 단위 리소스 초기화가 필요할 때 사용할 수 있습니다.

jspDestroy() 는 JSP 페이지가 소멸되기 직전에 호출됩니다. 파일 핸들, 커넥션, 캐시 등 페이지 인스턴스가 보유한 리소스를 정리할 때 사용할 수 있습니다.

다만 JSP에 직접 리소스 관리 코드를 많이 넣는 설계는 현대 웹 애플리케이션에서는 권장되는 방향이 아닙니다. 보통은 Servlet, Controller, Service, DI 컨테이너 같은 상위 구조에서 자원 수명을 관리하는 편이 더 안전합니다.

패턴 4. 컨테이너 구현체 관점

JSP 생명주기의 표준 흐름은 명세가 정하지만, 실제 번역·컴파일·재컴파일·캐싱 전략은 Tomcat Jasper 같은 컨테이너 구현체 설정의 영향을 받는다

예를 들어 Tomcat은 Jasper JSP 엔진을 사용해 JSP를 처리합니다. 개발 환경에서는 JSP 변경 감지와 재컴파일이 중요하고, 운영 환경에서는 첫 요청 지연을 줄이기 위해 사전 컴파일이나 production 설정을 고려할 수 있습니다.

Tomcat Jasper는 JSP 변경 시 백그라운드 컴파일을 수행하고, 새 JSP 컴파일이 성공하면 기존 컴파일 결과를 대체할 수 있는 방식도 제공합니다. 이런 동작은 JSP 생명주기의 큰 흐름 위에서 구현체가 제공하는 최적화로 볼 수 있습니다.

즉 JSP 생명주기는 표준적으로 이해하되, 실제 운영 문제를 볼 때는 사용하는 컨테이너의 JSP 엔진 설정까지 함께 확인해야 합니다.

핵심 포인트
JSP 생명주기에서 반드시 구분해야 하는 것은 표준 생명주기와 컨테이너 구현 세부사항입니다. translation, compilation, initialization, service, destruction 은 공통 흐름이지만, 언제 재컴파일하고 어디에 생성 파일을 두며 어떻게 캐싱하는지는 컨테이너마다 설정 차이가 있을 수 있습니다.

한계와 주의점

JSP는 요청마다 실행되는 화면 코드와 페이지 인스턴스 상태가 섞이기 쉬우므로, 생명주기를 모르면 동시성·성능·유지보수 문제가 빠르게 생긴다

첫째, JSP 인스턴스는 요청마다 새로 만들어지는 객체가 아닙니다. 따라서 선언부에 둔 필드가 모든 요청에 대해 안전한 요청별 상태처럼 동작한다고 생각하면 위험합니다.

둘째, JSP는 요청 처리 시점에 여러 스레드가 같은 인스턴스를 사용할 수 있습니다. 따라서 인스턴스 필드에 요청별 데이터를 저장하면 race condition이 발생할 수 있습니다.

셋째, JSP에 Java 로직을 많이 넣으면 변환된 Servlet 코드가 복잡해지고, 컴파일 오류를 추적하기도 어려워집니다. 현대적인 구조에서는 JSP는 View 역할에 가깝게 두고, 비즈니스 로직은 Controller나 Service 계층으로 분리하는 편이 안전합니다.

주의해야 할 지점
  • JSP는 요청마다 새 객체로 생성된다고 생각하면 안 됨
  • 선언부 필드는 요청별 안전한 상태 저장소가 아님
  • _jspService() 는 컨테이너가 자동 생성하므로 직접 정의하지 않아야 함
  • JSP 안에 복잡한 Java 로직을 넣으면 변환·컴파일·디버깅이 어려워짐
  • 컨테이너별 JSP 재컴파일 설정과 운영 모드는 별도로 확인해야 함

자주 하는 실수

JSP 생명주기를 어렵게 만드는 가장 흔한 원인은 JSP를 HTML 파일처럼만 보고, 서버에서는 Servlet으로 변환되어 재사용된다는 사실을 놓치는 데 있다
  • JSP 파일이 서버에서 그대로 실행된다고 생각함
  • JSP 요청마다 새로운 JSP 객체가 생성된다고 오해함
  • jspInit() 이 요청마다 호출된다고 생각함
  • _jspService() 를 JSP 작성자가 직접 정의하려고 함
  • 선언부 필드에 사용자별 요청 상태를 저장함
  • JSP 변경 후 재컴파일 여부를 컨테이너 설정과 분리해서 생각하지 않음
  • JSP 안에 DB 연결, 비즈니스 로직, 대량 처리 로직을 직접 넣음

실무 루틴

JSP를 다룰 때는 생명주기 메서드를 직접 쓰기보다, JSP는 View 로 두고 상태와 로직은 request/session/application scope 와 Controller/Service 계층으로 분리하는 습관이 중요하다
  1. JSP는 최종적으로 Servlet으로 변환된다는 점을 전제로 본다.
  2. 요청마다 달라지는 데이터는 request, session, model attribute 같은 요청/세션 범위로 전달한다.
  3. JSP 선언부 필드에 사용자별 상태를 저장하지 않는다.
  4. 초기화가 꼭 필요할 때만 jspInit() 을 검토하고, 가능하면 상위 애플리케이션 구조에서 관리한다.
  5. 종료 처리가 꼭 필요할 때만 jspDestroy() 를 검토하고, 자원 수명은 컨테이너나 프레임워크에 맡기는 편을 우선 고려한다.
  6. 운영 환경에서는 JSP 사전 컴파일, 재컴파일 설정, 생성 소스 위치를 컨테이너 문서 기준으로 확인한다.

디버깅

JSP 문제를 디버깅할 때는 화면 출력만 보지 말고, translation 단계 오류인지 compilation 단계 오류인지 request processing 단계 오류인지 먼저 나눠 보는 편이 빠르다
1
먼저 JSP가 컨테이너에서 번역 단계까지 성공했는지 확인한다.
2
컴파일 오류라면 생성된 Servlet 소스와 원본 JSP 라인 매핑을 함께 확인한다.
3
요청 처리 오류라면 _jspService() 안에서 실행되는 표현식, EL, 태그, 스크립틀릿을 의심한다.
4
수정한 JSP가 반영되지 않으면 컨테이너의 JSP 재컴파일 설정과 캐시된 class 파일을 확인한다.
5
사용자별 값이 섞이면 선언부 필드나 static 필드에 요청 상태를 저장하고 있지 않은지 확인한다.
점검 체크리스트
- translation error 인가
- compilation error 인가
- request processing error 인가
- 생성된 Servlet 소스를 확인했는가
- jspInit / _jspService / jspDestroy 호출 시점을 혼동하지 않았는가
- 선언부 필드에 요청별 상태를 저장하지 않았는가
- 컨테이너의 JSP 재컴파일 설정이 기대와 맞는가

요약

JSP 생명주기의 핵심은 JSP 파일이 서버에서 Servlet 구현 클래스로 변환되고, 초기화는 jspInit(), 요청 처리는 컨테이너 생성 _jspService(), 소멸은 jspDestroy() 를 통해 이루어진다는 데 있다
  • ✅ JSP는 서버에서 Servlet 구현 클래스로 변환되어 실행된다.
  • ✅ JSP 생명주기는 translation, compilation, loading, instantiation, initialization, service, destruction 으로 볼 수 있다.
  • jspInit() 은 JSP 페이지 초기화 시점에 호출된다.
  • _jspService() 는 JSP 본문에 대응하며 컨테이너가 자동 생성한다.
  • ✅ JSP 작성자는 _jspService() 를 직접 정의하면 안 된다.
  • jspDestroy() 는 JSP 페이지 소멸 직전에 호출된다.
  • ✅ JSP 인스턴스는 요청마다 새로 만들어지는 것이 아니므로 인스턴스 필드에 요청별 상태를 저장하면 위험하다.
  • ✅ 실제 재컴파일, 캐싱, 생성 소스 위치는 Tomcat Jasper 같은 컨테이너 구현체 설정의 영향을 받는다.

 

 

728x90