Dzisiaj moi koledzy i ja dyskutujemy na temat użycia final
sł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 final
pomogłoby wyrzucaniu elementów bezużytecznych w wyczyszczenie pamięci z nieużywanych zmiennych w metodzie po jej zakończeniu.
Czy to prawda?
java
garbage-collection
final
Goran Martinic
źródło
źródło
Odpowiedzi:
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.
źródło
Zadeklarowanie zmiennej lokalnej
final
nie 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ą,totalWeight
która została zaznaczonafinal
. Z drugiej strony, zadeklarowanie prymitywu (double
zamiastDouble
)final
pozwoli 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 kilkapublic static final Strings
w 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,
final
jeś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.źródło
Nie, to zdecydowanie nieprawda.
Pamiętaj, że
final
nie 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.źródło
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.
źródło
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.
źródło
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.
źródło
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
źródło
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.
źródło
final
na 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, żefinal
metody są bardzo podobne do klas.final
w polu statycznym może pozwolić na interpretację zmiennej jako „stałą czasu kompilacji” i na tej podstawie przeprowadzić optymalizację przez javac.final
na polach daje JVM pewną swobodę ignorowania relacji zdarzenie przed .źródło
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.
źródło
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.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.źródło
Ściśle mówiąc o polach instancji ,
final
może nieznacznie poprawić wydajność, jeśli konkretny GC chce to wykorzystać. KiedyGC
zdarza 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 aGC
wykonuje 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
final
przykład pola, ponieważ nie można ich modyfikować (bez odbicia), bariery te można pominąć. I to nie jest tylko czysta teoria.Shenandoah GC
ma je w praktyce (choć nie na długo ), a możesz np .:W algorytmie GC będą optymalizacje , które sprawią, że będzie on nieco szybszy. Dzieje się tak, ponieważ nie będzie przechwytywania barier
final
, ponieważ nikt nigdy nie powinien ich modyfikować. Nawet przez refleksję czy JNI.źródło
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ęć.
źródło
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ą.
źródło
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ę)
źródło