Czy użycie final dla zmiennych w Javie poprawia usuwanie pamięci?


86

Dzisiaj moi koledzy i ja dyskutujemy na temat użycia finalsłowa kluczowego w Javie do usprawnienia zbierania śmieci.

Na przykład, jeśli napiszesz metodę taką jak:

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

Zadeklarowanie zmiennych w metodzie finalpomogłoby wyrzucaniu elementów bezużytecznych w wyczyszczenie pamięci z nieużywanych zmiennych w metodzie po jej zakończeniu.

Czy to prawda?


właściwie są tu dwie rzeczy. 1) kiedy piszesz do pola lokalnego metody i instancji. Kiedy piszesz do instancji nie mógł być zaletą.
Eugene

Odpowiedzi:


86

Oto nieco inny przykład, jeden z końcowymi polami typu odwołania zamiast końcowymi zmiennymi lokalnymi typu wartości:

public class MyClass {

   public final MyOtherObject obj;

}

Za każdym razem, gdy tworzysz instancję MyClass, będziesz tworzyć wychodzące odwołanie do instancji MyOtherObject, a GC będzie musiał podążać za tym linkiem, aby wyszukać obiekty na żywo.

JVM korzysta z algorytmu GC typu mark-sweep, który musi sprawdzać wszystkie odniesienia na żywo w lokalizacjach „root” GC (podobnie jak wszystkie obiekty w bieżącym stosie wywołań). Każdy żywy obiekt jest „oznaczany” jako żywy, a każdy obiekt, do którego odnosi się żywy obiekt, jest również oznaczany jako żywy.

Po zakończeniu fazy oznaczania GC przeszukuje stertę, zwalniając pamięć dla wszystkich nieoznakowanych obiektów (i zwalniając pamięć dla pozostałych aktywnych obiektów).

Ponadto ważne jest, aby zdać sobie sprawę, że pamięć sterty Java jest podzielona na „młode pokolenie” i „starsze pokolenie”. Wszystkie obiekty są początkowo przydzielane do młodego pokolenia (czasami określane jako „żłobek”). Ponieważ większość obiektów jest krótkotrwała, GC jest bardziej agresywna w kwestii uwalniania ostatnich śmieci od młodego pokolenia. Jeśli obiekt przetrwa cykl kolekcji młodego pokolenia, zostaje przeniesiony do starego pokolenia (czasami nazywanego „pokoleniem najemnym”), które jest przetwarzane rzadziej.

Tak więc, na samą myśl, powiem „nie, 'ostateczny' modyfikator nie pomaga GC zmniejszyć obciążenia pracą”.

Moim zdaniem najlepszą strategią optymalizacji zarządzania pamięcią w Javie jest jak najszybsze wyeliminowanie fałszywych odwołań. Możesz to zrobić, przypisując „null” odwołaniu do obiektu, gdy tylko skończysz go używać.

Lub, jeszcze lepiej, zminimalizuj rozmiar każdego zakresu deklaracji. Na przykład, jeśli zadeklarujesz obiekt na początku metody 1000-liniowej i jeśli obiekt pozostanie żywy aż do zamknięcia zakresu tej metody (ostatni zamykający nawias klamrowy), wówczas obiekt może pozostać żywy znacznie dłużej niż w rzeczywistości niezbędny.

Jeśli użyjesz małych metod, z zaledwie kilkunastoma liniami kodu, to zadeklarowane w tej metodzie obiekty szybciej wyjdą poza zakres, a GC będzie mógł wykonać większość swojej pracy w ramach młoda generacjA. Nie chcesz, aby obiekty były przenoszone do starszej generacji, chyba że jest to absolutnie konieczne.


Do przemyślenia. Zawsze myślałem, że kod wbudowany jest szybszy, ale jeśli jvm ma za mało pamięci, to również będzie spowalniać. hmmmmm ...
WolfmanDragon,

1
Zgaduję tutaj ... ale zakładam, że kompilator JIT może wbudować końcowe wartości pierwotne (nie obiekty), aby uzyskać niewielki wzrost wydajności. Z drugiej strony, wstawianie kodu może generować znaczące optymalizacje, ale nie ma nic wspólnego z końcowymi zmiennymi.
benjismith

2
Nie byłoby możliwe przypisanie wartości null do już utworzonego obiektu końcowego, a może ostatecznego zamiast pomocy, mogłoby to utrudnić
Hernán Eche,

1
Można użyć {} również do ograniczenia zakresu w dużej metodzie, zamiast rozdzielać go na kilka metod prywatnych, które mogą nie mieć zastosowania do innych metod klasowych.
mmm

Można również używać zmiennych lokalnych zamiast pól, gdy jest to możliwe, aby ulepszyć wyrzucanie elementów bezużytecznych w pamięci i zmniejszyć powiązania referencyjne.
sivi,

37

Zadeklarowanie zmiennej lokalnej finalnie wpłynie na czyszczenie pamięci, oznacza jedynie, że nie możesz modyfikować zmiennej. Twój przykład powyżej nie powinien się kompilować, ponieważ modyfikujesz zmienną, totalWeightktóra została zaznaczona final. Z drugiej strony, zadeklarowanie prymitywu ( doublezamiast Double) finalpozwoli na wstawienie tej zmiennej do kodu wywołującego, co może spowodować pewną poprawę pamięci i wydajności. Jest to używane, gdy masz kilka public static final Stringsw klasie.

Ogólnie rzecz biorąc, kompilator i środowisko wykonawcze będą optymalizować tam, gdzie to możliwe. Najlepiej jest napisać kod odpowiednio i nie próbować być zbyt podstępnym. Użyj, finaljeśli nie chcesz, aby zmienna była modyfikowana. Załóżmy, że kompilator wykona wszelkie łatwe optymalizacje, a jeśli martwisz się o wydajność lub użycie pamięci, użyj profilera, aby określić rzeczywisty problem.


26

Nie, to zdecydowanie nieprawda.

Pamiętaj, że finalnie oznacza to stałego, po prostu oznacza, że ​​nie możesz zmienić odniesienia.

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

Może wystąpić niewielka optymalizacja oparta na wiedzy, że JVM nigdy nie będzie musiała modyfikować odniesienia (np. Nie będzie musiała sprawdzać, czy uległa zmianie), ale byłaby tak drobna, że ​​nie należy się tym martwić.

Final należy traktować jako przydatne metadane dla programisty, a nie jako optymalizację kompilatora.


17

Kilka kwestii do wyjaśnienia:

  • Anulowanie odwołania nie powinno pomóc GC. Gdyby tak było, oznaczałoby to, że twoje zmienne są poza zakresem. Jedynym wyjątkiem jest nepotyzm przedmiotowy.

  • W Javie nie ma jeszcze alokacji na stosie.

  • Zadeklarowanie zmiennej jako ostatecznej oznacza, że ​​nie możesz (w normalnych warunkach) przypisać nowej wartości do tej zmiennej. Ponieważ wersja ostateczna nie mówi nic o zakresie, nie mówi nic o jego wpływie na GC.


Istnieje alokacja na stosie (prymitywów i odwołań do obiektów w stercie) w java: stackoverflow.com/a/8061692/32453 Java wymaga, aby obiekty w tym samym zamknięciu co anonimowa klasa / lambda były również ostateczne, ale zmienia to tylko po to, aby zmniejszyć „wymaganą pamięć / ramkę” / zmniejszyć zamieszanie, więc nie ma związku z jej gromadzeniem ...
rogerdpack

11

No cóż, nie wiem o zastosowaniu w tym przypadku „końcowego” modyfikatora ani o jego wpływie na GC.

Ale można powiedzieć, to: korzystanie z Boxed wartości zamiast prymitywów (np Pokój zamiast podwójne) przeznaczy tych obiektów na stercie zamiast stosu i będzie produkować niepotrzebnych śmieci, że GC będzie musiał posprzątać.

Używam prymitywów w pudełkach tylko wtedy, gdy jest to wymagane przez istniejący interfejs API lub gdy potrzebuję prymitywów dopuszczających wartość null.


1
Masz rację. Potrzebowałem tylko szybkiego przykładu, aby wyjaśnić moje pytanie.
Goran Martinic

5

Zmiennych końcowych nie można zmienić po początkowym przypisaniu (wymuszonym przez kompilator).

Nie zmienia to zachowania wyrzucania elementów bezużytecznych jako takiego. Jedyną rzeczą jest to, że te zmienne nie mogą być zerowane, gdy nie są już używane (co może pomóc w usuwaniu elementów bezużytecznych w sytuacjach ograniczonej pamięci).

Powinieneś wiedzieć, że wersja ostateczna pozwala kompilatorowi na przyjęcie założeń dotyczących optymalizacji. Kod wbudowany i bez kodu, o którym wiadomo, że nie jest osiągalny.

final boolean debug = false;

......

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

Println nie zostanie uwzględniony w kodzie bajtowym.


@Eugene Zależy od twojego menedżera bezpieczeństwa i tego, czy kompilator wstawił zmienną, czy nie.
Thorbjørn Ravn Andersen

racja, po prostu byłem pedantyczny; nic więcej; również udzielił odpowiedzi
Eugene

4

Istnieje niezbyt znana narożna walizka z pokoleniowymi śmieciarzami. (Aby uzyskać krótki opis, przeczytaj odpowiedź Benjismitha, aby uzyskać głębszy wgląd, przeczytaj artykuły na końcu).

Idea pokoleniowych GC polega na tym, że przez większość czasu należy brać pod uwagę tylko młode pokolenia. Lokalizacja katalogu głównego jest skanowana w poszukiwaniu odniesień, a następnie skanowane są obiekty młodej generacji. Podczas tych częstszych przeciągnięć żaden obiekt ze starej generacji nie jest sprawdzany.

Teraz problem wynika z faktu, że obiekt nie może mieć odniesień do młodszych obiektów. Kiedy długo żyjący (stara generacja) obiekt otrzymuje odwołanie do nowego obiektu, to odwołanie musi być jawnie śledzone przez moduł odśmiecania pamięci (zobacz artykuł IBM na temat modułu zbierającego JVM hotspot ), co faktycznie wpływa na wydajność GC.

Powodem, dla którego stary obiekt nie może odnosić się do młodszego, jest to, że ponieważ stary obiekt nie jest sprawdzany w pomniejszych kolekcjach, jeśli jedyne odniesienie do obiektu jest przechowywane w starym obiekcie, nie zostanie ono oznaczone i będzie błędne zwolniony podczas etapu zamiatania.

Oczywiście, jak wielu wskazuje, końcowe słowo kluczowe nie wpływa w rzeczywistości na moduł odśmiecania pamięci, ale gwarantuje, że odniesienie nigdy nie zostanie zmienione na młodszy obiekt, jeśli ten obiekt przetrwa mniejsze kolekcje i trafi do starszej sterty.

Artykuły:

IBM na temat czyszczenia pamięci: historia , w wirtualnej maszynie wirtualnej hotspot i wydajność . Mogą one nie być już w pełni aktualne, ponieważ pochodzą z 2003/04, ale dają łatwy do odczytania wgląd w GC.

Sun on Tuning garbage collection


3

GC działa na nieosiągalnych referencjach. Nie ma to nic wspólnego z „ostatecznym”, które jest po prostu zapewnieniem jednorazowego przypisania. Czy to możliwe, że GC niektórych maszyn wirtualnych może korzystać z „wersji ostatecznej”? Nie wiem, jak i dlaczego.


3

finalna lokalnych zmiennych i parametrach nie ma znaczenia dla tworzonych plików klas, więc nie może wpływać na wydajność środowiska wykonawczego. Jeśli klasa nie ma podklas, HotSpot traktuje tę klasę tak, jakby była ostateczna (można cofnąć później, jeśli zostanie załadowana klasa, która łamie to założenie). Uważam, że finalmetody są bardzo podobne do klas. finalw polu statycznym może pozwolić na interpretację zmiennej jako „stałą czasu kompilacji” i na tej podstawie przeprowadzić optymalizację przez javac. finalna polach daje JVM pewną swobodę ignorowania relacji zdarzenie przed .


2

Wydaje się, że jest wiele odpowiedzi, które są błądzącymi domysłami. Prawda jest taka, że nie ma końcowego modyfikatora dla zmiennych lokalnych na poziomie kodu bajtowego. Maszyna wirtualna nigdy się nie dowie, że zmienne lokalne zostały zdefiniowane jako ostateczne, czy nie.

Odpowiedź na twoje pytanie brzmi: zdecydowanie nie.


Może to prawda, ale kompilator może nadal używać końcowych informacji podczas analizy przepływu danych.

@WernerVanBelle kompilator już wie, że zmienna jest ustawiana tylko raz. Musi już przeprowadzić analizę przepływu danych, aby wiedzieć, że zmienna może mieć wartość null, nie jest inicjowana przed użyciem, itp. Zatem lokalne finały nie oferują kompilatorowi żadnych nowych informacji.
Matt Quigley,

Tak nie jest. Analiza przepływu danych może wywnioskować wiele rzeczy, ale możliwe jest posiadanie kompletnego programu w bloku, który będzie ustawiał zmienną lokalną lub nie. Kompilator nie może z góry wiedzieć, czy zmienna zostanie zapisana i czy w ogóle będzie stała. Dlatego kompilator bez końcowego słowa kluczowego nie może zagwarantować, że zmienna jest ostateczna, czy nie.

@WernerVanBelle Jestem naprawdę zaintrygowany, czy możesz podać przykład? Nie rozumiem, jak może istnieć ostateczne przypisanie do zmiennej, która nie jest ostateczna, o której kompilator nie wie. Kompilator wie, że jeśli masz niezainicjowaną zmienną, nie pozwoli ci jej użyć. Jeśli spróbujesz przypisać końcową zmienną wewnątrz pętli, kompilator ci na to nie pozwoli. Jaki jest przykład, w którym zmienna MOŻE być zadeklarowana jako ostateczna, ale NIE jest, a kompilator nie może zagwarantować, że jest ostateczna? Podejrzewam, że jakikolwiek przykład byłby zmienną, której nie można było zadeklarować jako ostatecznej w pierwszej kolejności.
Matt Quigley,

Ach, po ponownym przeczytaniu twojego komentarza, widzę, że twój przykład jest zmienną zainicjowaną wewnątrz bloku warunkowego. Te zmienne nie mogą być ostateczne w pierwszej kolejności, chyba że wszystkie ścieżki warunków inicjalizują zmienną jeden raz. Kompilator wie więc o takich deklaracjach - w ten sposób pozwala skompilować ostateczną zmienną zainicjowaną w dwóch różnych miejscach (pomyśl final int x; if (cond) x=1; else x=2;). Kompilator bez słowa kluczowego final może zatem zagwarantować, że zmienna jest ostateczna, czy nie.
Matt Quigley,

1

Wszystkie metody i zmienne mogą być nadpisane przez domyślne w podklasach.Jeśli chcemy zachować podklasy przed nadpisaniem członków nadklasy, możemy zadeklarować je jako ostateczne za pomocą słowa kluczowego final. Na przykład - final int a=10; final void display(){......} Dokonywanie ostatecznej wersji metody gwarantuje, że funkcjonalność zdefiniowana w nadklasie i tak nigdy nie zostanie zmieniona. Podobnie nigdy nie można zmienić wartości końcowej zmiennej. Zmienne końcowe zachowują się jak zmienne klasowe.


1

Ściśle mówiąc o polach instancji , final może nieznacznie poprawić wydajność, jeśli konkretny GC chce to wykorzystać. Kiedy GCzdarza się współbieżność (oznacza to, że aplikacja nadal działa, podczas gdy GC jest w toku ), zobacz to, aby uzyskać szersze wyjaśnienie , GC muszą stosować pewne bariery podczas zapisywania i / lub odczytywania. Łącze, które wam dałem, wyjaśnia to, ale żeby było naprawdę krótkie: kiedy a GCwykonuje jakąś równoległą pracę, wszystkie czyta i zapisuje na stercie (podczas gdy ten GC jest w toku), są „przechwytywane” i stosowane później; aby równoczesna faza GC mogła zakończyć swoją pracę.

Na finalprzykład pola, ponieważ nie można ich modyfikować (bez odbicia), bariery te można pominąć. I to nie jest tylko czysta teoria.

Shenandoah GCma je w praktyce (choć nie na długo ), a możesz np .:

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

W algorytmie GC będą optymalizacje , które sprawią, że będzie on nieco szybszy. Dzieje się tak, ponieważ nie będzie przechwytywania barierfinal , ponieważ nikt nigdy nie powinien ich modyfikować. Nawet przez refleksję czy JNI.


0

Jedyne, co przychodzi mi do głowy, to to, że kompilator może zoptymalizować końcowe zmienne i wstawić je jako stałe do kodu, w wyniku czego nie zostanie przydzielona żadna pamięć.


0

absolutnie, o ile skracają żywotność obiektu, co daje duże korzyści z zarządzania pamięcią, ostatnio badaliśmy funkcję eksportu, w której zmienne instancji są w jednym teście, a inny test ma zmienną lokalną na poziomie metody. podczas testowania obciążenia JVM wyrzuca błąd z pamięci podczas pierwszego testu i JVM zostaje zatrzymany. ale w drugim teście udało się uzyskać raport dzięki lepszemu zarządzaniu pamięcią.


0

Jedynym przypadkiem, w którym wolę deklarować zmienne lokalne jako ostateczne, jest:

  • I mieć do nich ostateczną tak, że mogą być dzielone z jakiegoś anonimowego klasy (na przykład: tworzenie demona wątku i pozwolić jej przejść jakąś wartość od załączając metody)

  • I chcą , aby im ostateczny (na przykład: pewną wartość, że nie powinien / nie dostać przesłonięte przez pomyłkę)

Czy pomagają w szybkim zbieraniu śmieci?
AFAIK, obiekt staje się kandydatem do kolekcji GC, jeśli nie ma do niego silnych odniesień i również w tym przypadku nie ma gwarancji, że zostanie on natychmiast usunięty. Ogólnie rzecz biorąc, mówi się, że silne odniesienie umiera, gdy wychodzi poza zakres lub użytkownik jawnie ponownie przypisuje je do odniesienia zerowego, a zatem zadeklarowanie ich jako ostateczne oznacza, że ​​odniesienie będzie istniało do momentu istnienia metody (chyba że jego zakres zostanie wyraźnie zawężony do określony blok wewnętrzny {}), ponieważ nie można ponownie przypisać zmiennych końcowych (tj. nie można ponownie przypisać do wartości null). Więc myślę, że „final” w programie Garbage Collection może wprowadzić niepożądane możliwe opóźnienie, więc należy być mało ostrożnym przy definiowaniu zakresu, ponieważ ten kontroluje, kiedy staną się kandydatami do GC.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.