본문 바로가기
CS

CS 스터디 (Java) 4 - Autoboxing/Unboxing이 성능에 미치는 영향을 설명하고 이를 최적화할 수 있는 방법을 말하시오.

by gentle-tiger 2025. 5. 14.

Autoboxing/Unboxing이란

Autoboxing/Unboxing이란 기본 타입(primitive type)과 래퍼 클래스(wrapper class) 간의 자동 변환을 의미합니다. Autoboxing은 기본 타입을 래퍼 클래스로 자동 변환하는 것이며, Unboxing은 래퍼 클래스를 다시 기본 타입으로 변환하는 것을 말합니다.

 

 

Autoboxing/Unboxing이 성능에 미치는 영향

Autoboxing은 기본 타입을 래퍼 클래스로 변환할 때 내부적으로 Integer.valueOf() 같은 정적 메서드를 통해 객체가 생성되며, 이 객체는 힙 메모리에 할당되어 GC 부담을 증가시키고, 메모리 할당 비용이 발생합니다 . 다만, -128부터 127 사이의 값은 캐시를 통해 재사용됩니다.

Unboxing의 경우 래퍼 클래스가 null일 경우 primitive로 변환 시 NullPointerException이 발생할 수 있으며, 이는 예외 처리에 대한 부담을 증가시켜 서비스 안정성에 영향을 줄 수 있습니다. 박싱/언박싱이 빈번히 발생하는 경우, 특히 반복문이나 대규모 연산에서 성능 저하를 초래할 수 있습니다.

 

 

최적화 방법

1. Primitive 타입을 직접 사용

- Autoboxing이 필요한 경우가 아니라면, Integer, Double 등의 래퍼 클래스를 지양하고 int, double 등의 primitive 타입을 직접 사용하는 것이 가장 효율적입니다.

// 비효율적
List<Integer> numbers = new ArrayList<>();
// 효율적
int[] numbers = new int[size];

 

 

2. Boxing/Unboxing이 발생하는 위치 최소화

- 변환이 필요하다면, 반복문이나 연산이 집중되는 로직에서 박싱/언박싱이 발생하지 않도록 설계합니다.

- 특히, Stream API를 사용하는 경우에도 mapToInt()와 같은 메서드를 활용하여 primitive stream(IntStream, DoubleStream)을 사용함으로써 Autoboxing을 피할 수 있습니다.

- 이는 내부적으로 원시 타입을 직접 처리하기 때문에, 불필요한 객체 생성과 GC 비용을 줄이는 데 효과적입니다.

// 비효율적
List<Integer> list = ...;
int total = 0;
for (Integer i : list) {
    total += i; // 매 반복마다 unboxing 발생
}

// 효율적 (primitive stream 사용)
int total = list.stream().mapToInt(Integer::intValue).sum();

 

 

 

3. 데이터 구조 선택 주의

- Map<Integer, String> 같은 래퍼 클래스 기반의 컬렉션은 Autoboxing을 강제하며, 이로 인한 오버헤드가 존재합니다.

- 기본(primitive) 타입을 다루는 특화된 구조(예: Trove, FastUtil 등)를 고려하는 것도 방법입니다.

 

 

 

공식 문서 참고

자바 7의 공식 문서에 따르면, Autoboxing과 Unboxing은 성능에 민감한 연산에는 적합하지 않다고 명확히 명시되어 있습니다.

"It is not appropriate to use autoboxing and unboxing for scientific computing, or other performance-sensitive numerical code."
출처: Oracle Java SE 7 Autoboxing Guide

 

 

 

결론 

Autoboxing/Unboxing이란, 기본 타입(primitive type)과 래퍼 클래스(wrapper class) 간의 자동 변환을 의미합니다. Autoboxing은 기본 값을 래퍼 클래스로 변환할 때 발생하며, Unboxing은 래퍼 클래스를 기본 타입으로 변환할 때 발생합니다.

Autoboxing은 내부적으로 Integer.valueOf(x)와 같은 정적 메서드를 호출하여 객체를 생성하는데, 이 객체는 힙 메모리에 할당되므로 GC 부담이 증가하고 메모리 할당 비용이 발생합니다. Unboxing의 경우, 래퍼 클래스는 null 값을 허용하기 때문에 null을 primitive로 변환할 경우 NullPointerException이 발생할 수 있으며, 이는 서비스 안정성에 악영향을 줄 수 있습니다. 특히 박싱/언박싱이 빈번하게 발생하는 경우, 반복문이나 수치 연산 등에서 성능 저하로 이어질 수 있습니다.

최적화 방법으로는,
첫째, 가능한 경우 int, double 등의 primitive 타입을 직접 사용하는 것이 가장 효율적이며,
둘째, 반복문이나 연산이 집중되는 로직에서는 박싱/언박싱이 발생하지 않도록 위치를 최소화해야 하고,
셋째, List<Integer> 또는 Map<Integer, String> 같은 구조보다는 primitive 타입을 다룰 수 있는 특화된 컬렉션(Trove, FastUtil 등)을 고려하는 것이 좋습니다.

특히 Stream API를 사용할 경우에도 mapToInt()와 같은 메서드를 활용하여 IntStream 등 primitive stream을 사용하면, 내부적으로 원시 타입을 직접 처리하므로 Autoboxing으로 인한 성능 저하를 방지할 수 있습니다.