Java 구현체 캐싱으로 메모리 절약하기 (메모리 아나바다)

🧗🏼탐험가 시은·2023년 9월 3일
0

내 공부

목록 보기
2/2

시작하며

나, 1년차 주니어 백엔드 개발자🧑🏻‍💻. Wrapper Class의 경우 당연히 선언마다 새로운 메모리, 즉 다른 주소값을 할당받기 때문에 단순한 비교 연산자(==) 으로는 원하는 비교를 할 수 없다는 확신에 가득 찼었다.
헌데, 개발을 하던 중, int가 아닌 Integer로 할당되어 있는 변수가 (==) 연산자에 보란듯이 정상작동하고 있는 것이 아닌가..!

// [상황은 대략... 이랬다]
// 퀴즈에 따른 4지선다 정답을 Dto로 받아오는데, 이때 Dto에는
public class requestDto {
	...
    ...
    private Integer answer;
    ...
}

/////////////////
(Service 로직 중)

Integer temp = 3;

if(requestDto.getAnser() == temp){
	"...? 이게 if문이 왜 정상작동하지..?"
}

다른 오류 때문에 파헤쳐본 API에서 이런 뒤통수를 씨게 맞는 상황을 목격하고는 냅다 Integer를 조사해봤다.

위의 상황은 Java 17에서 비롯된 이야기이다.

나의 오류

위에서 말한 대로 우리는 Wrapper 클래스의 비교는 동등연산자 == 이 아닌 .eqauls 를 사용해야 한다고 익히 알고있다. (면접 질문으로도 자주 나오니 익히 외웠을수도 있다. 허허)
물론 이 말이 틀리지 않다. 아주 정상적이고 보통의 경우 아니, 거의 매 순간 Wrapper 클래스는 .equals로 비교하는 것이 바람직하다.
그럼 왜 저런 일이 발생했을까?

Integer 구현부 살펴보기

Integer의 구현부를 살펴보던 중 IntegerCache라는 클래스를 발견했다. Static으로 선언된 cache 클래스는 위 사진 처럼 구현되어 있었다.
대략 설명하자면, low(-128) 부터 high (127) 사이의 숫자는 캐싱된 값을 사용한다.가 되겠다.

엥? 진짜? 라는 생각이 스치면서 바로 코드로 쳐보자.

확인하기

귀신처럼 high (127) 이상의 값의 비교에선 우리에게 익숙한 false를 뱉어낸다.

그럼 왜 Java에서는 이런 방법을 사용했을까?

왜 이렇게 만들었을까?

그 이유는 메모리의 절약 때문이다.
이건 내 뇌피셜이지만, 자주 사용하는 녀석에 대한 캐싱을 통해서 추가적인 메모리 소모를 막으려던게 의도이지 않을까 생각해봤다.

왜냐하면, Java는 처음 실행될 때 JVM이 OS로부터 일부 메모리를 할당받게 된다.

그러면 프로그램이 실행되는 과정에서 한정된 메모리를 최대한 효율적으로 사용하는 것이 즉 쓸데없는 메모리 할당을 막는 것으로 생각할 수 있다.
또 익히 알고 있듯이, 메모리 꽉차서 Garbage collector 돌아가면 이게 또 성능의 저하라고 우리는 면접 준비를 통해 익히 (외우고) 알고 있지 않는가?

예를 들어 생각해보자.

1부터 200까지 숫자가 쓰인 Card 클래스를 한 사람이 3개씩 가져간다고 생각해보자. 총 1000000(백만)명의 사람이 참여한다.
각 숫자가로 선언된 Card 하나를 인스턴스(구현체) 라고 생각했을 때, Card 클래스 내부 캐시를 사용하지 않은 경우에는 모든 인스턴스가 각자의 메모리를 할당받아서 3000000개의 인스턴스가 공간을 할당받는다.

하지만, 127까지 캐싱된 경우, (128 ~ 200) 사이의 숫자를 가진 Card만 새로운 인스턴스를 생성하면 되니, 메모리를 한껏 아낄 수 있다.

간단한 예시지만 코드로 살펴보자.

(좌)측은 new를 통해 반드시 힙 영역에 할당하는 형태이고, 우측은 앞서 설명한 Integer 내부 cache를 활용할 수 있도록 선언한 형태이다.

0~200까지의 숫자를 백만번 선언하는 경우 아래와 같은 메모리 사용량 차이를 볼 수 있었다.

메모리 사용량에 사용된 totalMemory() 는 전체 메모리, freeMemory()는 사용 가능한 메모리이다.
즉 "totalMemory() - freeMemory()는 사용한 메모리를 나타낸다.

느낀점

정말 의도치 않게 확인한 부분을 이렇게 구현부까지 살펴본 경험이 사실 처음이었다.
해당 내용을 통해서 자주 사용되는 녀석들을 미리 캐싱해두고 사용하면 메모리를 아낄 수 있는 이점이 있다고 확실히 느껴졌다.
하지만, 구현체가 단순한 값으로서의 의미를 가질 때 유의미할 것이라 생각되기도 했다. 보통은 하나의 클래스 안에 한개의 필드만 있는 것이 아니니, 고정된 필드로만 구성된 클래스에서 더욱 좋은 효과가 날 수 있겠다 생각되기도 했다.
아직 이런 부분까지 고민하고 프로그래밍 한 경험이 없기에 단순 궁금증 해결이 아닌, 추가로 적용할 수 있는 부분들을 찾아서 포스팅할 수 있었으면 좋겠다.

profile
시은이의 살아남기 시리즈!

0개의 댓글