객체 생성 비용 줄이기 (Object Creation Cost)
면접에서 자주 나오는 질문이 있다.
객체를 많이 생성하면 성능이 나빠지나요?
많은 사람들이
new 많이 하면 메모리 많이 쓰니까 느리다
정도로 답한다.
반은 맞고 반은 틀리다.
실제로 현대 JVM은 객체 생성에 엄청난 최적화가 들어가 있다.
오히려 많은 경우
객체 생성 자체보다
GC
동기화
DB
네트워크
문자열 처리
가 더 큰 병목이 된다.
이번 글에서는
- 객체 생성 비용이 실제로 발생하는 곳
- JVM이 객체 생성을 얼마나 빠르게 처리하는지
- 어떤 경우 객체 생성이 문제가 되는지
- 실무에서 적용 가능한 최적화 방법
을 알아본다.
객체 생성은 생각보다 빠르다
예시
User user = new User();
대부분은
Heap 탐색
↓
빈 공간 찾기
↓
메모리 할당
↓
객체 생성
이라고 생각한다.
하지만 HotSpot JVM은 그렇지 않다.
대부분의 객체는
TLAB
(Thread Local Allocation Buffer)
에서 생성된다.
TLAB란?
각 스레드에게
Heap 일부를 미리 나눠주는 영역이다.
예를 들어
Heap
├── Thread1 전용 영역
├── Thread2 전용 영역
├── Thread3 전용 영역
객체 생성 시
new User()
실제로는
현재 포인터 증가
↓
객체 생성 완료
수준이다.
그림으로 보면
TLAB
[객체][객체][객체][빈공간...]
↑
현재 포인터
객체 생성
포인터 이동
↓
끝
그래서 JVM에서 객체 생성은 생각보다 매우 빠르다.
왜 빠를까?
C 언어의 malloc 같은 경우
빈 공간 탐색
↓
메모리 조각화 확인
↓
할당
이 필요하다.
반면 JVM Young Generation은
연속된 메모리 공간
을 사용한다.
따라서
Pointer Bump Allocation
만 수행한다.
예시
현재 주소
1000
객체 크기
64 byte
생성 후
1064
로 이동
끝.
그래서
new User()
는 생각보다 싸다.
객체 생성보다 비싼 것
실무에서 훨씬 비싼 것들
String 연결
String result = "";
for(int i = 0; i < 10000; i++) {
result += i;
}
매번
String 생성
↓
char[] 생성
↓
복사
발생
실제로는
StringBuilder
사용
동기화
synchronized(lock) {
}
객체 생성보다
락 획득 비용이 더 큰 경우가 많다.
DB 호출
repository.findById(id);
객체 생성
수 ns ~ 수십 ns
DB
수 ms
차이가 수만 배 이상 난다.
객체 생성이 문제되는 경우
그렇다고
new 많이 해도 괜찮다
는 아니다.
문제가 되는 경우가 있다.
초당 수백만 객체 생성
예시
while(true) {
new User();
}
또는
stream
파이프라인에서
map()
filter()
collect()
중간 객체가 엄청 생성되는 경우
결과
Young GC 폭증
GC 압박 증가
예시
for(int i = 0; i < 10000000; i++) {
User user = new User();
}
객체 대부분은
Young Generation
으로 들어간다.
Young 공간이 꽉 차면
Minor GC
발생
계속 반복되면
GC Time 증가
응답 시간 증가
대용량 로그 처리
예시
String log =
"[" + time + "]"
+ userId
+ action;
수천만 건 생성 시
임시 String 객체 폭증
GC 증가
실무에서 꽤 자주 보인다.
Wrapper 객체 남발
예시
Long total = 0L;
for(long i = 0; i < 1000000; i++) {
total += i;
}
내부적으로
Long 객체 생성
반복
실제로는
long total = 0;
사용
객체 재사용은 항상 좋을까?
많은 주니어가 하는 실수
User user = new User();
for(...) {
user.setName(...);
}
객체를 재사용하면 좋아 보인다.
하지만
가독성 하락
user.clear();
user.setXXX();
user.setYYY();
상태 추적 어려움
Thread Safe 문제
싱글톤 객체
@Component
public class UserHolder {
private User user;
}
멀티 스레드 환경
데이터 오염
발생
그래서
객체 재사용
>
객체 생성 감소
가 항상 정답은 아니다.
실무 최적화 우선순위
많은 개발자가
new User()
줄이기에 집착한다.
하지만 실제 성능 개선 순위는
1. DB 호출 감소
2. 네트워크 호출 감소
3. 캐시 적용
4. 문자열 처리 개선
5. GC 감소
6. 객체 생성 감소
정도다.
객체 생성 최적화는
보통 마지막 단계다.
객체 Pool은 언제 사용할까?
과거 Java에서는
Object Pool
을 많이 사용했다.
예시
Connection
생성 비용
매우 비쌈
그래서
HikariCP
같은 Pool 사용
반면
User
같은 일반 객체는
대부분
Pool 사용 안 함
JVM이 더 잘 처리한다.
StringBuilder도 객체 생성 최적화다
예시
String result = "";
for(int i = 0; i < 10000; i++) {
result += i;
}
실제로는
StringBuilder sb =
new StringBuilder();
for(int i = 0; i < 10000; i++) {
sb.append(i);
}
중간 객체 감소
GC 감소
성능 향상
실무 예시
나쁜 코드
public List<UserDto> findUsers() {
List<User> users =
repository.findAll();
return users.stream()
.map(user -> new UserDto(
user.getId(),
user.getName()
))
.collect(Collectors.toList());
}
이 자체는 문제없다.
하지만
100만 건
500만 건
1000만 건
이면?
DTO 객체
수백만 개 생성
메모리 압박
GC 증가
이런 경우는
Paging
또는
Cursor
기반 조회 고려
JVM이 이미 하는 최적화
많은 사람이 모르는 사실
JVM은 객체 생성도 제거한다.
예시
public int calculate() {
Point p = new Point(1, 2);
return p.x + p.y;
}
JIT 최적화 후
객체 생성 제거
가능
이것이
Escape Analysis
다.
다음 글에서 자세히 다룬다.
정리
객체 생성은 생각보다 비싸지 않다.
현대 JVM은
TLAB
Pointer Bump Allocation
JIT 최적화
덕분에 객체를 매우 빠르게 생성한다.
실무에서 진짜 문제는
DB
네트워크
GC
문자열 처리
인 경우가 훨씬 많다.
다만
초당 수백만 객체 생성
대용량 Stream 처리
불필요한 Wrapper 객체
문자열 남발
상황에서는 GC 압박으로 성능 문제가 발생할 수 있다.
따라서
객체 생성 자체를 두려워하지 말고
불필요한 객체 생성을 줄이고
GC를 이해하는 것
이 더 중요하다.
다음 글에서는 Escape Analysis 재방문을 다룬다.
JVM이 어떻게 객체 자체를 생성하지 않는 수준까지 최적화하는지, Scalar Replacement와 Lock Elision까지 포함해서 실제 JIT 관점에서 분석해본다.
'language > java' 카테고리의 다른 글
| GC 튜닝 기초 (왜 대부분의 성능 문제는 GC보다 객체 생성 패턴에서 시작되는가) (0) | 2026.06.29 |
|---|---|
| Escape Analysis 재방문 (Scalar Replacement, Lock Elision) (0) | 2026.06.29 |
| Spring MVC 예외 처리 내부 구조 (0) | 2026.06.24 |
| Annotation 기반 동작 원리 (0) | 2026.06.24 |
| ExecutorService와 @Async (0) | 2026.06.24 |
댓글