자바 언어·플랫폼 용어 사전
GC·JITThread-Local Allocation Buffer · 포인터 범프 · 할당 속도

TLAB

스레드마다 잘라 주는 힙 조각. 객체 할당이 포인터 하나 증가로 끝나는 이유.

스레드마다 잘라 주는 힙 조각(Thread-Local Allocation Buffer).

문제 — 할당은 공유 자원 경합이다

힙은 모든 스레드가 공유한다 할당할 때마다 "다음 빈 위치" 포인터를 원자적으로 옮겨야 한다면 코어 수만큼 CAS 경합이 생긴다 → 할당이 병목이 된다

해법

Eden 영역을 스레드마다 조각내 미리 나눠 준다 그 안에서는 나 혼자이므로 동기화가 필요 없다

  • 할당 = 포인터를 객체 크기만큼 증가시키고 끝 (bump-the-pointer)
  • 명령 몇 개. C 의 malloc(자유 리스트 탐색) 보다도 빠를 수 있다

"자바는 객체 생성이 느리다" 가 사실이 아닌 이유가 여기다 느린 것은 생성이 아니라 '오래 살아남는 것' 이다

조각이 부족해지면

남은 공간보다 큰 객체 요청 → 두 갈래

  • ① 남은 공간을 버리고 새 TLAB 을 받는다 (낭비 = TLAB refill)
  • ② 큰 객체는 TLAB 밖(공유 Eden) 에 직접 할당한다

JVM 이 스레드별 할당률을 관찰해 크기를 동적으로 조정한다 -XX:+PrintTLAB 으로 낭비율을 볼 수 있다

왜 이 구조가 세대 GC 와 맞물리나

  • 할당이 Eden 끝에서 순차적으로 일어난다 → 새 객체가 메모리상 인접

  • 대부분의 객체는 금방 죽는다 (약한 세대 가설)

  • Young GC 는 살아남은 소수만 복사하고 Eden 전체를 통째로 비운다

    • 죽은 객체를 훑는 비용이 0 이다
  • "할당이 싸고, 죽는 것도 싸다" 는 결론이 여기서 나온다

이스케이프 분석과의 관계

메서드 밖으로 새지 않는 객체는 JIT이 스칼라 치환으로 아예 힙에 만들지 않기도 한다 — 할당 자체가 사라지므로 TLAB 도 쓰지 않는다.

할당률을 어떻게 보나

GC 로그에서 Young GC 간격과 Eden 크기로 계산한다

  • Eden 4GB 가 2초마다 찬다 → 약 2GB/s 할당

할당률이 높다는 것 자체는 문제가 아니다 문제는 '살아남는 비율' 이다

  • 할당률 높음 + 생존율 낮음 → 정상. GC 가 싸다
  • 할당률 높음 + 생존율 높음 → 승격 폭주 → Old 가 차고 Full GC

async-profiler 의 alloc 모드로 어느 코드가 얼마나 할당하는지 볼 수 있다

객체 풀이 대체로 손해인 이유

풀링의 비용

  • 반납 관리 · 동시성 제어 코드
  • 풀에 든 객체는 오래 산다 → Old 로 승격 → GC 가 비싸진다
  • 세대 간 참조가 늘어 카드 테이블 부담

풀링이 여전히 옳은 경우

  • 생성 자체가 비싼 것 (커넥션 · 스레드 · 큰 버퍼)
  • 즉 '메모리' 가 아니라 '외부 자원' 을 감싼 객체

면접 함정

  • "객체 생성은 비싸니 재사용해야 한다" → 짧게 사는 객체는 매우 싸다. 객체 풀이 오히려 손해인 경우가 많다.
  • "TLAB은 스레드 스택" → 힙의 일부다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — GC·JIT는 무대 뒤에서 — 내부와 실무 튜닝