JVM 밑바닥까지 파헤치기 책의 GC와 멀티스레드를 이해한 내용을 정리한 글이며
JVM의 객체 생성에서 메모리 관리를 알아보자 글의 후속편입니다.
- 프로그램 카운터, JVM 스택 영역, 네이티브 메서드 영역은
스레드와 생명 주기를 같이하기에 메모리 할당과 회수는 고민하지 않아도 된다.- 반면에 힙과 메서드 영역은 불확실한게 아주 많다.
이 메모리 영역들의 할당과 회수는 동적으로 이뤄진다.
GC가 힙을 청소하려면 어떤 객체가 살아있고, 어떤 객체가 죽었는지 판단해야 한다.
오늘날 주류 프로그래밍 언어들은 객체 생사 판단에 도달 가능성 분석 알고리즘을 이용한다.
기본 아이디어는 GC루트
라고 하는 루트 객체들을 시작 노드 집합으로 쓰는 것이다.
시작 노드들에서 출발하여 참조하는 다른 객체들로 탐색해 들어간다.
탐색 과정에서 만들어지는 경로를 참조 체인이라 한다.
그리고 GC 루트로부터 도달 불가능한 객체는 더 이상 사용할 수 없는게 확실해진다.
GC 루트로 이용할 수 있는 객체의 대표적인 예
참조
Object obj = new Object()
와 같이 프로그램 코드에서 참조를 할당하는 것도달 불가능
으로 판단한 객체가 반드시 죽는건 아님. 두번의 표시 과정의 유예 단계를 거치는데 이때 빠져 나오지 못한 객체는 회수
ps. finalize()
는 사용하지 말자.
초기 자바에서 C, C++ 개발자들을 쉽게 끌어들이기 위한 기능이지 실행하는 비용이 높고 불확실성이 크다. (JDK 9부터 폐기)
현재 사용 가상 머신들의 GC는 세대 단위 컬렉션 이론에 기초해 설계되어있다.
(대다수 객체는 일찍 죽는다, 살아남은 횟수가 늘어날수록 더 오래 살 가능성이 커진다.)
JDK 8의 기본 가비지 컬렉터인 패러럴 컬렉터와 JDK 9 이후의 기본 가비지 컬렉터인 G1
패러럴 올드 컬렉터
G1 컬렉터
Stop-the-World
시간 사용자 설정 가능, 동시 처리량 증가런타임에는 실행 효율을 높이는 최적화를 JIT 컴파일러가 지속 수행
컴파일타임에는 개발자의 코딩 효율을 높이는 최적화를 프런트엔드 컴파일러가 수행
스레드 안정성을 이해하려면
안전하냐 아니냐
이분법적 사고에서 벗어나야 한다.
final
로 불변성 보장Long
, Double
, BigInteger
, `BigDecimal 등"어떤 런타임 환경에서든 호출자가 추가적인 동기화 조치를 할 필요 없다."
""스레드 안전하다
말할 수준synchronized
가 조건부 스레드 안전이고 절대적 스레드 안전은 아닌 이유
public class Main {
private static Vector<Integer> vector = new Vector<>();
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
}
});
removeThread.start();
printThread.start();
while (Thread.activeCount() > 20);
}
}
}
Vector
는 내부 메서드 하나하나는 synchronized
로 안전하지만, 다수의 메서드가 조합된 연산은 안전하지 않음
synchronized
된 블록에 여러번 다시 진입할 수 있다.concurrent
패키지에서 사용synchronized
와 비슷하지만 진보된 기능 제공ReentrantLock
은 동시에 여러 개의 Condition 객체와 연결 지을 수 있다.