Java에서 final for 변수를 사용하면 가비지 수집이 향상됩니까?


86

오늘 제 동료들과 저는 final가비지 콜렉션을 개선하기 위해 Java 에서 키워드를 사용하는 것에 대해 논의 했습니다.

예를 들어, 다음과 같은 메소드를 작성하는 경우 :

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

메서드에서 변수를 선언하면 final가비지 컬렉션이 메서드가 종료 된 후 메서드에서 사용되지 않는 변수에서 메모리를 정리하는 데 도움이됩니다.

이것이 사실입니까?


거기에 가지 사실은, 여기. 1) 메소드 로컬 필드 인스턴스에 쓸 때 . 인스턴스에 쓸 때 이점 이있을 있습니다.
유진

답변:


86

다음은 최종 값 유형 지역 변수가 아닌 최종 참조 유형 필드가있는 약간 다른 예입니다.

public class MyClass {

   public final MyOtherObject obj;

}

MyClass의 인스턴스를 만들 때마다 MyOtherObject 인스턴스에 대한 나가는 참조를 만들고 GC는 라이브 개체를 찾기 위해 해당 링크를 따라야합니다.

JVM은 마크 스윕 GC 알고리즘을 사용하는데, 이는 GC "루트"위치 (현재 호출 스택의 모든 객체와 같이)에있는 모든 라이브 참조를 검사해야합니다. 각 라이브 개체는 살아있는 것으로 "표시"되고 라이브 개체가 참조하는 모든 개체도 살아있는 것으로 표시됩니다.

표시 단계가 완료된 후 GC는 힙을 스윕하여 표시되지 않은 모든 개체에 대한 메모리를 해제하고 나머지 활성 개체에 대한 메모리를 압축합니다.

또한 Java 힙 메모리가 "젊은 세대"와 "이전 세대"로 분할된다는 사실을 인식하는 것이 중요합니다. 모든 개체는 초기에 젊은 세대 ( "보육원"이라고도 함)에 할당됩니다. 대부분의 개체는 수명이 짧기 때문에 GC는 젊은 세대의 최근 쓰레기를 제거하는 데 더 적극적입니다. 개체가 젊은 세대의 수집주기에서 살아남는 경우에는 이전 세대 ( "장기 세대"라고도 함)로 이동하여 덜 자주 처리됩니다.

그래서 머리 위에서 "아니오, '최종'수정자는 GC가 작업량을 줄이는 데 도움이되지 않는다"고 말할 것입니다.

제 생각에는 Java에서 메모리 관리를 최적화하는 가장 좋은 전략은 가능한 한 빨리 가짜 참조를 제거하는 것입니다. 사용을 마치 자마자 개체 참조에 "null"을 할당하면됩니다.

또는 더 좋은 방법은 각 선언 범위의 크기를 최소화하는 것입니다. 예를 들어 1000 행 메서드의 시작 부분에 개체를 선언하고 해당 메서드의 범위가 닫힐 때까지 (마지막 닫는 중괄호) 개체가 살아있는 경우 개체는 실제보다 훨씬 더 오래 살아있을 수 있습니다. 필요한.

12 줄 정도의 코드 만있는 작은 메서드를 사용하는 경우 해당 메서드 내에서 선언 된 개체가 더 빨리 범위를 벗어나고 GC는 훨씬 더 효율적인 작업을 수행 할 수 있습니다. 젊은 세대. 절대적으로 필요한 경우가 아니면 개체가 이전 세대로 이동되는 것을 원하지 않습니다.


생각할 거리. 나는 항상 인라인 코드가 더 빠르다고 생각했지만 jvm이 메모리가 부족하면 느려질 것입니다. hmmmmm ...
WolfmanDragon

1
나는 여기서 추측하고 있지만 JIT 컴파일러가 적당한 성능 향상을 위해 최종 원시 값 (객체가 아님)을 인라인 할 수 있다고 가정합니다. 반면에 코드 인라인은 상당한 최적화를 생성 할 수 있지만 최종 변수와 관련이 없습니다.
benjismith

2
이미 생성 된 최종 객체에 null을 할당하는 것은 불가능합니다. 그러면 아마도 help 대신 final이 될 것입니다. 일을 더 어렵게 만들 수 있습니다.
Hernán Eche 2011

1
다른 클래스 메서드와 관련이 없을 수있는 여러 개인 메서드로 분할하는 대신 {}를 사용하여 큰 메서드의 범위를 제한 할 수도 있습니다.
mmm

메모리 가비지 수집을 향상시키고 참조 관계를 줄이기 위해 가능한 경우 필드 대신 지역 변수를 사용할 수도 있습니다.
sivi

37

지역 변수를 선언 final해도 가비지 수집에는 영향을주지 않으며 변수를 수정할 수 없다는 의미 일뿐입니다. totalWeight표시된 변수 를 수정할 때 위의 예를 컴파일해서는 안됩니다 final. 반면에 프리미티브 ( double대신 Double)를 선언 final하면 해당 변수가 호출 코드에 인라인 될 수 있으므로 메모리와 성능이 향상 될 수 있습니다. public static final Strings클래스에 숫자가있을 때 사용됩니다 .

일반적으로 컴파일러와 런타임은 가능한 경우 최적화합니다. 코드를 적절하게 작성하고 너무 까다롭게하지 않는 것이 가장 좋습니다. final변수를 수정하지 않으려는 경우 사용 합니다. 쉬운 최적화가 컴파일러에 의해 수행 될 것이라고 가정하고 성능이나 메모리 사용이 걱정된다면 프로파일 러를 사용하여 실제 문제를 결정하십시오.


26

아니요, 그것은 사실이 아닙니다.

그 기억 final하지 평균 상수를하지, 그냥 당신이 참조를 변경할 수 없음을 의미합니다.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

JVM이 참조를 수정할 필요가 없다는 지식을 바탕으로 약간의 최적화가있을 수 있지만 (예 : 변경되었는지 확인하지 않음) 걱정할 필요가 없을 정도로 사소합니다.

Final 컴파일러 최적화가 아니라 개발자에게 유용한 메타 데이터로 간주되어야합니다.


17

정리해야 할 몇 가지 사항 :

  • 참조를 무효화하면 GC에 도움이되지 않습니다. 그럴 경우 변수 범위가 초과되었음을 나타냅니다. 한 가지 예외는 객체 네 포티 즘의 경우입니다.

  • Java에는 아직 스택에 할당이 없습니다.

  • 변수를 final로 선언하면 해당 변수에 새 값을 할당 할 수 없습니다 (정상 조건에서). final은 범위에 대해 아무것도 말하지 않기 때문에 GC에 미치는 영향에 대해서는 말하지 않습니다.


자바 (힙 개체에 원시와 참조)에 스택 할당이있다 : stackoverflow.com/a/8061692/32453 자바뿐만 아니라 마지막으로 익명의 클래스 / 람다와 같은 폐쇄에서 개체를 필요로하지만, 회전 즉 줄이기 위해 그냥 밖으로 / 혼란을 줄일 수 있도록 자사가 수집 관련이 ... "메모리 / 프레임 필요"
rogerdpack을

11

글쎄, 나는이 경우 "최종"수정 자의 사용이나 GC에 미치는 영향에 대해 모른다.

하지만 수 있습니다 당신이 말할 : 박스형의 사용은 프리미티브 (예를 들어, 대신 더블 더블)는 힙이 아니라 스택에 해당 객체를 할당하며, GC가 청소해야 할 것입니다 불필요한 쓰레기를 생산하는 것보다는 값.

기존 API에서 필요로하거나 nullable primative가 필요할 때만 boxed primitive를 사용합니다.


1
당신이 옳습니다. 내 질문을 설명하는 간단한 예만 필요했습니다.
Goran Martinic

5

최종 변수는 초기 할당 후에는 변경할 수 없습니다 (컴파일러에 의해 적용됨).

이것은 가비지 콜렉션 의 동작을 변경하지 않습니다 . 유일한 것은 이러한 변수가 더 이상 사용되지 않을 때 null이 될 수 없다는 것입니다 (메모리 부족 상황에서 가비지 수집에 도움이 될 수 있음).

최종적으로 컴파일러가 최적화 할 항목에 대한 가정을 할 수 있다는 것을 알아야합니다. 코드를 인라인하고 연결할 수없는 것으로 알려진 코드를 포함하지 않습니다.

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

println은 바이트 코드에 포함되지 않습니다.


@Eugene 보안 관리자와 컴파일러가 변수를 인라인했는지 여부에 따라 다릅니다.
Thorbjørn Ravn Andersen

맞아요, 난 그냥 현학적 이었죠. 더 이상은 없습니다. 또한 너무 대답을 제공
유진

4

세대 별 가비지 컬렉터에는 잘 알려지지 않은 코너 케이스가 있습니다. (간단한 설명은 benjismith 의 답변을 읽으십시오. 읽고 더 깊은 통찰력 려면 마지막 기사를 읽으십시오).

세대 별 GC의 아이디어는 대부분의 경우 젊은 세대 만 고려해야한다는 것입니다. 루트 위치에서 참조를 검색 한 다음 젊은 세대 개체를 검색합니다. 이보다 빈번한 스윕 동안 이전 세대의 개체는 확인되지 않습니다.

이제 문제는 객체가 더 젊은 객체에 대한 참조를 가질 수 없다는 사실에서 비롯됩니다. 수명이 긴 (이전 세대) 개체가 새 개체에 대한 참조를 가져 오면 해당 참조는 가비지 수집기에 의해 명시 적으로 추적되어야합니다 (IBM의 기사 참조). 핫스팟 JVM 수집기 ) 실제로 GC 성능에 영향을 미칩니다.

이전 객체가 더 젊은 객체를 참조 할 수없는 이유는 이전 객체가 부 컬렉션에서 확인되지 않기 때문에 객체에 대한 유일한 참조가 이전 객체에 유지되면 표시되지 않고 잘못 표시되기 때문입니다. 스윕 단계에서 할당이 취소되었습니다.

물론 많은 사람들이 지적했듯이 final 키워드는 가비지 수집기에 실제로 영향을 미치지는 않지만이 개체가 부 컬렉션에서 살아남아 이전 힙으로 만들면 참조가 더 젊은 개체로 변경되지 않을 것임을 보장합니다.

조항:

가비지 수집에 관한 IBM : 히스토리 , 핫스팟 JVM성능 . 이는 2003/04 년으로 거슬러 올라 가기 때문에 더 이상 완전히 유효하지 않을 수 있지만 GC에 대해 읽기 쉬운 통찰력을 제공합니다.

Sun on Tuning 가비지 콜렉션


3

GC는 연결할 수없는 참조에 대해 작동합니다. 이것은 단지 일회성 할당의 주장 인 "최종"과는 관련이 없습니다. 일부 VM의 GC가 "최종"을 사용할 수 있습니까? 어떻게, 왜 그런지 모르겠습니다.


3

final로컬 변수 및 매개 변수에 대해 생성 된 클래스 파일에 차이가 없으므로 런타임 성능에 영향을 미치지 않습니다. 클래스에 하위 클래스가없는 경우 HotSpot은 해당 클래스를 최종 클래스 인 것처럼 처리합니다 (이 가정을 위반하는 클래스가로드되면 나중에 실행 취소 할 수 있음). 나는 final메소드가 클래스와 거의 같다고 믿습니다 . final정적 필드에서 변수를 "컴파일 시간 상수"로 해석하고이를 기반으로 javac에서 최적화를 수행 할 수 있습니다. finalon 필드를 사용하면 JVM이 사전 발생 관계 를 무시할 수 있습니다 .


2

방황하는 추측이 많은 답변이있는 것 같습니다. 진실은 바이트 코드 수준에서 지역 변수에 대한 최종 수정자가 없다는 것입니다. 가상 머신은 로컬 변수가 최종적으로 정의되었는지 여부를 결코 알 수 없습니다.

귀하의 질문에 대한 대답은 단호한 아니오입니다.


사실 일 수 있지만 컴파일러는 데이터 흐름 분석 중에 최종 정보를 계속 사용할 수 있습니다.

@WernerVanBelle 컴파일러 는 변수가 한 번만 설정되었음을 이미 알고 있습니다. 변수가 null 일 수 있는지, 사용 전에 초기화되지 않았는지 등을 알기 위해 이미 데이터 흐름 분석을 수행해야합니다. 따라서 로컬 파이널은 컴파일러에 새로운 정보를 제공하지 않습니다.
Matt Quigley 2014

그렇지 않습니다. 데이터 흐름 분석은 많은 것을 추론 할 수 있지만 지역 변수를 설정하거나 설정하지 않을 블록 내부에 완전한 프로그램을 포함 할 수 있습니다. 컴파일러는 변수가 작성 될지 여부를 미리 알 수 없으며 전혀 일정하지 않습니다. 따라서 컴파일러는 final 키워드가 없으면 변수가 final인지 여부를 보장 할 수 없습니다.

@WernerVanBelle 정말 흥미 롭습니다. 예를 들어 주실 수 있나요? 컴파일러가 알지 못하는 비 최종 변수에 대한 최종 할당이 어떻게 될 수 있는지 모르겠습니다. 컴파일러는 초기화되지 않은 변수가 있으면 사용할 수 없다는 것을 알고 있습니다. 루프 내부에 최종 변수를 할당하려고하면 컴파일러가 허용하지 않습니다. final로 선언 할 수 있지만 NOT이 아닌 변수가 최종 변수임을 컴파일러가 보장 할 수없는 예는 무엇입니까? 나는 어떤 예도 처음에 최종적으로 선언 될 수없는 변수 일 것이라고 생각한다.
Matt Quigley

아, 귀하의 의견을 다시 읽은 후 귀하의 예제가 조건부 블록 내에서 초기화 된 변수임을 확인했습니다. 모든 조건 경로가 변수를 한 번 초기화하지 않는 한 이러한 변수는 처음에 최종적 일 수 없습니다. 따라서 컴파일러는 이러한 선언에 대해 알고 있습니다. 이렇게하면 두 개의 다른 위치에서 초기화 된 최종 변수를 컴파일 할 수 있습니다 (생각해보십시오 final int x; if (cond) x=1; else x=2;). 따라서 final 키워드가없는 컴파일러는 변수가 final인지 여부를 보장 할 수 있습니다.
Matt Quigley

1

모든 메소드와 변수는 서브 클래스에서 기본적으로 오버라이드 할 수 있습니다. 슈퍼 클래스의 멤버를 오버라이드하여 서브 클래스를 저장하려면 final 키워드를 사용하여 final로 선언 할 수 있습니다. 예를 들어 final int a=10; final void display(){......} , 메소드를 final로 만들면 수퍼 클래스에 정의 된 기능이 어쨌든 변경되지 않습니다. 마찬가지로 최종 변수의 값은 변경할 수 없습니다. 최종 변수는 클래스 변수처럼 작동합니다.


1

인스턴스 필드 에 대해 엄밀히 말하면 특정 GC가이를 악용하려는 경우 성능이 약간 향상 final 될 수 있습니다 . 동시 GC발생 (즉, 애플리케이션이 여전히 실행 중이고 GC가 진행 중임 을 의미 함)이 발생 하면 자세한 설명을 보려면 여기를 참조하십시오. 쓰기 및 / 또는 읽기가 완료 될 때 GC는 특정 장벽을 사용해야합니다. 제가 제공 한 링크는 그 사실을 거의 설명하지만, 정말 짧게 만들기 위해 a GC가 동시 작업을 수행 할 때 (GC가 진행되는 동안) 힙에 대한 모든 읽기 및 쓰기가 "차단"되어 나중에 적용됩니다. 동시 GC 단계에서 작업을 완료 할 수 있습니다.

에 대한 final 들어 필드는 수정할 수 없으므로 (반사하지 않는 한) 이러한 장벽을 생략 할 수 있습니다. 그리고 이것은 단순한 이론이 아닙니다.

Shenandoah GC을 가지고 실제로 (하지만 하지 오래 ), 당신은 예를 들어, 할 수 있습니다 :

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

그리고 GC 알고리즘에 최적화 가있어 약간 더 빨라질 것입니다. final아무도 그들을 수정해서는 안되기 때문에 가로채는 장벽이 없기 때문 입니다. 리플렉션이나 JNI를 통해서도 아닙니다.


0

내가 생각할 수있는 유일한 것은 컴파일러가 최종 변수를 최적화하여 코드에 상수로 인라인 할 수 있다는 것입니다. 따라서 메모리가 할당되지 않게됩니다.


0

절대적으로 메모리 관리의 큰 이점을 얻을 수있는 객체의 수명을 짧게 만드는 한, 최근에는 한 테스트에 인스턴스 변수가있는 내보내기 기능과 메서드 수준 로컬 변수가있는 다른 테스트를 조사했습니다. 부하 테스트 중에 JVM은 첫 번째 테스트에서 메모리 부족 오류를 발생시키고 JVM이 중지되었습니다. 그러나 두 번째 테스트에서는 더 나은 메모리 관리로 인해 보고서를 성공적으로 얻을 수있었습니다.


0

지역 변수를 final로 선언하는 것을 선호하는 유일한 경우는 다음과 같습니다.

  • 나는 그들에게 일부 익명 클래스와 공유 할 수있는 최종 있도록하기 위해 (예 : 데몬 쓰레드를 생성하고이 방법을 둘러싸고 일부 값에 액세스 할 수)

  • 나는 그것들을 최종적으로 만들고 싶다 (예 : 실수로 덮어 쓰지 말아야 / 무시하지 않는 값)

빠른 가비지 수집에 도움이됩니까?
AFAIK 객체에 대한 강력한 참조가없는 경우 객체는 GC 수집의 후보가되며이 경우에도 즉시 가비지 수집된다는 보장이 없습니다. 일반적으로 강력한 참조는 범위를 벗어나거나 사용자가 null 참조에 명시 적으로 재 할당하면 죽는다고합니다. 따라서 최종 선언은 메서드가 존재할 때까지 참조가 계속 존재 함을 의미합니다 (범위가 명시 적으로 좁혀지지 않는 한 특정 내부 블록 {}) 최종 변수를 다시 할당 할 수 없기 때문입니다 (즉, null로 다시 할당 할 수 없음). 그래서 wrt Garbage Collection 'final' 원치 않는 지연을 초래할 수 있으므로 GC 후보가 될 때를 제어하므로 범위를 정의하는 데 약간주의해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.