Eccoci qua di nuovo con un'esilarante novità Java esposta dal
sottoscritto. Forse qualcuno ne ha sentito parlare, forse qualcun altro
no, quest'ultimi in caso possono continuare a leggere e i primi pure
(perché sarà comunque una lettura leggera, o così spero).
Oggi
parliamo del progetto Lilliput, che al di là del chiaro tributo alla
minuscola nazione gulliveriana di Johnathan Swift, ha come obiettivo una
piccola rivoluzione nell'allocazione di nuovi oggetti in Java,
riducendo lo spazio in memora della loro intestazione, cioè lillipuziando l'object header.
Situazione attuale
Per iniziare cerchiamo di capire cosa sia questo object header e capire perché ne si vuole modificare la forma. L'object header è una struttura a 128bit di metadati che definiscono l'oggetto, tra cui il così detto lock/mark word
e il puntatore alla classe dell'oggetto. La struttura dell'intestazione
è così rappresentata (dove tutta la parte dei campi e dei metodi fanno
parte del class pointer):
Lock/Mark word:
è una sezione di 64 bit che viene usata per i meccanismi di locking
(tra cui un bit dedicato al deprecato meccanismo biased locking), per il
garbage collecting (anche se è più ilare dire per la nettezza della memoria) e per l'identity hashcode.
Class pointer:
è la famosa sezione di cui non dovremmo mai preoccuparci
(NullPointerException!), ma nella realtà dei fatti è il puntatore dei
nostri oggetti, puntatore che si collega ai metadati dell'istanza della
nostra classe. Da notare che tale sezione è sì di altri 64bit, ma che
nelle classi usuali, 32 di questi sono inutilizzati, tranne nel caso
dell'oggetto array dove invece viene salvata la sua dimensione (ecco perché la
length di un array è un integer, e non un long).
La proposta
Si
evince dalle precedenti informazioni che c'è quindi l'opportunità di
poter ridurre la dimensione dell'header, permettendo così di:
- far un minor uso della memoria di heap
- poter allocare più oggetti
- ridurre l'attività di garbage collecting (poiché più oggetti puoi generare, meno operatività del garbage collector per recuperare memoria è necessaria)
- rendere gli oggetti "compatti" tra loro, cosicché il principio di località possa essere applicato e reso più efficiente.
Di primo acchito per i meno avvezzi tutto sommato può sembrare un lavoro fattibile, ma ci sono alcuni aspetti da considerare:
- la generazione di un object header differente a seconda delle necessità può impattare sulle performance e sull'overflow
- la reimplementazione dell'object header richiede un cambiamento per tutte le piattaforme supportate dalla JDK (32bit comprese)
- questo può impattare sullo sviluppo di altri progetti in corso vedi Panama (per le librerie native), Loom (i virtual thread), Leyden (per le immagine statiche della VM) e Valhalla (value/primitive types)
- l'identity hashcode al momento è di 32bit, ma nulla vieta di ottenerne di 4 o 8 word, e questo impatterebbe sull'Object.hashCode() e tutte quelle collection di tipo Hash* (HashMap, LinkedHashMap, ecc...)
- la lunghezza di un array, come è stato menzionato prima, è di 32bit, anche qui quindi ci sarebbe spazio di manovra permettendo vettori di dimensioni più grandi, ma l'implicazione da considerare è sempre l'impatto sulle specifiche e la retrocompatibilità.
Conclusioni
Al momento il progetto è in via di sviluppo (questo è il repo:
https://github.com/openjdk/lilliput.git), ma non è dato ancora a sapere per quale versione di Java potrebbe essere pronto (per ora la JDK20 non ne fa menzione). È un progetto allettante, perché un'allocazione di oggetti più snella renderebbe le applicazioni esistenti o legacy meno esose in termini di risorse senza lavorarci troppo (vabbè dichiarazione da marketing, ma tant'è...).
Abbracci e omaggi!
Luca!