Java/JVM
[JVM 밑바닥까지 파헤치기] 12-2. 자바 메모리 모델과 스레드: 자바 메모리 모델
noahkim_
2024. 12. 26. 00:08
저우즈밍 님의 "JVM 밑바닥까지 파헤치기" 책을 정리한 포스팅 입니다
1. 자바 메모리 모델
- 자바 코드와 실제 메모리/CPU 사이의 추상화 규칙 집합
- ✅ OS/하드웨어에 상관없이 동일하게 동작하기 위한 명세
- ⚠️ 각 CPU는 캐시 구조가 다르고 메모리 일관성 모델이 다름
- ⚠️ 같은 자바 코드이더라도 CPU/OS에 따라 다르게 동작할 수 있음
- ➡️ 플랫폼에 상관없이 메모리를 일관된 방식으로 동작하도록 함 (가시성, 원자성, 순서 보장)
2. 메모리 상호작용
메모리 구조
| 항목 | 메인 메모리 |
작업 메모리
|
| 정의 | 모든 공유 변수가 저장되는 공간 |
각 스레드가 가지는 고유한 메모리 공간
|
| 접근 가능성 | 모든 스레드가 접근 가능 |
해당 스레드만 접근 가능
|
| 역할 | 프로그램 전역 상태 저장 |
메인 메모리의 복사본으로 작업 수행
|
| 데이터 이동 | read/write 명령어로 작업 메모리와 상호작용 |
load/store 명령어로 메인 메모리와 동기화
|
| 특성 | 데이터 일관성을 위한 기준점 |
계산은 여기서 수행되고, 결과는 메인 메모리에 반영됨
|
프로토콜
- 모든 공유 변수는 메인 메모리에 존재
- 각 스레드는 자기만의 작업 메모리를 가짐
- 직접 메인 메모리를 쓰지 않고 복사본으로 작업함
- ➡️ 메인 메모리와 작업 메모리 간 상호작용을 위해 프로토콜이 정의됨
동작 순서) 프로토콜
더보기
- read: 메인 메모리에서 값 읽기
- load: 읽은 값을 작업 메모리에 올림
- use: 연산 수행
- assign: 작업 메모리에 값 변경
- store: 메인 메모리에 쓸 준비
- write: 메인 메모리에 실제 반영
Volatile
- JVM이 제공하는 가장 가벼운 동기화 매커니즘
- ✅ 가시성과 순서 보장을 제공함 (락 사용 안함)
- ❌ 원자성 보장 안됨 (자바 명령어의 원자성 보장 안됨)
코드) 가시성
더보기
static boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
while (!flag) {
// 무한 루프 가능
}
System.out.println("끝");
}).start();
flag = true;
}
- 다른 스레드에 의해 flag가 true로 변경됨
- ✅ 내 스레드는 자기 CPU 캐시값만 볼 수 있음
- ⚠️ 무한 루프 가능
- ➡️ 쓰기 시 다른 코어 캐시 무효화하고 읽기 시 최신 값을 읽도록 함
코드) 순서 보장
더보기
private static Singleton instance;
static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
- CPU/JIT는 성능을 위해 명령어 순서를 변경할 수 있음
- ✅ 원래 순서: 메모리 할당 → 생성자 호출 → instance 참조 저장
- ✅ 정렬된 순서: 메모리 할당 → instance 참조 저장 → 생성자 호출
- ⚠️ 다른 스레드가 초기화 안된 객체를 볼 수 있음
- ➡️ 해당 변수 앞 뒤에 메모리 배리어를 삽입하여 재정렬 방지함
선행 발생 규칙
- 스레드간의 동작 순서와 메모리 가시성을 정의하는 원칙
- A가 B보다 먼저 발생하면, A의 변경은 B에게 반드시 보임
| 규칙 | 설명 |
| 프로그램 순서 규칙 |
하나의 스레드 안에서는 코드가 작성된 순서대로 실행됨
|
| 모니터 락 규칙 |
락 해제 전에 쓴 값은 다음 락 획득 스레드에게 반드시 보임
|
| volatile 변수 규칙 |
volatile write 이전의 모든 작업은 read 이후에 반드시 보임
|
| 스레드 시작 규칙 |
Thread.start() 이전의 작업은 새 스레드 내부에서 항상 보임
|
| 스레드 종료 규칙 |
Thread.join() 이후 작업은 내부 모든 작업 이후에 실행됨
|