17. [Java] Thread, JVM, Garbage Collection / 20230508
🧑🏻💻 TIL(Today I Learned)
✔️ Thread, JVM(Java Virtual Machine), Garbage Collection
1. Thread(스레드)
➡️ 프로세스 내에서 실행되는 소스 코드의 실행 흐름
➡️ 데이터와 애플리케이션이 확보한 자원을 활용하여 소스 코드를 실행
📍 프로세스(Process)?
→ 어떤 애플리케이션이 실행되면 운영체제가 해당 애플리케이션에 메모리를 할당해 주며 애플리케이션이 실행되는데 이처럼 실행 중인 애플리케이션을 '프로세스' 라고 부름
→ 데이터, 컴퓨터 자원, 스레드 이 세 가지로 구성됨
🔎 메인 스레드(Main Thread)
➡️ 자바 애플리케이션을 실행하면 가장 먼저 실행되는 메서드는 main() 메서드, 메인 스레드가 main() 메서드의 코드를 처음부터 끝까지 차례대로 실행시키며 코드의 끝을 만나거나 return 문을 만나면 실행 종료
public static void main(String[] args) { // 위에서 아래로 흐름
String data = null;
if(...) {
}
while(...) {
}
System.out.println("...");
}
}
✍🏻 싱글 스레드(Single-Thread)
- 어떤 자바 애플리케이션 소스 코드가 싱글 스레드로 작성되었다면 그 애플리케이션이 실행되어 프로세스가 될 때 오로지 메인 스레드만 가지는 싱글 스레드 프로세스가 됨
- 싱글 스레드에서는 메인 스레드가 종료되면 프로세스도 종료됨
✍🏻 멀티 스레드(Multi-Thread)
➡️ 하나의 애플리케이션 내에서 여러 작업을 동시에 수행하는 멀티 태스킹을 구현하는 데 핵심적인 역할
(ex. 메신저 프로그램 사용 시 보낼 사진을 업로드하면서 동시에 메세지 주고받기 가능)
➡️ 메인 스레드에서 또 다른 스레드를 생성하여 실행시키면 해당 애플리케이션은 멀티 프로세스로 동작하게 됨
➡️ 메인 스레드가 작업 스레드보다 먼저 종료되더라도 실행 중인 스레드가 하나라도 있다면 프로세스는 종료되지 않음
➡️ 하지만, 멀티 스레드는 프로세스 내부에서 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스가 종료되므로 다른 스레드에게 영향을 미침
🔎 스레드의 생성과 실행
➡️ 작업 스레드를 활용한다는 것은 작업 스레드가 수행할 코드를 작성하고 작업 스레드를 생성하여 실행시키는 것을 의미
➡️ 자바는 작업 스레드도 객체로 관리하기 때문에 클래스 필요
➡️ run() 메서드 내부에 스레드가 처리할 작업을 작성
➡️ 생성하고 실행하는 두 가지 방법
✍🏻 Runnable 인터페이스를 구현한 객체에서 run()을 구현하여 스레드를 생성하고 실행하는 방법
(일반적으로 많이 사용 --> 다중 상속 가능)
- 결과는 @ 과 # 이 섞여서 출력됨
→ 메인 스레드와 작업 스레드가 동시에 병렬로 실행되면서 각각 main() 메서드와 run() 메서드 코드 실행시켰기 때문
✍🏻 Thread 클래스를 상속받은 하위 클래스에서 run()을 구현하여 스레드를 생성하고 실행하는 방법
- 첫 번째 방법과는 달리 Thread 클래스를 직접 인스턴스화하지 않음
- 하지만 두 가지 다 작업 스레드를 만들고 run() 메서드에 작성된 코드를 처리하는 동일한 내부 동작 수행
→ 여기서 첫 번째와 두 번째의 run() 은 다르다 첫 번째 run() 은 인터페이스, 두 번째는 Thread 클래스
🔎 스레드의 이름
➡️ 메인 스레드는 "main"이라는 이름 가지며 그 외에 추가로 생성한 스레드는 기본적으로 "Thread-0"이라는 이름 가짐
// Thread 이름 조회하기
public class ThreadExample3 {
public static void main(String[] args) {
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Get Thread Name");
}
});
thread3.start();
System.out.println("thread3.getName() = " + thread3.getName());
// 출력값 : Get Thread Name thread3.getName() = Thread-0
}
}
// Thread 이름 설정하기
public class ThreadExample4 {
public static void main(String[] args) {
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Set And Get Thread Name");
}
});
thread4.start();
System.out.println("thread4.getName() = " + thread4.getName());
thread4.setName("ChangeName!!!");
System.out.println("thread4.getName() = " + thread4.getName());
// 출력 결과
// Set And Get Thread Name
// thread4.getName() = Thread-0
// thread4.getName() = ChangeName!!!
}
}
// Thread 인스턴스의 주소값 얻기
public class ThreadExample5 {
public static void main(String[] args) {
// 두 메서드는 모두 Thread 클래스로부터 인스턴스화된 인스턴스의 메서드이므로 호출할 때 스레드 객체의 참조 필요
// 실행 중인 스레드의 주소값을 사용해야 하는 상황이 발생한다면 Thread 클래스의 정적 메서드인 currentThread() 사용
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()); // main
}
});
thread1.start();
System.out.println(Thread.currentThread().getName()); // Thread-0
}
}
🔎 스레드의 동기화
➡️ 멀티 스레드 프로세스의 경우, 두 스레드가 같은 데이터를 공유하게 되어 문제가 발생할 수 있음
- 임계 영역(Critical section)
: 오로지 하나의 스레드만 코드를 실행할 수 있는 코드 영역 - 락(Lock)
: 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한 의미
➡️ 임계 영역으로 설정된 객체가 다른 스레드에 의해 작업이 이루어지고 있지 않을 때, 임의의 스레드 A 는 해당 객체에 대한 락을 획득하여 임계 영역 내의 코드를 실행할 수 있음
➡️ 스레드 A가 임계 영역 내의 코드를 모두 실행하면 락 반압, 이때 또 다른 스레드 중 하나가 락을 획득하여 임계 영역 내의 코드 실행할 수 있음
➡️ 특정 코드 구간을 임계 영역으로 설정할 때는 synchronized 키워드를 사용
2. JVM(Java Virtual Machine)
➡️ 자바 프로그램을 실행시키는 도구
➡️ 즉, 자바로 작성한 소스 코드를 해석해 실행하는 별도의 프로그램
📍 일반적인 프로그램 실행 과정
1. 프로그램이 실행되기 위해서는 CPU, 메모리, 각종 입출력 장치 등과 같은 컴퓨터 자원을 프로그램이 할당받아야 함
2. 프로그램이 자신이 필요한 컴퓨터 자원을 운영체제에게 주문하면 운영체제는 가용한 자원을 확인하고 프로그램이 실행되는 데에 필요한 컴퓨터 자원을 프로그램에게 할당
→ 이때 프로그램이 운영체제에게 필요한 컴퓨터 자원을 요청하는 방식이 운영체제마다 다름
⭐️ 프로그래밍 언어가 운영체제에 대해 종속성을 가지는 이유!
➡️ 하지만 자바는 JVM을 매개해서 운영체제와 소통 → JVM이 자바와 운영체제 사이의 일종의 통역가 역할
➡️ JVM은 각 운영체제에 맞게 개발되어 있으며 자바 소스 코드를 운영체제에 맞게 변환해 줌(→ 자바가 운영체제로부터 독립적인 이유)
🔎 JVM의 구조
➡️ 자바 소스 코드를 작성하고 실행하면 일어나는 일
- 제일 먼저 컴파일러가 실행되면서 컴파일 진행, 그 결과로 .java 확장자를 가졌던 자바 소스 코드가 .class 확장자를 가진 바이트 코드 파일로 변환
- JVM은 운영체제로부터 소스 코드 실행에 필요한 메모리 할당받음 → Runtime Data Area
- Class Loader가 바이트 코드 파일을 JVM 내부로 불러들여 Runtime Data Area에 넣음
→ 자바 소스코드를 메모리에 로드 - 로드가 완료되면 실행 엔진(Execution Engine)이 Runtime Data Area에 있는 바이트 코드 파일 실행
- 인터프리터를 통해 코드를 한 줄씩 기계어로 번역하고 실행시키기
- JIT Compiler(Just-In-Time)를 통해 바이트 코드 전체를 기계어로 번역하고 실행시키기
📍 JIT Cimpiler?
- JVM에서 바이트 코드를 실행하기 위해서 바이트 코드를 기계어로 변환하는 단계를 하나 더 거쳐야 함
→ 이때 기계어로 번역해 주는 것 JIT Compiler
- 실행 시점 전에 기계어로 변환하는 컴파일러 : 정적 컴파일러(오래 걸리지만 런타임 성능 좋음)
- 실행 중 기계어로 변환하는 컴파일러 : 동적 컴파일러(프로그램 성능 떨어짐)
- 정적 컴파일러와 동적 컴파일러의 한계점을 극복하기 위해 설계된 컴파일러
➡️ 실행 엔진은 기본 적으로 인터프리터 통해 바이트 코드 실행시키다가 특정 바이트 코드가 자주 실행되면 JIT Compiler 통해 실행
➡️ 어떤 바이트 코드가 등장할 때 인터프리터는 해당 바이트 코드를 해석하고 실행하지만 JIT Compiler가 동작하면 한번에 바이트 코드 해석하고 실행해줌
🔎 Stack 과 Heap
➡️ JVM 메모리 구조
[Java] 메모리 사용 영역
🧑🏻💻 자바에서 사용하는 메모리 영역에 대해 간단히 정리 프로그램이 실행되면 JVM은 OS로부터 메모리를 할당받고, 그 메모리를 용도에 따라 여러 영역으로 나누어 관리한다. 🔎 JVM(Java Vir
reeeemind.tistory.com
➡️ JVM에 자바 프로그램이 로드되어 실행될 때 특정 값 및 바이트코드, 객체, 변수 등과 같은 데이터들이 메모리에 저장되어야 함
이러한 정보를 담는 메모리 영역이 Runtime Data Area
✍🏻 Stack
➡️ 일종의 자료구조(※ 자료구조 : 프로그램이 데이터를 저장하는 방식)
➡️ LIFO(Last In First Out) : 마지막에 들어간 데이터가 가장 먼저 나온다
💡즉, 맨 마지막에 들어온 데이터가 가장 먼저 나가는 구조, LIFO는 스택의 데이터 입출력 순서를 나타내는 원칙
✍🏻 Heap
➡️ JVM이 작동되면 영역 자동 설정
➡️ 객체나 인스턴스 변수, 배열이 저장됨
➡️ 객체는 대부부분 일회성이며 메모리에 남아있는 기간이 짧다는 전제로 설계되어 있음
Person person = new Person();
- new Person() 실행되면 Heap 영역에 인스턴스 생성되며 인스턴스가 생성된 위치의 주소값을 person에게 할당해주는데 이 person 은 Strack 영역에 선언된 변수
- 우리가 객체를 다룬다는 것은 Stack 영역에 저장되어 있는 참조 변수를 통해 Heap 영역에 존재하는 객체를 다룬다는 의미
- Heap 영역은 실제 객체의 값이 저장되는 공간
3. Garbage Collection
➡️ 메모리를 자동으로 관리하는 프로세스
➡️ 프로그램에서 더이상 사용하지 않는 객체를 찾아 삭제하거나 제거하여 메모리 확보함
➡️ 아무한테도 참조되지 않고 있지 않은 객체 및 변수들을 검색하여 메모리에서 점유를 해제하며 메모리 공간을 확보하여 효율적으로 메모리를 사용할 수 있게 해줌
➡️ 객체가 얼마나 살아있냐에 따라서 힙 영역 안에서 영역을 나눔 Young/ Old
- Young 영역
: 새롭게 생성된 객체가 할당되는 곳, 많은 객체가 생성되었다 사라짐
이 영역에서 활동하는 가비지 컬렉터 → Minor GC - Old 영역
: Young 영역에서 상태를 유지하고 살아남은 객체들이 복사되는 곳, Young 영역보다 크게 할당되고 크기가 큰 만큼 가비지는 적게 발생
이 영역에서 활동하는 가비지 컬렉터 → Major GC - 기본적으로 가비지 컬렉션이 실행될 때의 단계
- Stop The World
: 가비지 컬렉션을 실행시키기 위해 JVM이 애플리케이션의 실행을 멈추는 작업
가비지 컬렉션이 실행될 때 가비지 컬렉션을 실행하는 스레드를 제외한 모든 스레드의 작업은 중단되고 가비지 정리가 완료되면 재개 - Mark and Sweep
: Mark 는 사용되는 메모리와 사용하지 않는 메모리를 식별하는 작업
Sweap은 Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업(제거)
- Stop The World
들어보긴 했었는데 잘 몰랐던 개념들을 다시 알 수 있는 시간이었다. JVM 구조는 저번에 공부해 놓은 게 있어서 이해가 쉬웠고 스레드와 가비지 컬렉터는 아직은 낯선 것 같다. 그래도 앞에서 람다식이랑 스트림 하는 것보다 재밌었다......😂 오늘의 정리 끝!
'SEB_BE_45 > 공부 정리' 카테고리의 다른 글
[Section2] 01. 자료구조와 알고리즘 기초 - 재귀 (0) | 2023.05.10 |
---|---|
[SEB BE] Section 1 회고 / 20230509 (0) | 2023.05.09 |
16. [Java] Annotation, 람다식, 스트림 / 20230503 (1) | 2023.05.09 |
16. [Java] Annotation, 람다식, 스트림 / 20230503 (0) | 2023.05.08 |
15. [Java] 컬렉션(Collection) 2 / 20230502 (0) | 2023.05.03 |