Java 내부 구현 코드에서 final 지역변수 사용

뫄뫄(ahk)·2023년 7월 22일
0

검색했을 때 생각보다 빨리 찾을 수 없었기 때문 나와 같은 의문을 가진 분들을 위해 블로그 글을 작성한다.

Java의 ArrayList 내부 구현 코드을 보고 있던 중 많은 함수 내에서 쓰이는 final 키워드가 눈에 띄었다.

위에서 elementData는 ArrayList내에서 데이터를 저장하는 Object 배열을 참조한다.
clear 함수는 elementData 내에 저장되어있는 모든 변수를 null로 바꾸는 함수다. 그런데 곧장 elementData 변수를 통해 for문 내의 코드를 실행하지 않고 es라는 Object 배열에 대한 final 지역 변수를 새로 선언하고 elementData를 참조한다. 정확하게는 elementData가 참조하는 메모리상의 주소를 받아와서 es에 저장한다. 왜 굳이 이러는걸까?

먼저 GPT에게 왜 지역변수로 elementData이 가리키는 배열에 접근해서 null로 변경하는지 물어봤다. GPT는 동시성 제어와 관련이 있다고 대답하는데.. 그닥 설득력 있지 않아서 final 키워드를 강조해서 다시 물어봤다. 그러자 인스턴스 변수를 final 지역변수로 가리키는 것은 최적화와 관련이 있다는 답을 내놓았다.

final local variable을 사용하는 이유

1. stack 메모리에 저장해서 더 빨리 접근하기위해

컴파일러는 지역변수를 stack 메모리에, 인스턴스 변수를 heap 메모리에 저장한다. stack 메모리는 접근이 비교적 간단하고 빠르고 heap은 보다 크지만 접근시간이 상대적으로 오래걸린다. 그렇기 때문에 인스턴스 변수에 저장된 값을 지역변수가 참조하면 더 빠른 접근이 가능하다.

2. 레지스터를 사용하여 더 빨리 접근하려고

또 컴파일러는 레지스터를 활용해서 성능 최적화를 한다. 레지스터는 CPU 내부에 있는 작고 매우 빠른 메모리이다. Java 컴파일러는 레지스터에는 지역변수 및 임시 값들을 저장하는데, 지역변수가 final이라면 인스턴스 변수에 저장된 값이 레지스터에 직접 저장되어 보다 빠른 접근이 가능하다.(registerization, 레지스터화)

public void exampleMethod(){
    final int localVar = instanceVar; // final 지역변수가 인스턴스 변수를 참조
}

위의 코드를 들어 설명해보자면, 지역변수가 final일 떄, 컴파일러는 지역변수 localVar의 값이 아닌 인스턴스 변수 instanceVarl의 값을 직접 레지스터에 저장한다. 메모리 관련 오버헤드*가 줄어들어 실행 속도가 향상됨.

3. 바이트 코드 명령어가 더 짧아짐

스택오버플로우에서 찾은 또 다른 답변들도 있다. 로컬변수를 로드하는 바이트코드 명령어가 인스턴스 변수의 그것보다 더 짧다고 한다. 로컬 변수는 stack에서 변수의 값을 직접 로드하거나 저장할 수 있는데, 인스턴스 변수는 heap에 저장되어있으니까 JVM에 해당 인스턴스 변수에 접근하기 위해서는 먼저 객체의 참조를 로드해서 주소를 찾아야하기 때문에 바이트코드 명령어가 더 길어지고 복잡해잔다. 이건 이정도만 알아두고, 아키텍쳐를 조금 더 공부한 후 깊게 봐야할 듯하다.

4. 동기화 관련 이유

또 메서드의 실행 중에 다른 스레드에 의해 인스턴스 변수가 변경되더라도 지역 변수가 동일한 값을 유지하기 때문에 동기화되지 않은 메서드 내에서의 의미론적 차이(semantic difference)가 있을 수 있지만 이렇게 구현하는 것이 반드시 thread safe한 메서드는 아니라고 한다. 여기서 의미론적 차이는 코드가 다른 실행환경에서 결과나 동작이 다르게 나타날 수 있다는 의미이다. (GPT가 말한대로 동기화를 위한 목적도 있었다..!)

결론

어쨌든 인스턴스 변수가 아닌 지역변수로 해당 객체에 접근하는 것이 더 빠르기 때문에 이렇게 구현한 것이다.

내 코드에 적용해도 괜찮을까?

답은 '아니요'이다. 나는 지금 간단히 공부를 위해 ArrayList를 스스로 구현해보려고 하는 중이기때문이다. 이 방법은 정말 조금의 성능이라고 향상시키는 것이 중요할 때나 내 코드에서 발생하는 병목 현상을 해결해야할 때가 아니면 사용하지 않는 것이 좋다. 컴파일러가 여태껏 해오던 것처럼 알아서 성능최적화를 해줄것이기 때문에, 내 수준에 아직은 이까지 신경쓰지 않아도 된다.

*오버헤드 : 오버헤드란 프로그램의 실행흐름에서 나타나는 현상중 하나로 예를 들어, 프로그램의 실행흐름 도중에 동떨어진 위치의 코드를 실행시켜야 할 때, 추가적으로 시간,메모리,자원이 사용되는 현상입니다

출처 :
1. https://stackoverflow.com/questions/68043544/is-it-a-good-practice-to-define-final-local-variable-when-i-want-to-use-class-fi
2. https://donggu1105.tistory.com/175
3. 친절한 chat GPT

profile
NONONONONONOYes!

1개의 댓글

comment-user-thumbnail
2023년 7월 22일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

답글 달기