2013-01-18 17 views
6

Come ben sapete a volte Java utilizza i pool di oggetti per wrapper e tipi di stringhe, a volte no.Pool di oggetti per wrapper e stringhe Java

Ad esempio:

Integer i1 = 1; 
Integer i2 = 1; 
Integer i3 = new Integer(1); 
String s1 = "String"; 
String s2 = "String"; 
String s3 = new String ("String"); 
System.out.println("(i1 == i2) " + (i1 == i2)); 
System.out.println("(i2 == i3) " + (i2 == i3)); 
System.out.println("(s1 == s2) " + (s1 == s2)); 
System.out.println("(s2 == s3) " + (s2 == s3)); 

Execution result: 

(i1 == i2) true 
(i2 == i3) false 
(s1 == s2) true 
(s2 == s3) false 

Come si vede la boxe di primitive prende oggetti dal pool, creando di uno stringhe tramite stringa letterale prende oggetti dalla piscina, troppo. Tali oggetti sono in realtà lo stesso oggetto (l'operatore == restituisce true su di essi).

Altri meccanismi di creazione di wrapper e stringhe non prendono oggetti dal pool. Gli oggetti creati in questi modi sono in realtà oggetti diversi (operatore == restituisce false su di essi).

Ciò che mi confonde è il fatto che la piscina è parzialmente utilizzata.

Se si tratta di un problema di memoria, perché non utilizzare la piscina tutte le volte? Se non è un problema di memoria, perché usarlo del tutto?

La domanda è: quali sono le ragioni per l'implementazione di tale comportamento (= utilizzo parziale del pool)?

La domanda è piuttosto teorica, ma ha un'applicazione pratica: può aiutare a capire in che modo utilizzare correttamente gli oggetti personalizzati e naturalmente comprendere come funziona Java.

+1

Quindi è il risultato di qualche scambio (quanto inaspettato !!!). È difficile selezionare la risposta migliore. Ci sono un paio di buoni candidati. Quindi, seleziono quello che fornisce la citazione "Specifica linguaggio Java". –

risposta

2

È un problema di velocità, l'assegnazione di un nuovo Integer ogni singola volta sarebbe costosa sia in termini di tempo che di memoria. Ma per lo stesso motivo, allocare all'avvio troppo all'avvio utilizza tonnellate di memoria e tempo.

Purtroppo, questo comportamento è contro-intuitivo.

Il risultato è questo strano compromesso che abbiamo. Le ragioni di questo comportamento sono discusse nello standard Java. (5,7)

Se il valore p essere inscatolato è vero, falso, un byte, un char nell'intervallo \ u0000 a \ u007F, o un int o numero breve tra -128 e 127, poi lasciare r1 e r2 sono i risultati di due conversioni di boxing di p. È sempre il caso che r1 == r2. Idealmente, inscatolare un dato valore primitivo p, darebbe sempre un riferimento identico. In pratica, questo potrebbe non essere fattibile utilizzando le tecniche di implementazione esistenti. Le regole di cui sopra sono un compromesso pragmatico. La clausola finale sopra richiede che determinati valori comuni siano sempre racchiusi in oggetti indistinguibili. L'implementazione può memorizzarli in cache, pigramente o impazientemente.

Per altri valori, questa formulazione non consente alcuna ipotesi sull'identità dei valori inseriti nella parte del programmatore. Ciò consentirebbe (ma non richiederà) la condivisione di alcuni o tutti questi riferimenti.

Ciò garantisce che, nei casi più comuni, il comportamento sarà quello desiderato, senza imporre una penalità di prestazioni eccessive, in particolare su dispositivi di piccole dimensioni. Ad esempio, meno implementazioni a memoria limitata potrebbero memorizzare nella cache tutti i caratteri e i corti, nonché interi e lunghi nell'intervallo di -32K - + 32K.

tl; dr

E 'impossibile per farlo funzionare alla perfezione, ed è troppo strano per non farlo funzionare a tutti. Quindi abbiamo il lavoro per "la maggior parte" del tempo.

3

Ci sono varie considerazioni. Ad esempio l'utilizzo della memoria, ma anche la semantica del linguaggio.

new Integer(1); 

è una creazione di oggetto esplicito. Sostituire questo con un operatore "get from pool" cambierebbe la semantica della lingua.

Integer.valueOf(1) 

è l'esplicito "take from pool, if in pool range". Si noti che il pool è statico e implementato in Java, non nella macchina virtuale. Puoi cercarlo: java.lang.Integer$IntegerCache. Immagino che le specifiche Java dicano che un cast da int a Integer viene effettuato inserendo una chiamata Integer.valueOf.

Ora, se si osserva come questa cache è implementata, si noterà che esiste un parametro sintonizzabile dall'utente per la dimensione della cache. Per impostazione predefinita, questa cache è costituita da un array Integer[256] che conserva copie preinizializzate per -128..127 (vale a dire l'intervallo di valori di un byte). Apparentemente, questa gamma di valori offre il miglior compromesso tra prestazioni e memoria per gli usi comuni.

Vedere ad es. http://martykopka.blogspot.de/2010/07/all-about-java-integer-cache.html per ulteriori dettagli.

Se si impiega un po 'di tempo a lavorare su calcoli numerici in Java, è do un impatto negativo della funzione di autoboxing. Per i numeri ad alte prestazioni, la prima regola empirica è di evitare qualsiasi tipo di autoboxing. GNU Trove, ad esempio, offre hashmap e strutture simili per tipi primitivi, e i benefici di runtime e memoria sono immensi.

Un oggetto Integer occupa 16 byte di memoria - 4 volte il valore di uno int. Così sopra la cache occupa circa 5kb di memoria, per esempio. Questo è qualcosa che la maggior parte delle applicazioni può sprecare. Ma ovviamente, non puoi farlo per tutti i numeri interi!

Come per le stringhe, il compilatore deve memorizzarle in modo appropriato nel file di classe. Quindi, come si memorizza una stringa in un file di classe? Il modo più semplice è quello di riscrivere il codice per qualcosa di simile:

private static final char[] chars_12345 = new char[]{ 't', 'e', 's', 't'}; 

private static final String CONST_STRING_12345 = new String(chars_12345); 

, che non si basa su alcuna manipolazione magia del tipo String. È solo una serie di primitivi avvolti. E, naturalmente, si desidera memorizzare ogni stringa univoca solo una volta per classe per ridurre le dimensioni della classe e quindi caricare il tempo.

+1

Grazie, ma preferisci rispondere alla domanda COME è fatto, non PERCHÉ. –

+0

Bene, se si guarda a come è fatto, ci sono anche commenti sul perché è implementato in questo modo, e non un altro. ;-) –

+0

Grazie per il link. –

1

È un caso di memoria e velocità. Non vuoi veramente creare 4 miliardi di oggetti Integer all'avvio della JVM perché la maggior parte delle volte non verranno mai utilizzati.

Per le stringhe, c'è anche il problema di trovare stringhe corrispondenti nel pool internato.

Le stringhe letterali nel codice sorgente sono semplici come il compilatore può trovare e organizzare internamente, ma se creo una stringa dinamicamente in fase di esecuzione, la JVM dovrebbe passare attraverso il pool e cercare ogni stringa per vedere se corrisponde e potrebbe essere riutilizzato e le stringhe possono

Mentre ci sono strutture dati che possono aiutare a velocizzare, in genere è semplicemente più semplice e veloce creare un nuovo oggetto.

+0

Trovare un oggetto corrispondente (nel pool) dovrebbe essere un problema (= richiederà un po 'di tempo e impegno) non solo per le stringhe, ma anche per altri oggetti. –

+0

@AlexKreutznaer In generale, sì. Ma a quali oggetti stai pensando specificamente? Per gli interi in un intervallo fisso, un array funziona perfettamente. – delnan

+0

Concordo, trovare un numero intero dovrebbe essere più semplice da una prospettiva di basso livello rispetto a un array di caratteri. –

1

Sede, il seguente segmento di codice:

Integer i1=1; 
Integer i2=2; 
String s1="String"; 
String s2="String"; 

Il i1, i2 sono riferimenti solo non oggetti, questi stand sono assegnati l'oggetto 1. Allo stesso modo s1, s2 sono solo riferimenti non oggetti, a loro viene assegnato l'oggetto "String".

considerando che il seguente codice:

Integer i3=new Integer(1); 
String s3=new String("String"); 

L'operatore new crea un newObject. Spero di averti autorizzato.

5

Se si tratta di un problema di memoria, perché non utilizzare la piscina tutte le volte?

Pooling tutto ogni volta è più costoso di una semplice cache che a volte non riesce: Avete bisogno di strutture dati più sofisticate (pool di piccoli interi può essere fatto con una gamma semplice e piccolo) e algoritmi, e si deve sempre andare anche se questo controllo anche quando la piscina non ti aiuterebbe. Inoltre, riunirai molti oggetti che non sono mai più necessari, il che è uno spreco di memoria: hai bisogno di più (inutili) voci della cache, e devi gestire quella cache (o soffri di tenere in vita oggetti inutili).

Se non è un problema di memoria, perché usarlo del tutto?

E è un problema di memoria. Un sacco di memoria viene salvato da questa ottimizzazione. Il pooling ogni oggetto non riduce necessariamente l'utilizzo della memoria, poiché non tutti gli oggetti vengono utilizzati molto. È un compromesso. L'approccio adottato consente di risparmiare una quantità significativa di memoria per alcuni casi di utilizzo comuni, senza rallentare altre operazioni indebitamente o sprecare molta memoria.

Problemi correlati