Dovremmo evitare la creazione di oggetti in Java?

230

Mi è stato detto da un collega che nella creazione di oggetti Java è l'operazione più costosa che si possa eseguire. Quindi posso solo concludere di creare il minor numero possibile di oggetti.

Questo sembra in qualche modo vanificare lo scopo della programmazione orientata agli oggetti. Se non stiamo creando oggetti, stiamo semplicemente scrivendo uno stile C di lunga durata, per l'ottimizzazione?

    
posta Slamice 22.05.2012 - 04:06
fonte

14 risposte

459

Il tuo collega non ha idea di cosa stiano parlando.

La tua operazione più costosa sarebbe ascoltarli . Hanno sprecato il tuo tempo indirizzandoti in modo errato a informazioni che sono scadute da oltre un decennio (a partire dalla data originale in cui è stata pubblicata questa risposta) così come devi passare il tempo a postare qui e fare ricerche su Internet per la verità.

Speriamo di rigurgitare in modo ignorante qualcosa che hanno ascoltato o letto da più di dieci anni e che non conoscono meglio. Prenderò qualsiasi altra cosa che dicono come sospetto, questo dovrebbe essere un errore noto da chiunque si aggiorni in ogni caso.

Tutto è un oggetto (eccetto primitives )

Tutto tranne le primitive ( int, long, double , ecc.) sono oggetti in Java. Non c'è modo di evitare la creazione di oggetti in Java.

La creazione di oggetti in Java a causa delle sue strategie di allocazione della memoria è più veloce di Il C ++ nella maggior parte dei casi e per tutti gli scopi pratici rispetto a qualsiasi altra cosa nella JVM può essere considerato "gratuito" .

All'inizio della fine degli anni '90, le implementazioni JVM degli anni 2000 hanno avuto un sovraccarico di prestazioni nell'assegnazione effettiva di oggetti. Questo non è stato il caso almeno dal 2005.

Se tune -Xms per supportare tutta la memoria necessaria per l'esecuzione corretta della tua applicazione, il GC potrebbe non dover mai eseguire e spazzare la maggior parte della spazzatura nelle moderne implementazioni di GC, i programmi di breve durata potrebbero mai GC a tutti .

Non cerca di massimizzare lo spazio libero, che è comunque un'aringa rossa, massimizza le prestazioni del runtime. Se ciò significa che l'heap di JVM è quasi del 100% allocato tutto il tempo, così sia. La memoria heap JVM gratuita non ti dà nulla rimanendo lì comunque.

Vi è un equivoco sul fatto che il GC libererà la memoria nel resto del sistema in un modo utile, questo è completamente falso!

L'heap JVM non cresce e si riduce in modo che il resto del sistema sia influenzato positivamente dalla memoria libera nell'heap JVM . -Xms alloca TUTTO ciò che è specificato all'avvio e la sua euristica è di non rilasciare mai realmente alcuna di quella memoria sul sistema operativo per essere condivisa con altri processi del sistema operativo fino a che l'istanza di JVM non si chiude completamente. -Xms=1GB -Xmx=1GB alloca 1 GB di RAM indipendentemente dal numero di oggetti effettivamente creati in un dato momento. Esistono alcune impostazioni che consentono di rilasciare le percentuali della memoria heap, ma per tutti gli scopi pratici la JVM non è mai in grado di rilasciare una quantità sufficiente di questa memoria affinché ciò avvenga mai , quindi nessun altro processo può recuperare questa memoria, quindi il resto del sistema non beneficia del fatto che l'Heap JVM sia libero . Un RFE per questo era "accettato" 29-NOV-2006, ma non è mai stato fatto nulla al riguardo. Questo comportamento non è considerato un problema da parte di chiunque dell'autorità.

Vi è un equivoco sul fatto che la creazione di molti piccoli oggetti di breve durata fa sì che la JVM si fermi per lunghi periodi di tempo, anche questo è falso

Gli attuali algoritmi GC sono ottimizzati per la creazione di molti molti piccoli oggetti di breve durata, ovvero l'euristica al 99% per gli oggetti Java in ogni programma. I tentativi di Pool di oggetti renderanno la JVM peggiore nella maggior parte dei casi.

Gli unici oggetti che hanno bisogno di raggruppare oggi sono oggetti che fanno riferimento a risorse finite che sono esterne alla JVM; Socket, file, connessioni database, ecc. E possono essere riutilizzati. Gli oggetti normali non possono essere raggruppati nello stesso senso delle lingue che consentono l'accesso diretto alle posizioni di memoria. L'oggetto caching è un concetto diverso e può o non può essere quello che alcune persone chiamano ingenuamente pooling , i due concetti non sono la stessa cosa e non dovrebbero essere confusi.

I moderni algoritmi GC non presentano questo problema perché non rilasciano deal in una pianificazione, ma rilasciano deallocate quando è necessaria memoria gratuita in una determinata generazione. Se l'heap è abbastanza grande, nessuna deallocazione si verifica abbastanza a lungo da causare pause.

I linguaggi dinamici orientati agli oggetti stanno battendo C anche ora i giorni in termini di calcolo sensibile test.

    
risposta data 22.05.2012 - 04:33
fonte
89

Conclusione: Non compromettere il tuo design per prendere scorciatoie creando oggetti. Evita di creare oggetti inutilmente. Se ragionevole, progettare per evitare operazioni ridondanti (di qualsiasi tipo).

Contrariamente alla maggior parte delle risposte - sì, l'assegnazione degli oggetti ha un costo associato. È un costo basso, ma è necessario evitare di creare oggetti non necessari. Come dovresti evitare qualsiasi cosa inutile nel tuo codice. I grafici a oggetti di grandi dimensioni rendono GC più lento, implicano tempi di esecuzione più lunghi in quanto probabilmente si avranno più chiamate di metodo, più errori di cache della CPU e maggiore probabilità che il processo venga scambiato su disco in casi di RAM bassa.

Prima che qualcuno batta di essere un caso limite, ho applicazioni profilate che, prima di ottimizzare, hanno creato 20 + MB di oggetti per elaborare circa 50 righe di dati. Questo va bene nei test, fino a quando non si aumentano fino a un centinaio di richieste al minuto e all'improvviso si creano 2 GB di dati al minuto. Se vuoi fare 20 reqs / sec stai creando 400MB di oggetti e poi buttali via. 20 reqs / sec è piccolo per un server decente.

    
risposta data 22.05.2012 - 04:41
fonte
60

In realtà, a causa delle strategie di gestione della memoria che il linguaggio Java (o qualsiasi altro linguaggio gestito) rende possibile, la creazione di oggetti è poco più che incrementare un puntatore in un blocco di memoria chiamato giovane generazione. È molto più veloce di C, dove è necessario cercare la memoria libera.

L'altra parte del costo è la distruzione dell'oggetto, ma è difficile da confrontare con C. Il costo di una raccolta si basa sulla quantità di oggetti salvati a lungo termine, ma la frequenza delle raccolte si basa sulla quantità di oggetti creati. .. alla fine, è ancora molto più veloce della gestione della memoria in stile C.

    
risposta data 22.05.2012 - 04:20
fonte
38

Altri poster hanno giustamente sottolineato che la creazione di oggetti è estremamente veloce in Java e che di solito non ci si deve preoccupare di esso in nessuna normale applicazione Java.

Ci sono un paio di situazioni molto speciali in cui è una buona idea per evitare la creazione di oggetti.

  • Quando stai scrivendo un'applicazione sensibile alla latenza e desideri evitare le pause GC. Più oggetti produci, più GC succede e maggiore è la possibilità di pause. Questa potrebbe essere una considerazione valida per i giochi rilevanti, alcune applicazioni multimediali, controllo robotico, trading ad alta frequenza, ecc. La soluzione è pre-allocare tutti gli oggetti / array necessari e riutilizzarli. Ci sono biblioteche specializzate nel fornire questo tipo di funzionalità, ad esempio Javolution . Ma probabilmente, se ti preoccupi della bassa latenza dovresti usare C / C ++ / assembler piuttosto che Java o C #: -)
  • Evitare i primitivi in scatola (Double, Integer ecc.) può essere una micro-ottimizzazione molto utile in determinate circostanze. Dal momento che primitive non condivise (double, int ecc.) Evitano il sovraccarico per oggetto, sono molto più veloci il lavoro intensivo della CPU come l'elaborazione numerica ecc. In genere, gli array primitivi funzionano molto bene in Java, quindi si preferiscono usare questi per il numero di crunch piuttosto che qualsiasi altro tipo di oggetto.
  • Nelle situazioni di memoria vincolata si desidera ridurre al minimo il numero di oggetti (attivi) creati poiché ogni oggetto porta un piccolo overhead (in genere 8-16 byte a seconda dell'implementazione JVM). In tali circostanze, è preferibile utilizzare un numero ridotto di oggetti o matrici di grandi dimensioni per archiviare i dati anziché un numero elevato di oggetti di piccole dimensioni.
risposta data 22.05.2012 - 13:17
fonte
17

C'è un nucleo di verità in ciò che dice il tuo collega. Suggerisco rispettosamente che il problema con l'oggetto creazione sia in realtà una raccolta . In C ++ il programmatore può controllare con precisione come viene rilasciata la memoria. Il programma può accumulare denaro per tutto il tempo o il meno possibile. Inoltre, il programma C ++ può scartare il crud utilizzando un thread diverso da quello che lo ha creato. Quindi il thread attualmente funzionante non deve mai fermarsi per ripulire.

Al contrario, Java Virtual Machine (JVM) interrompe periodicamente il codice per recuperare la memoria non utilizzata. La maggior parte degli sviluppatori Java non si accorge mai di questa pausa, dal momento che è di solito poco frequente e molto breve. Più la tua JVM si accumula o più è vincolata, più frequenti sono queste pause. Puoi utilizzare strumenti come VisualVM per visualizzare questo processo.

Nelle ultime versioni di Java, l'algoritmo della garbage collection (GC) può essere sintonizzato . Come regola generale, minore è il tempo di esecuzione delle pause, più costoso è il sovraccarico nella macchina virtuale (ovvero CPU e memoria utilizzate per coordinare il processo GC).

Quando potrebbe questo? Ogni volta che ti preoccupi dei costanti tassi di risposta inferiori al millisecondo, ti preoccuperai di GC. Sistemi di trading automatizzati scritti in Java ottimizzano pesantemente la JVM per minimizzare le pause. Le aziende che altrimenti scriverebbero Java si rivolgono a C ++ in situazioni in cui i sistemi devono essere sempre molto reattivi.

Per la cronaca, non condoni l'evitamento degli oggetti in generale! Predefinito per programmazione orientata agli oggetti. Regola questo approccio solo se il GC si mette sulla tua strada, e solo dopo aver provato a sintonizzare la JVM per mettere in pausa per meno tempo. Un buon libro sull'ottimizzazione delle prestazioni Java è Prestazioni Java di Charlie Hunt e Binu John.

    
risposta data 22.05.2012 - 05:34
fonte
11

C'è un caso in cui potresti essere scoraggiato dal creare troppi oggetti in Java a causa del sovraccarico - progettazione per prestazioni sulla piattaforma Android

Oltre a ciò, le risposte di cui sopra sono vere.

    
risposta data 22.05.2012 - 10:30
fonte
8

il GC è ottimizzato per molti oggetti di breve durata

che dice che se riesci banalmente a ridurre l'allocazione degli oggetti dovresti

un esempio sarebbe la costruzione di una stringa in un ciclo, il modo ingenuo sarebbe

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}

che crea un nuovo oggetto String su ciascuna operazione += (più un StringBuilder e il nuovo array di caratteri sottostanti)

puoi facilmente riscriverlo in:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();

questo modello (risultato immutabile e un valore intermedio mutabile locale) può essere applicato anche ad altre cose

ma a parte questo dovresti aprire un profiler per trovare il vero collo di bottiglia invece di inseguire i fantasmi

    
risposta data 22.05.2012 - 10:43
fonte
5

Questo dipende molto dall'applicazione specifica, quindi è davvero difficile dirlo in generale. Tuttavia, sarei piuttosto sorpreso se la creazione dell'oggetto fosse in realtà un collo di bottiglia delle prestazioni in un'applicazione. Anche se sono lenti, il vantaggio in stile codice probabilmente supererà le prestazioni (a meno che non sia effettivamente visibile per l'utente)

In ogni caso, non dovresti nemmeno cominciare a preoccuparti di queste cose finché non avrai profilato il tuo codice per determinare i colli di bottiglia effettivi delle prestazioni invece di indovinare. Fino ad allora, dovresti fare ciò che è meglio per la leggibilità del codice, non per le prestazioni.

    
risposta data 22.05.2012 - 04:08
fonte
5

Joshua Bloch (uno dei creatori di piattaforme Java) ha scritto nel suo libro Efficace Java in 2001:

Avoiding object creation by maintaining your own object pool is a bad idea unless the objects in the pool are extremely heavyweight. A prototypical example of an object that does justify an object pool is a database connection. The cost of establishing the connection is sufficiently high that it makes sense to reuse these objects. Generally speaking, however, maintaining your own object pools clutters up your code, increases memory footprint, and harms performance. Modern JVM implementations have highly optimized garbage collectors that easily outperform such object pools on lightweight objects.

    
risposta data 03.10.2014 - 19:23
fonte
3

Penso che il tuo collega debba aver detto dal punto di vista della creazione di oggetti non necessari. Voglio dire se stai creando lo stesso oggetto frequentemente, allora è meglio condividere quell'oggetto. Anche nei casi in cui la creazione di oggetti è complessa e richiede più memoria, è possibile clonare quell'oggetto ed evitare di creare quel processo di creazione di oggetti complessi (ma ciò dipende dal requisito). Penso che l'affermazione "La creazione di oggetti è costosa" dovrebbe essere presa nel contesto.

Per quanto riguarda i requisiti di memoria JVM, aspetta Java 8, non hai nemmeno bisogno di specificare -Xmx, le impostazioni di metadati si prenderanno cura della necessità della memoria JVM e cresceranno automaticamente.

    
risposta data 01.08.2013 - 23:48
fonte
1

C'è di più nella creazione di una classe piuttosto che nell'allocare memoria. C'è anche l'inizializzazione, non sono sicuro del perché quella parte non sia coperta da tutte le risposte. Le classi normali contengono alcune variabili e fanno qualche forma di inizializzazione, e ciò non è gratuito. A seconda della classe, può leggere i file o eseguire qualsiasi altro numero di operazioni lente.

Quindi considera ciò che fa il costruttore della classe, prima di capire se è gratuito o meno.

    
risposta data 22.05.2012 - 23:59
fonte
1

Il GC di Java è in realtà molto ottimizzato in termini di creazione di molti oggetti rapidamente in modo "burst". Da quello che posso capire usano un allocatore sequenziale (l'allocatore di O (1) più veloce e più semplice per le richieste di dimensioni variabili) per quel tipo di "burst cycle" in uno spazio di memoria che chiamano "Eden space", e solo se gli oggetti persistono dopo un ciclo di GC vengono spostati in un luogo in cui il GC può raccoglierli uno per uno.

Detto questo, se le tue esigenze di prestazioni diventano abbastanza critiche (come misurato con i reali requisiti di fine utente), allora gli oggetti portano un sovraccarico, ma non ci penso molto in termini di creazione / allocazione. Ha più a che fare con la località di riferimento e la dimensione aggiuntiva di tutti gli oggetti in Java come richiesto per supportare concetti come reflection e dynamic dispatch ( Float è maggiore di float , spesso qualcosa come 4 volte più grande su 64-bit con i suoi requisiti di allineamento e una matrice di Float non è necessariamente garantita per essere archiviata contigua da ciò che ho capito).

Una delle cose più accattivanti che abbia mai visto sviluppate in Java che mi ha fatto considerare un concorrente dei pesi massimi nel mio campo (VFX) era un tracciante di percorso standard multithread interattivo (che non utilizzava il caching di irradianza o BDPT o MLS o altro altro) sulla CPU che fornisce anteprime in tempo reale che convergono piuttosto rapidamente in un'immagine senza rumore. Ho lavorato con professionisti in C ++ dedicando le loro carriere a cose del genere con fantasiosi profiler in mano che hanno avuto difficoltà a farlo.

Ma ho guardato intorno al codice sorgente e mentre utilizzava molti oggetti a un costo insignificante, le parti più critiche del tracciatore percorso (il BVH e triangoli e materiali) evitavano molto chiaramente e deliberatamente oggetti a favore di grandi matrici di tipi primitivi (per lo più float[] e int[] ), che hanno fatto usare molto meno memoria e localita 'spaziale garantita per ottenere da un float nella matrice al successivo. Non penso che sia troppo speculativo pensare che, se l'autore usasse tipi in scatola come Float , sarebbe arrivato a un costo piuttosto elevato per le sue prestazioni. Ma stiamo parlando della parte più assolutamente critica di quel motore, e sono abbastanza sicuro, dato quanto abilmente lo ha ottimizzato lo sviluppatore, che ha misurato e applicato quell'ottimizzazione in modo molto, molto giudizioso, dal momento che ha felicemente utilizzato oggetti ovunque con costo insignificante per il suo impressionante path tracciante in tempo reale.

I was told by a colleague that in Java object creation is the most expensive operation you could perform. So I can only conclude to create as few objects as possible.

Anche in un campo così critico dal punto di vista delle prestazioni come il mio, non scriverai prodotti efficienti se tieni nei posti che non contano. Direi anche che i campi più critici per le prestazioni potrebbero aumentare la domanda di produttività, dal momento che abbiamo bisogno di tutto il tempo in più che possiamo ottenere per mettere a punto quegli hotspot che contano davvero non sprecando tempo in cose che non . Come con l'esempio del tracciato del percorso dall'alto, l'autore ha abilmente e giudiziosamente applicato tali ottimizzazioni solo ai luoghi che veramente, veramente importanti, e probabilmente a posteriori dopo aver misurato, e ancora felicemente usato oggetti ovunque.

    
risposta data 20.12.2018 - 23:39
fonte
0

Come si è detto, la creazione di oggetti non è un grosso costo in Java (ma scommetto più grande della maggior parte delle operazioni semplici come l'aggiunta, ecc.) e non dovresti evitarlo troppo.

Questo ha detto che è ancora un costo, ea volte potresti trovarti a cercare di eliminare il maggior numero possibile di oggetti. Ma solo dopo il profiling ha dimostrato che questo è un problema.

Ecco una grande presentazione sull'argomento: link

    
risposta data 24.05.2012 - 04:56
fonte
0

Ho fatto un rapido microbenchmark a riguardo e ho fornito fonti complete in github. La mia conclusione è che se la creazione di oggetti è costosa o meno, non è il problema, ma la creazione continua di oggetti con la nozione che il GC si prenderà cura di te farà sì che la tua applicazione inneschi il processo di GC prima. GC è un processo molto costoso ed è meglio evitarlo ogni volta che è possibile e non provare a spingerlo per dare il calcio.

    
risposta data 25.01.2015 - 02:24
fonte