Possibile inquinamento dell'heap tramite il parametro varargs


433

Capisco che ciò si verifica con Java 7 quando si utilizza varargs con un tipo generico;

Ma la mia domanda è ...

Cosa significa esattamente Eclipse quando dice "il suo utilizzo potrebbe potenzialmente inquinare l'heap?"

E

In che modo la nuova @SafeVarargsannotazione impedisce questo?



possibile duplicato della funzione varargs
200_successo

Lo vedo nel mio editore:Possible heap pollution from parameterized vararg type
Alexander Mills,

Risposte:


252

L'inquinamento da ammasso è un termine tecnico. Si riferisce a riferimenti che hanno un tipo che non è un supertipo dell'oggetto a cui puntano.

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

Questo può portare a "inspiegabili" ClassCastException.

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargsnon lo impedisce affatto. Tuttavia, ci sono metodi che di fatto non inquinano l'heap, il compilatore non può provarlo. In precedenza, i chiamanti di tali API avrebbero ricevuto avvisi fastidiosi che erano completamente inutili ma che dovevano essere soppressi in ogni sito di chiamata. Ora l'autore dell'API può eliminarlo una volta nel sito della dichiarazione.

Tuttavia, se il metodo in realtà non è sicuro, gli utenti non verranno più avvisati.


2
Quindi stiamo dicendo che l'heap è inquinato perché contiene riferimenti i cui tipi non sono quelli che ci possiamo aspettare? (Elenco <A> vs Elenco <B> nel tuo esempio)
hertzsprung,


30
Questa risposta è una bella spiegazione di cosa sia l'inquinamento da ammasso, ma non spiega perché i vararg siano così particolarmente suscettibili di causarlo da giustificare un avvertimento specifico.
Dolda 2000,

4
Anche a me mancano informazioni su come garantire che il mio codice non contenga questo problema (es. Come faccio a sapere se è abbastanza rinforzato per aggiungere @SafeVarargs)
Daniel Alder

237

Quando dichiari

public static <T> void foo(List<T>... bar) il compilatore lo converte in

public static <T> void foo(List<T>[] bar) poi a

public static void foo(List[] bar)

Sorge quindi il pericolo di assegnare erroneamente valori errati all'elenco e il compilatore non genererà alcun errore. Ad esempio, se Tè un Stringallora il seguente codice verrà compilato senza errori ma fallirà in fase di esecuzione:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

Se hai esaminato il metodo per assicurarti che non contenga tali vulnerabilità, puoi annotarlo @SafeVarargsper eliminare l'avviso. Per le interfacce, utilizzare @SuppressWarnings("unchecked").

Se viene visualizzato questo messaggio di errore:

Il metodo Varargs potrebbe causare l'inquinamento dell'heap dal parametro varargs non riutilizzabile

e sei sicuro che il tuo utilizzo sia sicuro, dovresti @SuppressWarnings("varargs")invece usarlo . Vedi @SafeVarargs è un'annotazione appropriata per questo metodo? e https://stackoverflow.com/a/14252221/14731 per una bella spiegazione di questo secondo tipo di errore.

Riferimenti:


2
Penso di capire meglio. Il pericolo arriva quando lanci varargs Object[]. Finché non lo fai Object[], sembra che dovresti andare bene.
djeikyb,

3
Come esempio di una cosa stupida che si possa fare: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. E poi chiama bar(Arrays.asList(1,2));.
djeikyb,

1
@djeikyb se il pericolo si presenta solo se eseguo il cast Object[]perché il compilatore dovrebbe attivare un avviso se non lo faccio? Dovrebbe essere abbastanza facile controllarlo in fase di compilazione, dopo tutto (nel caso in cui non lo passi a un'altra funzione con una firma simile, nel qual caso l'altra funzione dovrebbe attivare un avviso). Non credo che questo sia davvero il nocciolo dell'avvertimento ("Sei al sicuro se non lanci"), e ancora non capisco in quale caso sto bene.
Qw3ry,

5
@djeikyb Puoi fare esattamente la stessa cosa stupida senza varargs parametrizzati (ad es bar(Integer...args).). Allora, qual è il punto di questo avvertimento allora?
Vasiliy Vlasov,

3
@VasiliyVlasov Questo problema è rilevante solo per le variabili con parametri. Se si tenta di fare la stessa cosa con array non tipizzati, il runtime impedirà di inserire il tipo errato nell'array. Il compilatore si avverte che il runtime non sarà in grado di prevenire comportamenti scorretti, perché il tipo di parametro è sconosciuto in fase di esecuzione (al contrario, gli array non conoscono il tipo di loro elementi non generici in fase di esecuzione).
Gili,

8

@SafeVarargs non impedisce che ciò accada, tuttavia impone che il compilatore sia più rigoroso durante la compilazione del codice che lo utilizza.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html spiega questo in maggiore dettaglio.

L'inquinamento da heap si verifica quando si ottiene un'operazione ClassCastExceptionquando si esegue un'operazione su un'interfaccia generica e contiene un tipo diverso da quello dichiarato.


Le ulteriori restrizioni del compilatore sul suo utilizzo non sembrano particolarmente rilevanti.
Paul Bellora,

6

Quando usi varargs, può risultare nella creazione di un Object[]per contenere gli argomenti.

Grazie all'analisi di escape, JIT può ottimizzare questa creazione di array. (Una delle poche volte che l'ho trovato lo fa) Non è garantito che venga ottimizzato, ma non mi preoccuperei a meno che tu non veda un problema nel tuo profiler di memoria.

AFAIK @SafeVarargselimina un avviso dal compilatore e non modifica il comportamento di JIT.


6
Interessante anche se in realtà non risponde alla sua domanda @SafeVarargs.
Paul Bellora,

1
No. Non è questo l'inquinamento da cumulo. "L'inquinamento dell'heap si verifica quando una variabile di un tipo con parametri si riferisce a un oggetto che non è di quel tipo con parametri". Rif: docs.oracle.com/javase/tutorial/java/generics/…
Doradus,

1

Il motivo è perché varargs offre la possibilità di essere chiamato con un array di oggetti non parametrizzato. Quindi se il tuo tipo era Elenco <A> ..., può anche essere chiamato con il tipo Elenco [] non varargs.

Ecco un esempio:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

Come puoi vedere, l'Elenco [] b può contenere qualsiasi tipo di consumatore, eppure questo codice viene compilato. Se usi varargs, allora stai bene, ma se usi la definizione del metodo dopo la cancellazione del tipo - test vuoto (Elenco []) - il compilatore non controllerà i tipi di parametri del modello. @SafeVarargs sopprimerà questo avviso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.