Teoricamente, qualsiasi tipo di dereferenziazione di puntatori nulli in Java attiva un NullPointerException
, che può essere gestito all'interno di Java come qualsiasi altra eccezione. Questo non significa che sia buono : in pratica, è piuttosto difficile recuperare da tale eccezione, tranne rimuovendo la parte difettosa (cioè lasciando che il thread chiamante muoia o tornando a un catch- tutta la clausola). Seguire un puntatore nullo è come sovrascrivere un buffer: è ancora un bug; la differenza tra Java e i linguaggi non protetti (come C) è che Java è più aggraziato dalle conseguenze di tale errore: in particolare, l'eccezione si propagherà di nuovo allo stack delle chiamate, rilasciando i monitor (le parole chiave synchronized
) e eseguendo le clausole finally
e sarà interessato solo il thread difettoso.
In pratica, tuttavia, potrebbero sorgere alcuni problemi. In che modo JVM intercetta i dereferences del puntatore nullo fino all'implementazione JVM; ma di solito le implementazioni sono le seguenti: seguono semplicemente il puntatore. Se il puntatore è null
, questo porta a un accesso alla memoria nella prima pagina della memoria, che è vietata dal sistema operativo. Il sistema operativo cattura l'evento (tramite la MMU) e informa l'applicazione in modo relativamente brutale (su sistemi di tipo Unix, questo è un segnale SIGSEGV
). La JVM riceve le informazioni e le trasforma in NullPointerException
.
La conversione da segnale a eccezione è una cosa complessa perché la JVM riceve solo l'indirizzo opcode offendente, che è da qualche parte nell'interprete bytecode o nel codice binario prodotto da JIT. La JVM deve individuare il metodo e lo stack di thread. Questo ha alcune belle interazioni con il modo in cui il sistema operativo mette le cose in memoria in primo luogo. Tale codice è abbastanza stabile oggigiorno, su piattaforme mainstream. Tuttavia, su una combinazione meno comune (il JDK di Sun è stato convertito in FreeBSD), a volte avevo riferimenti ai puntatori nulli per i quali JVM non era in grado di localizzare lo stack chiamante (perché il codice JVM era sintonizzato per Linux, non per FreeBSD, e apparentemente stava facendo un'ipotesi su dove dovrebbe essere lo stack, ipotesi che non è sempre stata soddisfatta su FreeBSD). La conseguenza è stata la cessazione immediata della JVM, in un modo molto spiacevole.
Un altro possibile trucchetto è che il dereferenziamento del puntatore nullo viene catturato perché l'accesso cade da qualche parte in una pagina "vietata"; vale a dire, la prima pagina dello spazio degli indirizzi. Tuttavia, quando si accede a un campo di istanza, la JVM di solito accede direttamente all'elemento di dati corretto: se il riferimento è, a livello di codice nativo, un puntatore all'indirizzo x e il campo è all'offset n nella struttura dell'oggetto, il codice leggerà i byte all'indirizzo x + n (questo dipende in realtà da come la JVM rappresenta gli oggetti internamente, ma una struttura simile a C è comune). Se la classe ha molti campi, allora n potrebbe essere maggiore della dimensione di una pagina, nel qual caso l'accesso in lettura non cadrà sulla prima pagina, ma il secondo. Ciò che accade in quella situazione dipende dal sistema operativo.
La situazione di "campo ad alto scostamento" non si presenta con gli array, perché gli accessi agli array sono controllati per lunghezza e la "lunghezza" è solitamente un tipo di campo vicino all'intestazione dell'oggetto. Inoltre, esiste un limite al numero di campi in una classe Java (esiste un limite assoluto a 65535 nel formato del file di classe, in realtà inferiore a quello perché ogni campo deve avere un nome e un tipo, che sono anche nella classe formato di file e colpirà altri limiti prima di quello). Quindi il problema non si presenterà su sistemi "comuni", ad es. un Linux che esegue Sun / Oracle VM. Questo è principalmente qualcosa di cui gli implementatori di VM devono essere consapevoli.
Quindi il mio consiglio è di considerare NullPointerException
come un bug grave, proprio come un buffer overflow (che è IndexOutOfBoundsException
in Java). La sicurezza della VM ti aiuterà a eseguire il debug del tuo codice (le tracce dello stack sono davvero utili) e, se non esegui il debug abbastanza prima della distribuzione, probabilmente la tua skin verrà salvata. Ma un bug è un bug.