자바 메모리 구조

메서드 영역 : 메서드 영역은 프로그램을 실행한는데 공통적으로 필요한 데이터 관리
클래스 정보 : 클래스의 실행 코드, 필드, 메서드와 생성자 코드등 모든 실행 코드가 존재
static : static 변수들 보관
런타임 상수 풀 : 프로그램을 실행하는데 필요한 공통 리터럴 상수 보관
stack : 자바 실행시 하나의 실행 스택이 생성, 각 스택 프레임은 지역변수, 중간 연산결과, 메서드 호출
stack frame : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임. 메서드를 호출할 때마다 하나의 스택 프레임 생성, 종료되면 스택 프레임 제거
Heap 영역 : 객체 인스턴스와 배열이 생성되는 영역. 가비지 컬렉션이 주로 이뤄지는 영역
1 . 스레드 생성 - Thread 상속
public class HelloThread extends Thread {
@Override
public void run() {
//main 스레드는 main 메서드 스택 프레임에 스택을 올리면서 시작
//직접 만드는 스레드는 run 메서드의 스택 프레임을 스택에 올리면서 시작
//main은 단지 지시만 할 뿐
System.out.println(Thread.currentThread().getName() + " : run()");
//currentThread -> 해당 스레드 객체 조회 가ㅡㄴㅇ
//.getName -> 이름 조회
}
}
public class HelloThreadMain {
public static void main(String[] args) {
//main이 실행 중
//Thread-0 : run() -> 실행
System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출
HelloThread helloThread = new HelloThread();
System.out.println("Thread.currentThread().getName()" + " : start() 호출 전");
helloThread.start(); // run 호출 하면 안됨
System.out.println("Thread.currentThread().getName()" + " : start() 호출 후");
System.out.println("Thread.currentThread().getName()" + " : main() end");
}
}


start를 호출 해야 스택 공간에 할당 받고 스레드 작동
스레드에 이름을 주지 않으면 자바는 임의로 스레드 이름 지정
run() 메서드의 스택 프레임을 스택에 올리면서 run() 메서드를 시작
main -> main
직접 만든 Thread -> run

start 호출 -> Thread-0 시작, run 메서드 호출
main -> run을 실행하는 것이 아닌, Thread-0이 run 메서드. 호출
main 스레드는 단지 start() 메서드를 통해 Thread-0 스레드에게 실행을 지시
public class BadThreadMain {
public static void main(String[] args) {
//main이 실행 중
//Thread-0 : run() -> 실행
System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출
HelloThread helloThread = new HelloThread();
System.out.println("Thread.currentThread().getName()" + " : start() 호출 전");
//별도의 스레드가 run을 호출하는 것이 아닌, main 스레드가 run 메서드를 호출
helloThread.run(); // run 호출
System.out.println("Thread.currentThread().getName()" + " : start() 호출 후");
System.out.println("Thread.currentThread().getName()" + " : main() end");
}
}

main 스레드가 HelloThread 인스턴스에 있는 run 이란 메서드 호출

스레드의 start() 메서드는 스레드에 스택 공간을 할당하면서 스레드를 시작하는 아주 특별한 메서드
main 스레드가 아닌 별도의 스레드에서 재정의한 run() 메서드를 실행하려면, 반드시 start() 메서드를 호출
사용자 스레드
프로그램의 주요 작업을 수행
작업이 완료될 때 까지 실행
모든 User 스레드 종료 -> JVM 종료
데몬 스레드
백그라운드에서 보조적인 작업을 수행한다.
모든 user 스레드가 종료되면 데몬 스레드는 자동으로 종료된다.
public class DemonThreadMain {
public static void main(String[] args) {
//main이 실행 중
//Thread-0 : run() -> 실행
System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출
DemonThread demonThread = new DemonThread();
demonThread.setDaemon(true); //데몬 스레드 여부
//false로 변경 -> 10 초 기다리고 종료 함
demonThread.start(); // run 호출 하면 안됨
System.out.println("Thread.currentThread().getName()" + " : main() end");
//프로그램 바로 종료 -> main 스레드가 end까지 실행 -> 자바 종료
//10초 기다리지 않고 main 스레드 종료 해버림
}
//Thread의 보조적 역할
static class DemonThread extends Thread {
@Override
public void run() {
System.out.println("Thread.currentThread().getName()" + " : run() start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread.currentThread().getName()" + " : run() end");
}
}
}
demonThread.setDaemon(true); : 데몬 스레드 여부, start 이전에 결정해야 함, main 스레드 종료시 자바 종료
demonThread.setDaemon(false); : main 스레드 종료 -> user Thread-0이 종료 되기 전까지 자바 종료 안됨
package java.lang;
public interface Runnable {
void run();
}
//실무에서는 이 방법 주로 사용
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : run()");
}
}
public class HelloRunnableMain {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " : main() start");
HelloRunnable helloRunnable = new HelloRunnable();
Thread thread = new Thread(helloRunnable); // 작업을 넣어줄 수 있다.
thread.start();
System.out.println(Thread.currentThread().getName() + " : main() end");
}
}
Runnable 인터페이스 구현 방식 사용 추천
Thread 상속
장점 : 간단한 구현, run 메서드만 재정의 하면 그만
단점 : 상속의 제한, 유연성 부족
Runnable 인터페이스 구현
장점 : 다른 클래스 상속 받아도 구현 문제 없음, 코드의 분리, 여러 스레드가 동일한 Runnable 객체 공유 가능
단점 : 코드 복잡성 약간 증가
public class ManyThreadMainV1 {
public static void main(String[] args) {
log("main() start");
HelloRunnable helloRunnable = new HelloRunnable();
Thread thread1 = new Thread(helloRunnable);
thread1.start();
Thread thread2 = new Thread(helloRunnable);
thread2.start();
Thread thread3 = new Thread(helloRunnable);
thread3.start();
log("main() end");
//실행 순서를 보장 할 수 없음
//스레드 생성할 때 모두 같은 HelloRunnable 인스턴스의 스레드 작업 실행
//HelloRunnable 인스턴스에 있는 run 메서드 실행
}
}

인스턴스 스레드의 실행 작업으로 모든 스레드 전달
public class ManyThreadMainV2 {
public static void main(String[] args) {
log("main() start");
HelloRunnable runnable = new HelloRunnable();
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(runnable);
thread.start();
}
log("main() end");
}
}
public class InnerRunnableMainV1 {
public static void main(String[] args) {
log("main() start");
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
log("main() end");
}
//특정 클래스 안에서만 사용하는 경우 중첩 클래스 사용
static class MyRunnable implements Runnable {
@Override
public void run() {
log("run()");
}
}
}
public class InnerRunnableMainV2 {
public static void main(String[] args) {
log("main() start");
//익명 클래스 사용
Runnable runnable = new Runnable() {
public void run() {
log("run() start");
}
};
Thread thread = new Thread(runnable);
thread.start();
log("main() end");
}
}
public class InnerRunnableMainV3 {
public static void main(String[] args) {
log("main() start");
//익명 클래스 변수 없이 전달
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
log("run()");
}
});
thread.start();
log("main() end");
}
}
public class InnerRunnableMainV4 {
public static void main(String[] args) {
log("main() start");
//람다
Thread thread = new Thread(() ->
log("run()"));
thread.start();
log("main() end");
}
}