같은 상태(프로퍼티)를 가지는 객체가 자주 사용된다면 이를 매번 생성하여 사용하는 것 보다는 객체 하나를 재사용하는 편이 낫다.
String s = new String("str");
String s = "str";
위 두 s
는 동일한 str
문자열을 가진다. 다만 전자는 매번 str
을 생성하는 반면 후자는 Java의 String pool에 의해 캐싱되고 이를 재사용한다.
위와 비슷하게 Integer
도 자주 사용되는 -128
부터 127
까지의 숫자를 캐싱하고 있다.
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer#valueOf
를 보면 인자 i로 -128에서 127 사이의 숫자가 전달되면 IntegerCache
에 저장된 Integer 객체를 그대로 전달한다.
이는 인스턴스 생성 비용이 비싼 객체일수록 더더욱 활용가치가 높다.
인스턴스 생성 비용이 비싸다는 의미는 인스턴스를 생성하는데 드는 비용이 크다는 의미이다. 즉, 메모리, 디스크 사용랑, 대역폭 등이 높을수록 생성 비용이 비싸다는 이야기를 한다.
보통 Connection
, Pattern
인스턴스들이 비싼 객체로 소개된다.
private static String PHONE_NUMBER = "010-[0-9]{0,4}-[0-9]{0,4}";
public boolean checkPhoneNumber(String phoneNumber) {
return phoneNumber.matches(PHONE_NUMBER);
}
Java의 String에서는 패턴에 맞는 문자열임을 확인할 수 있는 matches
메서드를 제공한다. 다만, 이 메서드는 매번 Pattern
객체를 생성하여 해당 문자열이 패턴에 맞는지 검사한다. 때문에 가끔 한번 사용하는 경우는 문제되지 않지만 성능이 중요한 경우 비싼 Pattern
을 매번 생성하므로 적절치 못하다. 따라서 Pattern
객체를 미리 생성해두고 재사용하는 것이 성능상 유리하다.
private static Pattern PHONE_NUMBER = Pattern.compile("(010)-[0-9]{,4}-[0-9]{,4}");
public boolean checkPhoneNumber(String phoneNumber) {
return PHONE_NUMBER.matcher(phoneNumber).matches();
}
오토박싱이란 기본 타입과 박싱타입 ex. int와 Integer
를 섞어서 쓸 때 자동으로 상호 변환해주는 기능이다.
Integer i = 1;
위와 같이 i
는 Integer
로 박싱타입을 가지지만 대입은 기본 int 타입인 1
을 하고 있다. 이경우 Java는 자동으로 1이라는 int
를 Integer
로 변환한다.
겉으로보면 큰 문제는 없어보이지만 이렇게 타입을 변환하는데에도 조그맣게 비용이 든다.
@Test
void autoBoxing() {
long result = 0;
long start = System.currentTimeMillis();
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
result += Integer.valueOf(i);
}
long end = System.currentTimeMillis();
System.out.println("execute: " + (end - start));
}
간단하게 long
기본 타입 result에 0부터 Integer.MAX_VALUE
까지 수를 더하는 예시이다. 이때 기본타입 int
인 i를 Integer로 박싱하여 더했을때 다음과 같은 결과가 나타난다.
반면 Integer
가 아닌 그냥 int
를 더할때는 다음과 같은 시간이 소요된다.
@Test
void autoBoxing() {
long result = 0;
long start = System.currentTimeMillis();
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
result += i;
}
long end = System.currentTimeMillis();
System.out.println("execute: " + (end - start));
}
이렇게 약 10배정도 차이가 난다. 떄문에 불필요한 박싱 객체 사용보다는 기본 타입을 권장하며 의도치 않은 오토박싱이 일어나지 않도록 주의해야한다.
JVM에서 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 것은 크게 부담이 되지 않는 일이다. 또한 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것은 일반적으로 좋다. 특히 값 객체 VO, value object
로써 객체 자체를 값으로 사용하는 방식은 Thread Safe하므로 멀티스레드 환경에서 안전한 프로그래밍을 할 수 있다.
단순히 객체 생성을 피하고자 객체 풀을 만드는 것 또한 지양해야한다. 객체 풀은 코드를 헷갈리게 만들 뿐만 아니라 메모리 사용량을 늘리고 성능을 떨어뜨릴 수 있다.