Po omówieniu słowa kluczowego final dla klas, metod, zmiennych lokalnych oraz argumentów funkcji można przejść do final
w kontekście pól. Pola obiektów można podzielić na statyczne (czyli takie, które są związane z daną klasą) oraz instancyjne (związane bezpośrednio z danym obiektem). W obu przypadkach final
określa, że dane pole może mieć tylko jedno przypisanie, które z resztą musi być wykonane w czasie tworzenia obiektu/ładowania klasy. Cóż interesującego można powiedzieć o polach statycznych finalnych?
Typy prymitywne i Stringi
Jak głosi Oficjalny tutorial do Javy 8 autorstwa Oracle, pola statyczne finalne zwane są compile-time constant
(albo były, bo aktualnie trudno znaleźć tę nazwę w nowszych źródłach). Jakkolwiek, każde użycie takiego pola jest zamieniane w czasie kompilacji do bytecode’u na jego wartość.
Zatem optymalizacja dla final
, którą zauważyliśmy również dla finalnych zmiennych lokalnych, tzn. Constant Folding ma zastosowanie również i w tym przypadku.
Obiekty
O ile przy typach prymitywnych można zrobić Constant Folding, o tyle w przypadku samych obiektów raczej nie ma takiej możliwości (ciężko sobie to wyobrazić). Warto jednak sprawdzić optymalizację odwołań do danego pola takiego obiektu umieszczonego w polu static final
. Jednak wówczas mówimy tak na prawdę o finalu w kontekście niestatycznym, zatem opiszę to przy innej okazji.
Jaki wpływ ma dodanie final
do pola statycznego w kontekście wywoływania jego metody?
Aby się tego dowiedzieć, wykonajmy prosty test:
private static final Super F_SUPER = new Super(); private static final Super F_SUB_AS_SUPER = new Sub(); private static final Sub F_SUB = new Sub(); private static Super N_SUPER = new Super(); private static Super N_SUB_AS_SUPER = new Sub(); private static Sub N_SUB = new Sub(); // FOR EACH public int benchmark() { return SOME_CASE.someMethodInvocation(); }
W tym benchmarku sprawdzamy wywoływanie metody, której treść zawiera zwrócenie stałej wartości. Sprawdzamy wywołanie polimorficzne, bezpośrednie nadklasy oraz bezpośrednio podklasy. W przypadku każdego zastosowania słowa kluczowego final
mamy (na moim laptopie) 250 milionów operacji na sekudnę. Jeśli spojrzeć w kod wygenerowany przez C2
, to zobaczymy tam wyłącznie zwrócenie tej stałej wartości. Ten brak dodatkowych akcji zarówno dla wywołań polimorficznych jak i bezpośrednich wynika z tego, że zarówno po pierwszym, jak i po 15 000 wywołaniu metody znamy obiekt, którego metodę wywołujemy. Jest w polu finalnym, więc nie może się zmienić. Po zainicjalizowaniu nie da się go również zamienić na null
. Stąd prosty kod maszynowy:
mov $0x5,%eax add $0x10,%rsp pop %rbp mov 0x108(%r15),%r10 test %eax,(%r10) ; {poll_return}
Taką samą przepustowość otrzymałem również dla private static Sub N_SUB
. Stało się tak pomimo, iż jeśli spojrzymy w kod C2, zobaczymy tam dodatkowego nullchecka (4 dodatkowe instrukcje kodu maszynowego). Jednak nie musimy sprawdzać typu obiektu w polu dzięki wspomnianemu wcześniej mechanizmowi CHA. Stąd kod maczynowy wygląda następująco:
movabs $0x716320790,%r10 mov 0x84(%r10),%r11d test %r11d,%r11d je 0x7f3968742977 mov $0x5,%eax add $0x10,%rsp pop %rbp mov 0x108(%r15),%r10 test %eax,(%r10) ; {poll_return}
Nieco więcej instrukcji trzeba wykonać w przypadku polimorficznego wywołania metody pola statycznego niefinalnego. Oprócz wspomnianego wcześniej nullchecka musimy dodatkowo sprawdzić typ obiektu – jest to dodatkowy odczyt z pamięci, co skutkuje zmniejszeniem przepustowości z 250 do 243 milionów operacji na sekundę. Wspomniane zmiany są widoczne na zrzucie instrukcji kodu maszynowego wygenerowanego przez C2
.
movabs $0x7164c8a90,%r10 ; {oop()} mov 0x88(%r10),%r11d ;*getstatic N_SUB_AS_SUPER {reexecute=0 rethrow=0 return_oop=0} mov 0x8(%r12,%r11,8),%r10d ; implicit exception: dispatches to 0x00007f84ceff0822 cmp $0x80126b8,%r10d ; {metadata('pl/jgardo/classes/hierarchy/with/FinalClass')} jne 0x7f84ceff0810 movabs $0x716320790,%r10 mov 0x84(%r10),%r11d test %r11d,%r11d je 0x7f3968742977 mov $0x5,%eax add $0x10,%rsp pop %rbp mov 0x108(%r15),%r10 test %eax,(%r10) ; {poll_return}
Podsumowanie
To chyba najkrótszy z dotychczasowych wpisów.
Podsumować go można stwierdzeniem, że dla pól statycznych słowo kluczowe final
ma znaczenie – dla typów prymitywnych, stringów, lecz również dla obiektów.
Następny artykuł z serii final: tworzenie obiektów z polami finalnymi.