본문 바로가기
이펙티브 자바

불필요한 객체 생성을 피하라

by 이상한나라의개발자 2024. 1. 11.

이펙티브 자바의 해당 item은 성능 최적화와 메모리 관리를 강조합니다. 이 원칙은 불필요한 객체 생성을 피함으로써 시스템의 메모리 사용과 성능을 개선하는데 중점을 둡니다.

 

  • 불필요한 객체 생성의 문제 : 매번 필요할 때마다 새로운 객체를 생성하는 것은 메모리 사용을 증가시키고 가비지 컬렉션의 부담을 가중시킬 수 있습니다.
  • 재사용을 통한 최적화 : 변경 불가능한 객체(immutable objects)나 변경 가능성이 없는 객체는 재사용하는 것이 좋습니다. 예를 들어 String 대신 StringBuilder를 사용할 수 있습니다.
  • 팩토리 메소드 활용 : 객체 생성이 필요한 리소스가 큰 경우 "new" 대신 팩토리 메서드를 사용하여 기존 객체를 재사용할 수 있습니다.
  • 경량 객체 사용 : 작은 메모리를 사용하는 경량 객체를 사용하여 리소스를 최소화 합니다.

 

재사용을 통한 최적화

String s = "hello"; // 재사용을 위해 리터럴 사용
String s = new String("hello") // 매번 새로운 객체 사용

 

위 코드를 설명하면 첫번제 String s = "hello" 부분은 s1이 선언되고 hello 라는 리터럴을 참조하면 , 자바 런타임은 힙 메모리 내에 위치한 String Constant Pool에 해당 문자열을 찾습니다. Pool에 해당 리터럴이 없으면 새로 생성되고 이미 존재한다면 해당 참조를 하게 됩니다. 

 

* 리터럴 : 리터럴이란 프로그램에서 직접 표현한 값을 의미합니다. ( 예 : 문자타입 -> char 2바이트, unicode -> char c = \uae00; )

 

예를 들어 아래와 같이 두개의 String 리터럴이 있습니다.

String s1 = "Effective Java";
String s2 = "Effective Java";

 

 

 

 

위 두개의 코드는 s1 의 메모리 참조주소가 0x0x1 이면 s2 의 참조주소도 0x0x1 이 됩니다. 

 

두번째로, new 키워드로 String 객체를 직접 생성하는 경우는 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없는 String 인스턴스가 수백개 수천개 만들어 질 수도 있다. 

 

불변객체(immutable object), 가변객체(mutable object)

 

String 클래스의 내부를 보면 final 클래스로 선언이 되어 있습니다. 이는 한번 생성이 되면 그 상태를 변경할 수 없는 객체를 의미합니다. 

String a = "java";
a = "spring";

 

위와 같이 a 클래스에 문자열을 할당하고 a 값을 변경하게 된다면 해당 참조값에 데이터가 변경되는 것이 아니라 새로운 참조값이 생성되어 할당 되게 되면 문자열 "java" 는 가비지 컬럭터 대상이 되게 됩니다.

 

StringBuilder은 문자열을 변경하거나 추가할 때 새로운 객체를 생성하지 않고, 기존 객체의 내용을 직접 수정할 수 있습니다. 그러므로, 문자열을 자주 변경해야 하는 경우에 성능 이점을 제공합니다.

 

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // "Hello World"
sb.insert(6, "Java "); // "Hello Java World"
sb.delete(6, 11); // "Hello World"
sb.reverse(); // "dlroW olleH"

 

내부적으로 같은 문자 배열을 재사용 함으로써, 새로운 String 객체를 반복적으로 생성하는 오버헤드를 피할 수 있습니다.

 

정적 팩토리 메서드 사용

Boolean value = Boolean.valueOf("true"); // 이미 존재하는 객체를 재사용

 

위 코드는 정적 팩토리 메서드가 객체를 재사용하는 좋은 예 입니다. 이 메서드는 문자열 매개변수를 기반으로 Boolean 객체를 반환 합니다. 그 과정에서 "true" 에 해당하는 Boolean 값을 반환하게 됩니다.

 

내부 소스를 확인해 보면 이미 Boolean TRUE 라는 static 으로 인스턴스가 생성이 되어 있으며, 같은 참조값을 사용하게 됩니다.

 

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }
}

 

 

경량 객체의 사용

경량 객체의 사용은 객체 지향 프로그래밍에서 성능 최적화를 위해 중요합니다. 작은 메모리를 가진 객체를 사용하여 어플리케이션의 전체적인 리소스 사용을 최소화 하는 것 입니다. 경량 객체는 객체를 재사용함으로써 객체 생성과 가비지 컬렐션에 따른 오버헤드를 줄일 수 있습니다. 

 

예시

 

자바의  Warapper 클래스 : Integer, Character 등의 와퍼 클래스는 특정 범위의 값을 캐시하여 재사용 합니다. 예를들어, Integer.valueOf() 메서드는 -128 ~ 127 범위의 Integer 객체를 미리 생성하고 캐시하여 재사용 합니다.

 

String Constant Pool : 자바에서 문자열 리터럴은 해당 pool에 저장되어 재사용 됩니다. 이는 같은 리터럴 문자열이 프로그램 전체에서 단 하나의 String 객체로 관리 되도록 하여 메모리 사용을 최적화 합니다.

 

참고자료 : https://www.geeksforgeeks.org/string-constant-pool-in-java/