[JPA] 영속성 컨텍스트 메모리, 100만 건 조회
2026. 10. 6.
대량 조회 시 영속성 컨텍스트에 엔티티가 쌓여 힙 부하가 생긴다. 아래 블록은 같은 조건에서 그 부하의 크기, OOM이 나는 조건, 대안이 부하를 얼마나 줄이는지를 보여준다.
새 엔티티를 하나의 영속성 컨텍스트에서 계속 저장하면 트랜잭션이 끝날 때까지 모든 엔티티가 관리 상태로 남는다. 필요한 메모리는 엔티티 상태, 연관관계, 영속성 컨텍스트의 관리 정보에 따라 달라지므로 객체 수만으로 안전한 힙 크기를 정할 수 없고, JVM의 최대 힙이 작으면 OutOfMemoryError가 발생할 수 있다. 최대 힙을 지정하지 않으면 64비트 서버 JVM은 가용 메모리의 1/4을 최대 힙으로 잡는다. 100만 건을 조회하면 영속성 컨텍스트가 엔티티만 보유할 때보다 힙을 더 많이 점유한다.
2026-10-07
- 일반 조회로 100만 건을 읽으면 영속성 컨텍스트가 힙 786MB를 차지했다. 엔티티만 메모리에 올려 둘 때(535MB)의 약 1.5배다.
- 힙을 700MB로 제한하면 OOM이 났고, 900MB에서는 성공했다.
- scroll과 clear, StatelessSession, select sum 집계는 힙 64MB에서도 처리됐고 힙 점유량은 약 15~20MB였다.
측정 상세 펼치기접기
환경
- 프레임워크
- Hibernate ORM 6.4.4.Final
- 데이터베이스
- H2 2.2.224 (임베디드, 파일 DB)
- JVM
- OpenJDK 17.0.18, G1 (기본값)
- 머신
- macOS, 12코어, 메모리 24GiB
- 엔티티
- 필드 12개의 단일 엔티티, 연관관계 없음
- 데이터
- 100만 건
- 측정 방식
- 모드마다 별도 JVM. 로딩이 끝난 뒤 System.gc()를 3회 호출하고, 그때의 사용 힙에서 로딩 전 값을 뺌
결과힙 점유량, 100만 건
100만 건 전량 조회목록으로 전부 메모리에 올림
DTO 목록-Xmx4g
518 MB
엔티티만-Xmx4g
535 MB
읽기 전용 조회-Xmx4g
722 MB
일반 엔티티 조회 (1차 캐시 보관)-Xmx4g
786 MB
100만 건 순회 및 분할 처리Cursor / Scroll / Clear 적용
scroll + 1,000건마다 clear-Xmx64m
15.1 MB
StatelessSession-Xmx64m
15.2 MB
select sum 집계-Xmx64m
18 MB
100만 건 단건 등록 (Insert)persist만 호출
flush 없이 persist-Xmx4g
744 MB
1,000건마다 flush와 clear-Xmx64m
15.2 MB
일반 조회, 힙 한도별 결과
✕ 700MBOOM✓ 900MB성공✓ 1GB성공✓ 1.2GB성공✓ 2GB성공✓ 4GB성공
700MB와 900MB 사이는 측정하지 않았다. 성공한 5회 실행의 힙 점유량은 782.9~786.1MB로 일치했다.
문헌과 대조
문헌
영속성 컨텍스트는 관리 엔티티가 차지하는 메모리의 두 배가 필요하다
Vlad Mihalcea, dirty checking 해부 (원문에 측정 수치 없음)이 측정
건당 786B 대 535B, 약 1.5배
값이 String, BigDecimal, Long인 12필드 엔티티 기준. 다른 필드 구성에서는 측정하지 않았다.측정하지 않은 것과 한계
- 연관관계가 있는 엔티티는 측정하지 않았다.
- Date 같은 가변 타입 필드가 있는 엔티티는 측정하지 않았다.
- 임베디드 H2만 썼다. MySQL, PostgreSQL 같은 네트워크 DB의 드라이버 버퍼는 측정하지 않았다.
- G1 외의 GC는 측정하지 않았다.
- 변경 감지의 CPU 비용은 측정하지 않았다.
- 대부분 모드당 1회 실행이다. 일반 조회만 힙 한도를 바꿔 5회 실행했다.
재현
java -Ddb=<db경로> -Xmx4g -cp <classpath> bench.Seed 1000000
java -Ddb=<db경로> -Xmx4g -cp <classpath> bench.Load managed
java -Ddb=<db경로> -Xmx64m -cp <classpath> bench.Load scrollClear
java -Ddb=<db경로> -Xmx900m -cp <classpath> bench.Ins noclear 1000000출처
Hibernate User Guide, Batching (6.4.4)insert 예제의 OOM 설명, flush와 clear, StatelessSession
Hibernate 포럼, StatefulPersistenceContext 메모리loaded state를 유지해야 한다는 메인테이너 설명