본문 바로가기

Java/Java Language

[Java] Process와 Thread

Process

프로세스란?

프로세스를 간단히 말하면 프로그램이 실행된것이다. 프로그램은 실행하기전에는 단순히 파일에 불과하다. 그러나 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아서 프로세스가 된다.

 

운영체제의 각 프로세스는 독립적인 메모리 공간을 갖고 있다. 이렇게 운영체제에 의해 프로세스들은 각기 격리되어 관리되기 때문에 하나의 프로세스가 충돌해도 다른 프로세스에 영향을 끼치지 않는다. 

 

프로세스의 메모리 영역

  • code: 실행할 프로그램의 코드가 저장되는 영역
  • data: static 변수와 global 변수가 저장되는 영역
  • heap: 동적으로 할당되는 영역
  • stack: 메서드 호출시 생성되는 지역변수, 매개변수와 반환주소가 저장되는 영역

프로세스의 역할

  • 프로세스는 메모리 공간, 파일 핸들, 시스템 자원(네트워크, 열은 파일 등)이 포함되어 실행 환경을 제공한다. 이는 컨테이너 역할을 하는 것이다.
  • 프로세스 자체는 스케줄러에 의해 직접 실행되지 않으며, 프로세스 내의 스레드가 실행된다. 실제로 CPU에 의해서 실행되는 단위는 스레드이다.

Thread

스레드란?

프로세스는 프로그램을 수행하는데 필요한 데이터와 메모리 등의 자원 그리고 스레드로 구성되어 있다. 스레드는 프로세스의 자원(코드, 데이터, 시스템 자원)을 사용해서 실제로 작업을 수행하는 실행 단위이다. 그래서 모든 프로세스에는 최소한 하나의 스레드(Single-Thread Process)가 존재하며, 둘 이상의 스레드를 가진 프로세스를 멀티스레드 프로세스(Multi-Thread Process)라고 한다. 

 

메모리상에서 각 스레드는 고유의 stack 영역을 갖으며, 동일한 프로세스 내에서는 code, data, heap을 모든 스레드가 공유한다.스레드는 프로세스보다 단순하므로 생성 및 관리의 비용이 적다. 그래서 스레드를 경량 프로세스(light-weight process)라고도 부른다.

 

스택은 스레드의 실행 컨텍스트를 유지한다. 메서드 호출 시, 스레드는 새로운 스택 프레임을 스택에 푸시(push)한다. 스택 프레임은 스택 내에서 메서드 호출 시 생성되는 데이터 구조로, 메서드 호출 시 필요한 정보(지역 변수, 매개변수, 반환 주소 등)를 포함한다. 메서드가 실행되는 동안 현재 스택 프레임의 정보가 사용된다.

 

코드의 각 명령어는 스택 프레임의 데이터와 공유되는 자원을 참조하여 실행된다. 스레드는 코드를 한줄씩 실행하고 완료되면, 스레드는 현재 스택 프레임을 팝(pop)하여 스택에서 제거한다. 이 작업으로 제어는 메서드 호출 지점으로 돌아가게 된다. 이때, 메서드 호출 시 저장된 반환 주소를 사용하여 원래 위치로 복귀한다.

 

스레드의 역할

디스크의 실행 파일로 존재하던 프로그램을 운영체제가 메모리에 로드하고 프로그램 실행에 필요한 라이브러리를 링킹을 하면서 프로세스로 만든다. 그리고나서 생성된 스레드는 프로세스내의 코드를 한줄씩 코드를 실행시킨다. 그래서 스레드를 작업을 처리하는 'Worker'라고도 부른다.

 

일반적으로 main 메서드부터 시작해서 힌즐씩 내려가면서 코드가 실행된다. 이렇게 프로세스의 코드를 실행하는 흐름을 'Thread '라 한다. Thread는 "실을 꿰다"라는 뜻이며, 코드를 위에서 아래로 하나 하나 꿰면서(실행하면서) 내려가는것을 빈댓것이다.

 

정리하자면 프로세스는 실행 환경과 자원을 제공하는 컨테이너 역할을 하고, 스레드는 CPU를 사용해서 코드를 한줄씩 실행한다.

 

프로세스가 가질 수 있는 스레드 개수는 제한되어 있지 않으나, 스레드가 작업을 수행하는데 개별적인 메모리 공간인 Call Stack이 필요하다. 그러므로 프로세스의 메모리 한계에 따라 생성할 수 있는 스레드의 수가 결정된다. 보통 프로세스의 메모리 한계까지 스레드를 많이 생성하는 것은 없을 것이다.

Java에서 스레드 상태

 

  • NEW: 스레드가 생성되었지만 아직 실행되지 않은 상태이다.
  • RUNNABLE: JVM내의 스레드가 CPU를 할당받을 수 있는 상태로, 실행 대기 중이거나 실제로 실행되고 있는 경우를 모두 포함한다. 
  • BLOCKED: 다른 스레드의 모니터 락을 기다리고 있는 상태이다. synchronized 블록 또는 메서드에 접근하려고 하지만 이미 다른 스레드가 락을 가지고 있어 대기하는 상태이다.
  • WAITING: 다른 스레드에 의해 특정 조건이 충족될 때까지 대기하는 상태입니다. 예를 들어 Object.wait(), Thread.join(), 또는 LockSupport.park() 메서드를 호출했을 때 이 상태가 됩니다.
  • TIMED_WAITING: 일정 시간 동안 대기하는 상태이다. Thread.sleep(milliseconds), Object.wait(milliseconds), Thread.join(milliseconds), 또는 LockSupport.park()와 같은 메서드가 호출될 때 이 상태로 전환된다.
  • TERMINATED: 스레드의 실행이 완료된 상태이다. run() 메서드가 정상적으로 종료되거나 예외가 발생해서 스레드가 종료된 상태이다.

Java에서 스레드 상태의 변환 예시

  • NEW  RUNNABLE: start() 메서드가 호출될 때 (실제 새로운 스레드가 생성되고 스레드의 독립적 작업을 위한 call stack이 생성된다.)
  • RUNNABLE  BLOCKED: 다른 스레드가 락을 소유하고 있을 때
  • RUNNABLE  WAITING: wait(), join(), 또는 park() 메서드를 호출할 때
  • WAITING  RUNNABLE: 대기 중인 조건이 충족되거나 notify() 또는 interrupt() 메서드가 호출될 때
  • RUNNABLE  TERMINATED: run() 메서드가 종료될 때

Java 애플리케이션 프로그램 실행 과정

1. 사용자 명령어 입력

사용자는 커맨드라인에서 자바 애플리케이션을 실행시키도록 java -jar myapp.jar 명령어를 실행시킨다. 이 명령어는 JVM을 통해 JAR 파일에 포함된 자바 애플리케이션 프로그램을 실행하는 명령어이다.

 

2. 운영 체제의 프로세스 생성

운영 체제는 java -jar myapp.jar 명령어를 해석하여 fork  exec 시스템 호출을 사용하여 JDK 포함된 java 프로그램을 실행시켜서 프로세스를 생성한다. 여기서 java 프로세스가 JVM의 모든 구성 요소도 함께 메모리에 로드된다.

  • java 프로그램은 자바 애플리케이션을 실행하기 위해 JVM(Java Virtual Machine)을 시작하고 관리하는 프로그램이다.
  • fork는 운영체제가 새로운 프로세스를 생성하는 시스템 호출이고, exec는 이 프로세스가 실행할 실제 프로그램을 로드하는 시스템 호출이다.

3. JVM에 동적 라이브러리 로드 및 링킹

JVM의 실행에 필요한 핵심 라이브러리들이 로드 및 링킹된다.

리눅스에서 실행된다면, 리눅스의 표준 C 라이브러리(libc.so), 스레딩 라이브러리(libpthread.so) 등과 JVM의 핵심 기능을 제공하는 libjvm.so, 신호 처리를 위한 libjsig.so 등이 로드된다. (JDK_HOME/lib/server경로에 저장되어 있다.)

4. JVM 초기화

  • JVM의 기본 데이터 구조를 초기화 한다. (Runtime Data Area의 heap, stack 크기 등)
  • JVM의 Class Loaders, Garbage Collector, Execution Engine 등을 초기화한다.
  • java.lang.Object, java.lang.String, java.lang.Thread 등의 공통적인 상위 계층 클래스들과 주요 클래스들을 하나의 공유 가능한 파일로 묶어서 로드시킨다. 새로 생성한 클래스는 공유된 클래스 데이터를 참조하여 빠르게 로딩될 수 있다. (Main 클래스 로드 전에 필요)

5. Main 클래스 로드

JVM은 JAR 파일의 메타데이터에 기록된 Main 클래스를 로드한다.

  • -jar myapp.jar 옵션은 JVM에게 JAR 파일을 찾고 JAR 파일의 메인 클래스를 실행하도록 지시한것이다.

6. Main Thread 생성 및 실행

로드한 Main 클래스에서 메인 클래스의 main() 메서드를 호출하여 애플리케이션을 실행시킨다. 메인 스레드가 main() 메서드의 코드를 한줄 한줄 내려가면서 실행시킨다.