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

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

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

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

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

이것이 사실입니까?



답변

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

public class MyClass {

   public final MyOtherObject obj;

}

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

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

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

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

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

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

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

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


답변

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

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


답변

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

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

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

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

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


답변

정리해야 할 몇 가지 사항 :

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

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

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


답변

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

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

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


답변

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

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

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

final boolean debug = false;

......

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

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


답변

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

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

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

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

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

조항:

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

Sun on Tuning 가비지 콜렉션