동적 배열과 재할당
자바에서 ArrayList는 대표적인 동적 배열이다. 내부적으로는 일반 배열을 사용하지만, 요소가 추가되거나 제거될 때 필요에 따라 배열의 크기를 자동으로 조절한다. 이러한 특징으로 개발자가 배열의 크기를 정할 필요 없이 유연하게 사용 가능하다는 장점이 있다.
내부적으로는 용량 부족을 감지하고 현재 용량보다 더 큰 (일반적으로 1.5배) 배열을 생성해 모든 요소를 복사한다. 이후 새로운 배열을 참조하도록 변경하는 과정을 거친다.
ArrayList의 경우, 배열의 인덱스는 int 타입으로 관리된다. int 타입의 최댓값은 2^31 - 1이니, 이론적으로는 약 21억 개의 요소를 가질 수 있다고 판단하기 마땅하다. 그러니 실제로 21억 개의 요소를 담아보자.
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 2_100_000_000; i++) {
list.add(i);
}
}
}
간단한 for문으로 21억 개의 Integer 요소를 넣어보았고, 그 결과 아래와 같은 예외가 터진 것을 확인할 수 있었다.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
at java.base/java.util.Arrays.copyOf(Arrays.java:3481)
at java.base/java.util.ArrayList.grow(ArrayList.java:237)
at java.base/java.util.ArrayList.grow(ArrayList.java:244)
at java.base/java.util.ArrayList.add(ArrayList.java:454)
at java.base/java.util.ArrayList.add(ArrayList.java:467)
이는 ArrayList의 이론적인 최대 크기가 int 타입의 최댓값에 의해 제한되나, 실제로는 시스템의 가용 힙 메모리로 인해 그보다 훨씬 작은 크기에서 OutOfMemoryError가 발생하여 사용이 불가능해진 것이라고 볼 수 있다.
"힙 오버플로우의 원인이 정확히 이것이다!"라고 해도 될지는 모르겠지만, 내가 아는 수준에서는 연속된 메모리 공간의 부재가 원인이다. JVM의 힙 영역은 매우 큰 가상 메모리 공간이지만, 실제로 운영체제로부터 할당받는 물리적 메모리나 가상 메모리 블록들은 시간의 흐름에 따라 단편화될 수 있기 때문이다.
여기서 힙 단편화란, 힙 메모리 중간중간 작은 빈 공간들이 생기는 것을 말하는데, 간략히 설명하자면 GC가 더 이상 사용되지 않는 객체들을 제거하면서 생긴 공간들이다. (애플리케이션이 실행되는 동안 수많은 객체가 힙에 생성되고 소멸되는데, 이때 GC는 더 이상 사용되지 않는 객체를 제거한다.)
이게 무슨 말이냐면, 21억 크기의 배열을 요청했을 때 전체 힙에 10GB의 빈 공간이 있다고 해도, 그 10GB는 여러 개의 작은 조각으로 흩어져 있을 수 있기 때문에 8GB짜리 연속된 공간을 찾지 못해 배열 할당에 실패할 수 있다는 이야기다. 이 배열이 할당되기 위해서는 단일하고 연속적인 메모리 블록이 있어야 한다.
물론 이외에도, 물리적/가상 메모리 한계 및 운영체제 오버헤드가 원인이라는 의견도 있다. 아무리 JVM이 힙을 크게 잡아도, 그 '힙'이라는 것은 결국 운영체제가 제공하는 물리적 RAM이나 스왑 공간 위에 존재할 것이기 때문에, 8GB 같은 매우 큰 연속된 메모리 요청은 운영체제 입장에서도 부담이 될 수 있다는 말이다.
JVM 자체의 내부적인 제약으로 실제 int 타입의 최댓값보다 작은 값까지 허용되는 것이란 의견도 존재하나, OutOfMemoryError: Requested array size exceeds VM limit이라는 에러 메시지는 아직까지 직접 본 적이 없기도 하고, 검증되지 않은 영역이라 선뜻 동의하기는 어렵다.
물론 지금까지 말은 이렇게 했지만, 어차피 대부분의 애플리케이션 시나리오에서 21억 개 이상의 요소를 가지는 단일 배열은 있을 리 없다. 이유는 생각보다 간단한데, 그저 실용적이지 않고 비효율적이기 때문이다. 이 정도 규모의 데이터는 스트림 처리, 데이터베이스 저장, 또는 분산 시스템과 같이 다른 방식으로 처리하는 것이 일반적일 테니까.
'MAIN > My Study' 카테고리의 다른 글
| 07. 제네릭의 핵심 개념과 설계 철학 (7) | 2025.07.30 |
|---|---|
| Common vs Global: 패키지 구조 이해하기 (0) | 2025.06.05 |
| .prettierrc (0) | 2025.06.04 |
| Database driver: undefined/unknown (0) | 2025.05.24 |
| Docker Compose에서 MySQL 데이터 영속성 설정하기 (1) | 2025.05.23 |