본문 바로가기
Java

[Servlet-jsp] 스택과 힙 메모리 / 멀티쓰레드 상황에서 문제가 발생할 가능성이 있는 Controller의 코드

by bkuk 2023. 3. 31.

인스턴스 생성에 따른 비용 발생

매번 클래스의 인스턴스를 생성할 때는 비용이 발생한다. 인스턴스를 생성하고 더 이상 사용하지 않을 경우 가비지 콜렉션 과정을 통해 메모리에서 해제하는 과정 또한 비용이 발생한다.

따라서 인스턴스를 매번 생성할 필요가 없는 경우 번 인스턴스를 생성하지 않는 것이 성능 측면에서 더 유리하다.

예를 들어, 모델 데이터를 담고있는 DTO(Data Transfer Object)는 클라이언트마다 서로 다른 상태 값을 가진다. 이 경우에는 매 요청마다 인스턴스를 생성해야 한다. 하지만 JdbcTemplate, DAO(Data Access Object), Controller 인스턴스를 매 요청마다 생성해야 할까? 아니다. 인스턴스 하나를 생성한 후 재사용할 수 있다.

서블릿은 서블릿 컨테이너가 시작할 때 인스턴스 하나를 생성한 후 재사용한다.
멀티쓰레드 환경에서 여러명의 사용자가 인스턴스 하나를 재사용 하고 있다.

 

문제가 발생할 가능성이 있는 코드?

멀티 쓰레드 환경에서는 소스코드 구현을 잘못한다면 심각한 버그를 만들어 낼 수 있다.

아래 코드를 보자.

ShowController는 인스턴스 하나를 여러 개의 쓰레드가 공유하고 있다. 위와 같은 코드가 메모리에서 어떻게 동작하는지 생각해보자.

 

스택과 힙 영역?

먼저 JVM은 코드를 실행하기 위해 메모리를 스택과 힙 영역으로 나눠서 관리한다.

  • 스택 영역은 각 메서드가 실행될 때 메서드의 인자, 로컬 변수 등을 관리하는 메모리 영역으로 각 쓰레드 마다 서로 다른 스택 영역을 가진다.
  • 힙 영역은 쓰레드가 서로 공유할 수 있는 영역이다.

 

위 소스코드를 실행한 결과를 메모리의 상태 변화를 통해 확인해보자.

JVM은 각 메서드별로 스택 프레임(Stack Frame)을 생성한다. ShowController의 execute() 메서드를 실행하면 execute() 메서드에 대한 스택 프레임의 로컬 변수 영역의 첫 번째 위치에 자기 자신에 대한 메모리 위치를 가르킨다.

ShowController에 대한 인스턴스는 힙에 생성되어 있으며, ShowController 필드에 Question과 List<answer>를 가지기 때문에 힙에 생성되어 있는 Question과 List<Answer> 인스턴스를 가르키는 구조로 실행된다. 

 

첫 번째 쓰레드(사용자)가 접근하고, 두 번째 쓰레드가 접근한다면?

첫 번째 쓰레드(사용자)가 접근했을 때는 별다른 특이사항이 없다. 문제는 첫번째 쓰레드가 실행되는 과정.. 즉, execute() 메서드가 완료되지 않은 상태에서 두 번째 스레드 요청에 의해 execute() 메서드가 실행되는 경우 발생한다.

두 번째 쓰레드가 실행되면서 ShowController가 가르키는 Question과 List<Answer>가 1번이 아닌 2번으로 바뀌었다. 두 번째 쓰레드는 정상적으로 2번 글에 대한 질문과 답변을 응답으로 받는다. 하지만 첫 번쨰 쓰레드는 1번 글을 보기 위한 요청을 보냈고, 2번 글에 대한 질문과 답변을 응답으로 받게된다.

 

아래 그림을 보면서 상상해보자.

 

전역 변수가 아닌 로컬 변수로 구현하자

위와 같이 execute() 메서드의 로컬 변수로 구현하면 ShowController가 Question과 List<Answer> 인스턴스에 대한 참조를 가지지 않고 ShowController execute() 메서드의 스택 프레임의 로컬 변수 영역에서 인스턴스에 대한 참조를 가진다.

 

로컬 변수로 구현했을 때, 동시 접속 상태

따라서, 2개의 쓰레드가 동시에 접속했을 떄의 상태 변화는 아래와 같다.

댓글