yeo72.devlog

반복문(loop) 안에서 객체 생성과 성능 비교 본문

Study/Java

반복문(loop) 안에서 객체 생성과 성능 비교

짱이08 2023. 10. 18. 01:33

프로그램 성능 최적화를 고려할 때, 객체의 생성과 메모리 관리는 핵심적인 주제입니다.

특히 반복 작업에서 빈번한 객체 생성은 메모리 할당과 관리에 부담을 주며,

가비지 컬렉션의 작동으로 프로그램의 성능을 저해할 수 있습니다.

 

루프 내에서 객체가 생성되면 이들은 힙(heap) 영역에 저장됩니다.
그러나 더 이상 참조되지 않는 객체는 가비지 컬렉션의 대상이 됩니다.

 

가비지 컬렉션은 불필요한 객체들을 자동으로 정리하는 과정입니다.
가비지 컬렉션이 발생하면 "stop the world"가 발생하게 되는데

이는 프로그램 성능 하락을 초래합니다.

 

따라서 어떻게 객체를 생성하고 활용하는지는 프로그램의 성능과 안정성에 직접적으로 영향을 미칩니다.

테스트를 통해 루프 안에서 빈 문자열 객체를 생성하고 사용하는것에서 어떤 성능 차이가 있는지 확인해 보겠습니다.

 

public class LoopTest {
    public static void main(String[] args) {
        objectInLoop();
        objectOutLoop();
    }

    public static void objectInLoop() {
        long beforeTime = System.currentTimeMillis();
        for(int i = 0; i < 100000000; i++) {
            String s = new String("");
            System.out.print(s);
        }
        System.out.println();
        long afterTime = System.currentTimeMillis();
        System.out.println(afterTime - beforeTime + "ms");
    }

    public static void objectOutLoop() {
        long beforeTime = System.currentTimeMillis();
        String s = "";
        for(int i = 0; i < 100000000; i++) {
            System.out.print(s);
        }
        System.out.println();
        long afterTime = System.currentTimeMillis();
        System.out.println(afterTime - beforeTime + "ms");
    }
}

결과:

주어진 코드에서 objectInLoop 메서드는 각 루프에서 새로운 빈 문자열 객체를 생성하며

이로 인해 더 많은 시간이 소요됩니다.

 

생성된 인스턴스들은 heap 영역에 저장되고, 변수 s가 빈 문자열 객체를 참조합니다.

그러나 각 반복이 끝날 때마다 변수 s는 더 이상 새로운 객체를 참조하지 않습니다.


이전에 생성된 빈 문자열 객체들은 더 이상 참조되지 않으므로 가비지 컬렉션의 대상이 됩니다.

이러한 이유로 성능을 향상시키기 위해서는 반복문 안에서 불필요한 객체 생성을 피하고,

가능한 경우 이미 존재하는 객체를 재사용하는 것이 좋습니다.

 

이렇게 하면 가비지 컬렉션의 부하를 줄이고 메모리 사용을 최적화할 수 있어서 프로그램의 성능을 향상시킬 수 있습니다.


추가 예제

public class FruitBoxEx3 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        long beforeTime = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++) {
            fruitBox.add(new Fruit());
        }
        System.out.println(Juicer.makeJuice(fruitBox));
        long afterTime = System.currentTimeMillis();
        System.out.println(afterTime-beforeTime);

    }
}
class Juice {
    String name;
    Juice(String name) {this.name = name + "Juice";}
    public String toString() {return name;}
}

class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        String tmp = "";
        for(Fruit f : box.getList())
            tmp += f + " ";
        return new Juice(tmp);
    }

제네릭 예제를 살펴보는데 루프문에서 객체 생성에 대한 예시가 있길래 추가합니다.

 

String tmp = "";
        for(Fruit f : box.getList())
            tmp += f + " ";

맨 아래 Juicer의 코드를 살펴보면

tmp에 box.getList()의 요소를 하나씩 더하고 있는 것을 확인 할 수 있는데


이는 String tmp를 계속 생성하므로써 heap 영역에 계속해서 새로운 tmp가 생성이 되고,

이전의 tmp는 더이상 참조하지 않기 때문에 GC가 관리하게 됩니다.

 

이 코드를 SrtingBuilder로 수정하면 아래와 같습니다.

    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        StringBuilder sb = new StringBuilder();
        for(Fruit f : box.getList())
            sb.append(f).append(" ");
        return new Juice(sb.toString());
    }

이렇게 StringBuilder를 사용하게되면 문자열을 더할때 마다 새로운 객체가 생성되는 것이 아니라

기존의 StringBuilder 객체에 문자열을 더하는 것이기 때문에

GC가 발생하지 않아 메모리 관리에 효율적입니다.

 

그러면 실제로 코드를 돌려서 얼마나 시간이 차이가 나는지 확인해보겠습니다.

  • String 의 경우

 

  • StringBuilder의 경우

'Study > Java' 카테고리의 다른 글

[Java] 메서드 디스패치란?  (0) 2023.11.07
ArrayList와 LinkedList는 언제 쓰면 좋을까?  (0) 2023.11.05
[JAVA] 상속과 포함관계  (0) 2023.09.12
[JAVA]JVM 동작원리  (0) 2023.09.10
[Java]Scanner 와 BufferedReader의 차이  (0) 2023.03.15