ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [자바성능] Collection 메모리 최적화 테스트
    기타 2024. 7. 25. 21:19
    728x90

    회사에서 랜덤 문자열을 생성하는 로직을 작업 중이었다. 이때 생성된 문자열은 DB에는 존재하지 않아야 한다.

    해당 로직을 작성중 의문점이 생겼다. 평소에 함수 내에서 사용하지 않는 컬렉션(List, Set...) 변수를

    사용이 다 했다면, 해당 변수는 메모리 할당 제거하면 효율성이 더 좋아지지 않을까? 라는 의문점이 생겨서 해당 테스트를 진행했다.

    특정 함수 3개를 만들어서 테스트를 진행했다.

    동일 조건: 함수 마다 100만 개의String List를 4개 생성한다. 이중 1개만 반환하도록 한다.

    1번째 함수: 반환하지 않는 함수들의 메모리 할당을 제거하지 않는다.

    2번째 함수: 반환하지 않는 함수들을 clear()한다. (컬렉션에 속해있는 변수들 제거)

    3번째 함수: 반환하지 않는 함수들을 null값으로 반환한다.

    위의 조건을 만족하는 테스트코드는 아래와 같다.

    class MomoryTest {
        private final int size = 1_000_000;
    
        // JVM의 현재 Runtime 인스턴스 얻기
        Runtime runtime = Runtime.getRuntime();
        @Test
        void collectionSetTest() throws InterruptedException {
            runtime.gc();
            Thread.sleep(1000);        // 초기 메모리 사용량 출력
    
    
            System.out.println("===========================");
            // 초기 메모리 사용량 출력
            printMemoryUsage("Initial");
            List<String> strings = notResetList();
            // 초기 메모리 사용량 출력
            printMemoryUsage("notResetList");
            strings.clear();
            // 컬렉션 비운 후 메모리 사용량 출력
            printMemoryUsage("After clearing elements");
    
            // 가비지 컬렉션 실행 요청
            runtime.gc();
    
            // 가비지 컬렉션 후 메모리 사용량 출력
            printMemoryUsage("After GC");
            System.out.println("===========================");
    
        }
    
        @Test
        void collectionSetTest2() throws InterruptedException {
            System.out.println("===========================");
    
            runtime.gc();
            Thread.sleep(1000);        // 초기 메모리 사용량 출력
    
            // 초기 메모리 사용량 출력
            printMemoryUsage("Initial");
            List<String> strings = resetList();
            // 초기 메모리 사용량 출력
            printMemoryUsage("resetList");
            strings.clear();
            // 컬렉션 비운 후 메모리 사용량 출력
            printMemoryUsage("After clearing elements");
    
            // 가비지 컬렉션 실행 요청
            runtime.gc();
    
            // 가비지 컬렉션 후 메모리 사용량 출력
            printMemoryUsage("After GC");
            System.out.println("===========================");
    
        }
    
        @Test
        void collectionSetTest3() throws InterruptedException {
            System.out.println("===========================");
    
            runtime.gc();
            Thread.sleep(1000);        // 초기 메모리 사용량 출력
            printMemoryUsage("Initial");
            List<String> strings = clearList();
            // 초기 메모리 사용량 출력
            printMemoryUsage("clearList");
            strings.clear();
            // 컬렉션 비운 후 메모리 사용량 출력
            printMemoryUsage("After clearing elements");
    
            // 가비지 컬렉션 실행 요청
            runtime.gc();
    
            // 가비지 컬렉션 후 메모리 사용량 출력
            printMemoryUsage("After GC");
            System.out.println("===========================");
    
        }
    
        public List<String> notResetList() {
            List<String> result = new ArrayList<>();
            List<String> result1 = new ArrayList<>();
            List<String> result2 = new ArrayList<>();
            List<String> result3 = new ArrayList<>();
    
            for (int i = 0; i < size; i++) {
                result.add(String.valueOf(i));
                result1.add(String.valueOf(i));
                result2.add(String.valueOf(i));
                result3.add(String.valueOf(i));
            }
    
            return result;
        }
    
    
        public List<String> resetList() {
            List<String> result = new ArrayList<>();
            List<String> result4 = new ArrayList<>();
            List<String> result5 = new ArrayList<>();
            List<String> result6 = new ArrayList<>();
    
            for (int i = 0; i < size; i++) {
                result.add(String.valueOf(i));
                result4.add(String.valueOf(i));
                result5.add(String.valueOf(i));
                result6.add(String.valueOf(i));
            }
            result4 = null;
            result5 = null;
            result6 = null;
    
            return result;
        }
    
        public List<String> clearList() {
            List<String> result = new ArrayList<>();
            List<String> result4 = new ArrayList<>();
            List<String> result5 = new ArrayList<>();
            List<String> result6 = new ArrayList<>();
    
            for (int i = 0; i < size; i++) {
                result.add(String.valueOf(i));
                result4.add(String.valueOf(i));
                result5.add(String.valueOf(i));
                result6.add(String.valueOf(i));
            }
            result4.clear();
            result5.clear();
            result6.clear();
    
            return result;
        }
    
        private static void printMemoryUsage(String phase) {
            Runtime runtime = Runtime.getRuntime();
            long usedMemory = runtime.totalMemory() - runtime.freeMemory();
            System.out.println(phase + " memory usage: " + usedMemory / (1024 * 1024) + " MB");
        }
    }

    해당 테스트를 진행한 결과 확실히 사용하지 않는 컬렉션을 초기화 하니 메모리 소비를 덜한다는 결론이 나왔다.
    String 객체 100만개 기준 약 10mb를 절약할 수 있는 것으로 보였다. 신기한 것은
    반환된 컬렉션을 clear을 했을 때 차이점이 보였다는 것이다. 아직 어떤 원인 때문인지는 파악을 못했다..
    추측해보자면 "함수내에서 생성된 List들이 반환되지 않은건 아닐까?" 라는 생각이 든다.

    더 깊은 고민을 해봐야 하는 것은, 실제 엔티티같이 필드가 많은 클래스들을 갖고 테스트를 하면 얼마나 메모리 소비량이 차이날지
    테스트를 해봐야 할 것 같다.

    이 테스트로써 알게된 것은 지역변수를 사용한 뒤, 할당제거를 한다면 메모리에 이점이 있다는것을 알 수 있었다..

    ===========================
    Initial memory usage: 7 MB
    notResetList memory usage: 259 MB
    After clearing elements memory usage: 260 MB
    After GC memory usage: 7 MB
    ===========================
    ===========================
    Initial memory usage: 3 MB
    resetList memory usage: 235 MB
    After clearing elements memory usage: 236 MB
    After GC memory usage: 7 MB
    ===========================
    ===========================
    Initial memory usage: 2 MB
    clearList memory usage: 235 MB
    After clearing elements memory usage: 236 MB
    After GC memory usage: 7 MB
    ===========================
    728x90
Designed by Tistory.