Creazione di una perdita di memoria con Java


Ho appena avuto un'intervista e mi è stato chiesto di creare una perdita di memoria con Java. Inutile dire che mi sono sentito abbastanza stupido non avendo idea di come iniziare a crearne uno.

Quale sarebbe un esempio?

Author: Mat Banik, 2011-06-24

30 answers

Ecco un buon modo per creare una vera perdita di memoria (oggetti inaccessibili eseguendo il codice ma ancora memorizzati in memoria) in puro Java:

  1. L'applicazione crea un thread di lunga durata (o utilizza un pool di thread per perdere ancora più velocemente).
  2. Il thread carica una classe tramite un ClassLoader (opzionalmente personalizzato).
  3. La classe alloca un grande blocco di memoria (ad esempio new byte[1000000]), memorizza un riferimento forte ad esso in un campo statico e quindi memorizza un riferimento a se stesso in un ThreadLocal. L'allocazione della memoria extra è facoltativa (la perdita dell'istanza di classe è sufficiente), ma renderà la perdita molto più veloce.
  4. Il thread cancella tutti i riferimenti alla classe personalizzata o al ClassLoader da cui è stato caricato.
  5. Ripetere.

Funziona perché ThreadLocal mantiene un riferimento all'oggetto, che mantiene un riferimento alla sua Classe, che a sua volta mantiene un riferimento al suo ClassLoader. Il ClassLoader, a sua volta, mantiene un riferimento a tutte le classi che ha caricare.

(Era peggio in molte implementazioni JVM, specialmente prima di Java 7, perché Classi e classloader erano allocati direttamente in permgen e non erano mai stati GC'd. Tuttavia, indipendentemente dal modo in cui la JVM gestisce lo scarico della classe, un ThreadLocal impedirà comunque il recupero di un oggetto di classe.)

Una variazione su questo modello è il motivo per cui i contenitori di applicazioni (come Tomcat) possono perdere memoria come un setaccio se si ridistribuiscono frequentemente le applicazioni che si utilizzano ThreadLocals in qualsiasi modo. (Poiché il contenitore dell'applicazione utilizza i thread come descritto e ogni volta che si ridistribuisce l'applicazione viene utilizzato un nuovo ClassLoader.)

Update: Poiché molte persone continuano a chiederlo, ecco un codice di esempio che mostra questo comportamento in azione.

 1994
Author: Daniel Pryden, 2017-09-06 02:56:18

Campo statico con riferimento all'oggetto [campo finale esp]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Chiamata String.intern() sulla stringa lunga

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Non chiuso) flussi aperti (file, rete ecc... )

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Connessioni non chiuse

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Aree non raggiungibili dal garbage collector di JVM , come la memoria allocata tramite metodi nativi

Nelle applicazioni Web, alcuni oggetti vengono memorizzati nell'ambito dell'applicazione fino a quando l'applicazione viene esplicitamente interrotta o rimossa.

getServletContext().setAttribute("SOME_MAP", map);

Opzioni JVM errate o inappropriate , come l'opzione noclassgc su IBM JDK che impedisce la garbage collection di classe non utilizzata

Vedere Impostazioni IBM jdk .

 1087
Author: Prashant Bhate, 2018-01-30 23:32:27

Una cosa semplice da fare è usare un HashSet con un hashCode() o equals() errato (o inesistente), e quindi continuare ad aggiungere "duplicati". Invece di ignorare i duplicati come dovrebbe, il set crescerà sempre e solo e non sarai in grado di rimuoverli.

Se vuoi che queste chiavi/elementi non validi rimangano in giro puoi usare un campo statico come

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
 405
Author: Peter Lawrey, 2011-06-24 21:50:56

Di seguito ci sarà un caso non ovvio in cui Java perde, oltre al caso standard di ascoltatori dimenticati, riferimenti statici, chiavi false/modificabili in hashmap o solo thread bloccati senza alcuna possibilità di terminare il loro ciclo di vita.

  • File.deleteOnExit() - perde sempre la stringa, se la stringa è una sottostringa, la perdita è ancora peggiore (anche il carattere[] sottostante è trapelato) - in Java 7 la sottostringa copia anche il char[], quindi il successivo non si applica; @ Daniel, no needs per i voti, però.

Mi concentrerò sui thread per mostrare il pericolo di thread non gestiti per lo più, non voglio nemmeno toccare swing.

  • Runtime.addShutdownHook e non rimuovere... e poi anche con removeShutdownHook a causa di un bug nella classe ThreadGroup riguardante i thread non avviati potrebbe non essere raccolto, in modo efficace perdere il ThreadGroup. JGroup ha la perdita in GossipRouter.

  • Creando, ma non avviando, un Thread entra nella stessa categoria di sopra.

  • La creazione di un thread eredita ContextClassLoader e AccessControlContext, più ThreadGroup e qualsiasi InheritedThreadLocal, tutti questi riferimenti sono potenziali perdite, insieme alle intere classi caricate dal classloader e tutti i riferimenti statici e ja-ja. L'effetto è particolarmente visibile con l'intero framework j.u.c.Executor che presenta un'interfaccia ThreadFactory super semplice, ma la maggior parte degli sviluppatori non ha idea del pericolo in agguato. Anche molte librerie iniziano i thread su richiesta (troppi settori biblioteche popolari).

  • ThreadLocal cache; quelli sono malvagi in molti casi. Sono sicuro che tutti hanno visto un po ' di cache semplici basate su ThreadLocal, beh la cattiva notizia: se il thread continua ad andare più del previsto la vita del ClassLoader di contesto, è una pura piccola perdita. Non utilizzare cache ThreadLocal a meno che non sia realmente necessario.

  • Chiamare ThreadGroup.destroy() quando il ThreadGroup non ha thread, ma mantiene comunque i threadgroup figli. Una cattiva perdita che impedirà il ThreadGroup da rimuovere dal suo genitore, ma tutti i bambini diventano non enumerabili.

  • Utilizzando WeakHashMap e il valore (in)fa riferimento direttamente alla chiave. Questo è difficile da trovare senza una discarica di heap. Questo vale per tutti gli estesi Weak/SoftReference che potrebbero mantenere un riferimento rigido all'oggetto custodito.

  • Utilizzo di java.net.URL con il protocollo HTTP(S) e caricamento della risorsa da(!). Questo è speciale, il KeepAliveCache crea un nuovo thread nel gruppo di thread di sistema che perde il classloader di contesto del thread corrente. Il thread viene creato alla prima richiesta quando non esiste un thread vivo, quindi potresti essere fortunato o semplicemente perdere. La perdita è già stata risolta in Java 7 e il codice che crea il thread rimuove correttamente il classloader di contesto. Ci sono pochi altri casi (come ImageFetcher, risolto anche) di creare thread simili.

  • Utilizzando InflaterInputStream passando new java.util.zip.Inflater() nel costruttore (PNGImageDecoder per esempio) e non chiamare end() del gonfiatore. Bene, se passi nel costruttore con solo new, nessuna possibilità... E sì, chiamare close() sullo stream non chiude il gonfiatore se viene passato manualmente come parametro del costruttore. Questa non è una vera perdita dal momento che sarebbe stata rilasciata dal finalizzatore... quando lo ritiene necessario. Fino a quel momento mangia la memoria nativa così male che può causare Linux oom_killer per uccidere il processo impunemente. Il problema principale è che la finalizzazione in Java è molto inaffidabile e G1 ha peggiorato le cose fino al 7.0.2. Morale della storia: rilascia le risorse native il prima possibile; il finalizzatore è troppo povero.

  • Lo stesso caso con java.util.zip.Deflater. Questo è molto peggio dal momento che Deflater ha fame di memoria in Java, cioè usa sempre 15 bit (max) e 8 livelli di memoria (9 è max) allocando diverse centinaia di KB di memoria nativa. Fortunatamente, Deflater non è ampiamente utilizzato e per quanto ne so JDK non contiene abusi. Chiamare sempre end() se si crea manualmente un Deflater o Inflater. La parte migliore degli ultimi due: non è possibile trovarli tramite i normali strumenti di profilazione disponibili.

(Posso aggiungere altre perdite di tempo che ho incontrato su richiesta.)

Buona fortuna e stare al sicuro; le perdite sono il male!

 237
Author: bestsss, 2016-02-16 15:52:26

La maggior parte degli esempi qui sono "troppo complessi". Sono casi limite. Con questi esempi, il programmatore ha commesso un errore (come non ridefinire equals/hashcode), o è stato morso da un caso d'angolo della JVM/JAVA (carico di classe con static...). Penso che non sia il tipo di esempio che un intervistatore desidera o anche il caso più comune.

Ma ci sono casi davvero più semplici per perdite di memoria. Il garbage collector libera solo ciò che non è più referenziato. Noi come sviluppatori Java non ci preoccupiamo memoria. Lo assegniamo quando necessario e lasciamo che sia liberato automaticamente. Raffinato.

Ma qualsiasi applicazione longeva tende ad avere uno stato condiviso. Può essere qualsiasi cosa, statica, single... Spesso le applicazioni non banali tendono a creare grafici di oggetti complessi. Basta dimenticare di impostare un riferimento a null o più spesso dimenticare di rimuovere un oggetto da una raccolta è sufficiente per creare una perdita di memoria.

Naturalmente tutti i tipi di ascoltatori (come gli ascoltatori dell'interfaccia utente), cache o qualsiasi stato condiviso di lunga durata tendono a produrre perdite di memoria se non gestite correttamente. Ciò che deve essere compreso è che questo non è un caso d'angolo Java o un problema con il garbage collector. È un problema di progettazione. Progettiamo di aggiungere un listener a un oggetto di lunga durata, ma non lo rimuoviamo quando non è più necessario. Nascondiamo gli oggetti, ma non abbiamo alcuna strategia per rimuoverli dalla cache.

Forse abbiamo un grafico complesso che memorizza lo stato precedente necessario per un calcolo. Ma lo stato precedente è stesso legato allo stato prima e così via.

Come se dovessimo chiudere connessioni o file SQL. Dobbiamo impostare i riferimenti corretti a null e rimuovere gli elementi dalla raccolta. Avremo strategie di caching appropriate (dimensione massima della memoria, numero di elementi o timer). Tutti gli oggetti che consentono a un listener di essere notificato devono fornire sia un metodo addListener che removeListener. E quando questi notificanti non vengono più utilizzati, devono cancellare la loro lista di ascoltatori.

Una perdita di memoria è infatti veramente possibile ed è perfettamente prevedibile. Non c'è bisogno di funzioni linguistiche speciali o casi d'angolo. Le perdite di memoria sono un indicatore che forse manca qualcosa o addirittura di problemi di progettazione.

 165
Author: Nicolas Bousquet, 2018-02-13 07:27:47

La risposta dipende interamente da ciò che l'intervistatore pensava di chiedere.

È possibile in pratica creare perdite Java? Certo che lo è, e ci sono molti esempi nelle altre risposte.

Ma ci sono più meta-domande che potrebbero essere state poste?

  • Un'implementazione Java teoricamente "perfetta" è vulnerabile alle perdite?
  • Il candidato capisce la differenza tra teoria e realtà?
  • Il candidato capire come funziona la garbage collection?
  • O come dovrebbe funzionare la garbage collection in un caso ideale?
  • Sanno di poter chiamare altre lingue attraverso interfacce native?
  • Sanno di perdere memoria in quelle altre lingue?
  • Il candidato sa anche cos'è la gestione della memoria e cosa sta succedendo dietro le quinte in Java?

Sto leggendo la tua meta-domanda come "Qual è una risposta che avrei potuto usare in questa situazione di intervista". E quindi, mi concentrerò sulle abilità di intervista invece di Java. Credo che sia più probabile che tu ripeta la situazione di non conoscere la risposta a una domanda in un'intervista di quanto tu debba essere in un posto in cui devi sapere come far perdere Java. Quindi, si spera, questo aiuterà.

Una delle abilità più importanti che puoi sviluppare per intervistare è imparare ad ascoltare attivamente le domande e lavorare con l'intervistatore per estrarre il loro intento. Non solo questo ti consente di rispondere alla loro mettere in discussione il modo in cui vogliono, ma dimostra anche che avete alcune abilità di comunicazione vitali. E quando si tratta di una scelta tra molti sviluppatori altrettanto talentuosi, assumerò colui che ascolta, pensa e capisce prima che rispondano ogni volta.

 138
Author: PlayTank, 2015-01-27 12:52:33

Il seguente è un esempio piuttosto inutile, se non capisci JDBC. O almeno come JDBC si aspetta che uno sviluppatore chiuda Connection, Statement e ResultSet istanze prima di scartarle o perdere riferimenti ad esse, invece di fare affidamento sull'implementazione di finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Il problema con quanto sopra è che l'oggetto Connection non è chiuso, e quindi la connessione fisica rimarrà aperta, fino a quando il garbage collector non si avvicina e vede che è irraggiungibile. CG invocherà il metodo finalize, ma ci sono driver JDBC che non implementano finalize, almeno non nello stesso modo in cui Connection.close è implementato. Il comportamento risultante è che mentre la memoria verrà recuperata a causa della raccolta di oggetti non raggiungibili, le risorse (inclusa la memoria) associate all'oggetto Connection potrebbero semplicemente non essere recuperate.

In un evento in cui il metodo Connection di finalize non ripulisce tutto, si potrebbe effettivamente scoprire che la connessione fisica al il server di database durerà diversi cicli di garbage collection, fino a quando il server di database non capirà che la connessione non è attiva (se lo fa) e dovrebbe essere chiusa.

Anche se il driver JDBC dovesse implementare finalize, è possibile che vengano generate delle eccezioni durante la finalizzazione. Il comportamento risultante è che qualsiasi memoria associata all'oggetto ora "dormiente" non verrà recuperata, poiché finalize è garantito per essere invocato solo una volta.

Lo scenario di cui sopra di l'incontro di eccezioni durante la finalizzazione dell'oggetto è correlato a un altro scenario che potrebbe portare a una perdita di memoria - resurrezione dell'oggetto. La resurrezione dell'oggetto viene spesso eseguita intenzionalmente creando un forte riferimento all'oggetto da finalizzare, da un altro oggetto. Quando la resurrezione dell'oggetto viene utilizzata in modo improprio, porterà a una perdita di memoria in combinazione con altre fonti di perdite di memoria.

Ci sono molti altri esempi che puoi evocare - come

  • Gestione di un'istanza List in cui si sta solo aggiungendo all'elenco e non eliminando da esso (anche se si dovrebbe eliminare gli elementi che non sono più necessari), o
  • Apre Socket s o File s, ma non li chiude quando non sono più necessari (simile all'esempio precedente che coinvolge la classe Connection).
  • Non scaricare Singleton quando si abbassa un'applicazione Java EE. Apparentemente, il Classloader che ha caricato la classe singleton manterrà un riferimento alla classe, e quindi l'istanza singleton non verrà mai raccolta. Quando viene distribuita una nuova istanza dell'applicazione, di solito viene creato un nuovo caricatore di classi e il caricatore di classi precedente continuerà ad esistere a causa del singleton.
 117
Author: Vineet Reynolds, 2016-02-16 15:38:36

Probabilmente uno degli esempi più semplici di una potenziale perdita di memoria, e come evitarlo, è l'implementazione di ArrayList.rimuovere (int):

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Se lo stessi implementando tu stesso, avresti pensato di cancellare l'elemento dell'array che non è più utilizzato (elementData[--size] = null)? Quel riferimento potrebbe mantenere in vita un oggetto enorme ...

 106
Author: meriton, 2015-09-01 04:50:05

Ogni volta che si mantengono riferimenti a oggetti di cui non è più necessario, si verifica una perdita di memoria. Vedere Gestione delle perdite di memoria nei programmi Java per esempi di come le perdite di memoria si manifestano in Java e cosa si può fare al riguardo.

 64
Author: Bill the Lizard, 2011-06-24 16:18:07

È possibile creare perdite di memoria con sun.varie.Classe non sicura . In effetti questa classe di servizio viene utilizzata in diverse classi standard (ad esempio in java.nio classi). Non è possibile creare direttamente un'istanza di questa classe, ma è possibile utilizzare reflection per farlo.

Il codice non viene compilato in Eclipse IDE-compilalo usando il comando javac (durante la compilazione riceverai avvisi)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}
 46
Author: stemm, 2016-01-05 08:37:35

Posso copiare la mia risposta da qui: Il modo più semplice per causare perdite di memoria in Java?

"Una perdita di memoria, in informatica (o perdita, in questo contesto), si verifica quando un programma per computer consuma memoria ma non è in grado di rilasciarla al sistema operativo."(Wikipedia)

La risposta facile è: Non puoi. Java gestisce automaticamente la memoria e libererà risorse che non sono necessarie per te. Non puoi impedire che accada. Sarà SEMPRE in grado di rilasciare il resources. Nei programmi con gestione manuale della memoria, questo è diverso. Puoi ottenere un po ' di memoria in C usando malloc(). Per liberare la memoria, è necessario il puntatore restituito da malloc e chiamare free() su di esso. Ma se non hai più il puntatore (sovrascritto o superato la durata), sfortunatamente non sei in grado di liberare questa memoria e quindi hai una perdita di memoria.

Tutte le altre risposte finora sono nella mia definizione non proprio perdite di memoria. Tutti mirano a riempire la memoria con roba inutile molto veloce. Ma in qualsiasi momento potresti ancora dereferenziare gli oggetti che hai creato e quindi liberare la memoria - > NESSUNA PERDITA. la risposta di acconrad si avvicina piuttosto, come devo ammettere, poiché la sua soluzione è effettivamente quella di "mandare in crash" il garbage collector forzandolo in un ciclo infinito).

La risposta lunga è: È possibile ottenere una perdita di memoria scrivendo una libreria per Java usando il JNI, che può avere una gestione manuale della memoria e quindi avere perdite di memoria. Se tu chiama questa libreria, il tuo processo java perderà memoria. Oppure, puoi avere bug nella JVM, in modo che la JVM perda memoria. Probabilmente ci sono bug nella JVM, potrebbero esserci anche alcuni noti poiché la garbage collection non è così banale, ma è ancora un bug. In base alla progettazione questo non è possibile. Potresti chiedere del codice java che viene effettuato da un tale bug. Mi dispiace non ne conosco uno e potrebbe non essere più un bug nella prossima versione Java comunque.

 40
Author: yankee, 2017-05-23 11:47:29

Ecco un semplice / sinistro via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Poiché la sottostringa si riferisce alla rappresentazione interna della stringa originale, molto più lunga, l'originale rimane in memoria. Quindi, finché hai un StringLeaker in gioco, hai anche l'intera stringa originale in memoria, anche se potresti pensare che ti stai solo aggrappando a una stringa di caratteri singoli.

Il modo per evitare di memorizzare un indesiderato il riferimento alla stringa originale è fare qualcosa del genere:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

Per aggiungere cattiveria, potresti anche .intern() la sottostringa:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

In questo modo manterrà sia la stringa lunga originale che la sottostringa derivata in memoria anche dopo che l'istanza StringLeaker è stata scartata.

 33
Author: Jon Chambers, 2011-07-21 19:00:35

Un esempio comune di questo nel codice GUI è quando si crea un widget/componente e si aggiunge un listener a un oggetto con ambito statico/applicazione e quindi non si rimuove il listener quando il widget viene distrutto. Non solo si ottiene una perdita di memoria, ma anche un colpo di prestazioni come quando qualunque cosa si sta ascoltando incendi eventi, tutti i vostri vecchi ascoltatori sono chiamati troppo.

 33
Author: pauli, 2016-02-16 15:40:24

Prendere qualsiasi applicazione web in esecuzione in qualsiasi contenitore servlet (Tomcat, Jetty, Glassfish, qualunque cosa...). Ridistribuire l'app 10 o 20 volte di seguito (potrebbe essere sufficiente toccare semplicemente la GUERRA nella directory autodeploy del server.

A meno che qualcuno non lo abbia effettivamente testato, è probabile che si ottenga un OutOfMemoryError dopo un paio di ridistribuzioni, perché l'applicazione non si è occupata di ripulire da sola. Potresti anche trovare un bug nel tuo server con questo test.

Il problema è che la durata del contenitore è più lunga della durata dell'applicazione. È necessario assicurarsi che tutti i riferimenti che il contenitore potrebbe avere agli oggetti o alle classi della propria applicazione possano essere raccolti in garbage.

Se c'è un solo riferimento sopravvissuto alla non distribuzione della tua app Web, il classloader corrispondente e di conseguenza tutte le classi della tua app Web non possono essere raccolte in garbage.

Thread avviati dall'applicazione, ThreadLocal variabili, appendici di registrazione sono alcuni dei soliti sospetti per causare perdite classloader.

 32
Author: Harald Wellmann, 2011-07-03 22:41:17

Forse usando il codice nativo esterno tramite JNI?

Con Java puro, è quasi impossibile.

Ma si tratta di un tipo "standard" di perdita di memoria, quando non è più possibile accedere alla memoria, ma è ancora di proprietà dell'applicazione. È invece possibile mantenere i riferimenti agli oggetti inutilizzati o aprire i flussi senza chiuderli in seguito.

 29
Author: Rogach, 2011-06-24 21:48:48

Ho avuto una bella "perdita di memoria" in relazione all'analisi PermGen e XML una volta. Il parser XML che abbiamo usato (non ricordo quale fosse) ha fatto una stringa.intern () sui nomi dei tag, per rendere il confronto più veloce. Uno dei nostri clienti ha avuto la grande idea di memorizzare i valori dei dati non in attributi XML o testo, ma come tagnames, quindi abbiamo avuto un documento come:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

Infatti, non utilizzavano numeri ma ID testuali più lunghi (circa 20 caratteri), che erano unici e arrivavano a un tasso di 10-15 milioni giorno. Ciò rende 200 MB di spazzatura al giorno, che non è mai più necessario e mai GCed (poiché è in PermGen). Avevamo permgen impostato su 512 MB, quindi ci sono voluti circa due giorni prima che arrivasse l'eccezione out-of-memory (OOME)...

 28
Author: Ron, 2016-02-16 15:44:24

Recentemente ho riscontrato una situazione di perdita di memoria causata in un modo da log4j.

Log4j ha questo meccanismo chiamato Nested Diagnostic Context(NDC) che è uno strumento per distinguere l'output del log interleaved da fonti diverse. La granularità in cui NDC funziona è thread, quindi distingue separatamente gli output di log da thread diversi.

Per memorizzare tag specifici del thread, la classe NDC di log4j utilizza una Hashtable che viene digitata dall'oggetto Thread stesso (come opposto a dire l'id del thread), e quindi fino a quando il tag NDC rimane in memoria anche tutti gli oggetti che pendono dall'oggetto thread rimangono in memoria. Nella nostra applicazione web usiamo NDC per contrassegnare i logoutputs con un ID di richiesta per distinguere separatamente i log da una singola richiesta. Il contenitore che associa il tag NDC a un thread, lo rimuove anche mentre restituisce la risposta da una richiesta. Il problema si è verificato quando durante il corso dell'elaborazione di una richiesta, è stato generato un thread figlio, qualcosa come il seguente codice:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Quindi un contesto NDC è stato associato al thread inline che è stato generato. L'oggetto thread che era la chiave per questo contesto NDC, è il thread in linea che ha l'oggetto hugeList appeso fuori di esso. Quindi, anche dopo che il thread ha finito di fare ciò che stava facendo, il riferimento a hugeList è stato mantenuto vivo dal contesto NDC Hastable, causando così una perdita di memoria.

 22
Author: Puneet, 2014-03-07 21:29:39

Ho pensato che fosse interessante che nessuno usasse gli esempi di classe interni. Se si dispone di una classe interna; mantiene intrinsecamente un riferimento alla classe contenente. Ovviamente non è tecnicamente una perdita di memoria perché Java alla fine lo ripulirà; ma questo può causare che le classi si blocchino più a lungo del previsto.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Ora se chiami Example1 e ottieni un Example2 scartando Example1, avrai intrinsecamente ancora un collegamento a un oggetto Example1.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Ho ho anche sentito una voce che se hai una variabile che esiste più a lungo di una specifica quantità di tempo; Java presuppone che esisterà sempre e in realtà non proverà mai a ripulirlo se non può più essere raggiunto nel codice. Ma questo è completamente non verificato.

 21
Author: Suroot, 2011-07-09 01:23:55

Cos'è una perdita di memoria:

  • È causato da un bug o cattivo design.
  • E ' uno spreco di memoria.
  • Peggiora col passare del tempo.
  • Il garbage collector non può pulirlo.

Esempio tipico:

Una cache di oggetti è un buon punto di partenza per rovinare le cose.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

La tua cache cresce e cresce. E ben presto l'intero database viene risucchiato nella memoria. Un design migliore utilizza un LRUMap (Mantiene solo gli oggetti usati di recente nella cache).

Certo, puoi rendere le cose molto più complicate:

  • utilizzando ThreadLocal costruzioni.
  • aggiunta di più alberi di riferimento complessi .
  • o perdite causate da librerie di terze parti .

Cosa succede spesso:

Se questo oggetto Info ha riferimenti ad altri oggetti, che di nuovo hanno riferimenti ad altri oggetti. In un certo senso si potrebbe anche considerare questo per essere un qualche tipo perdita di memoria, (causata da un cattivo design).

 20
Author: bvdb, 2014-10-28 13:18:50

Crea una mappa statica e continua ad aggiungere riferimenti duri ad essa. Quelli non saranno mai GC'd.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
 17
Author: duffymo, 2011-07-21 17:32:58

È possibile creare una perdita di memoria in movimento creando una nuova istanza di una classe nel metodo finalize di tale classe. Punti bonus se il finalizzatore crea più istanze. Ecco un semplice programma che perde l'intero heap in pochi secondi e pochi minuti a seconda della dimensione dell'heap:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}
 16
Author: sethobrien, 2011-07-22 08:05:07

L'intervistatore stava probabilmente cercando un riferimento circolare come il codice qui sotto (che incidentalmente perde solo memoria in JVM molto vecchie che usavano il conteggio dei riferimenti, che non è più il caso). Ma è una domanda piuttosto vaga, quindi è un'opportunità privilegiata per mostrare la tua comprensione della gestione della memoria JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Quindi puoi spiegare che con il conteggio dei riferimenti, il codice precedente perderebbe memoria. Ma la maggior parte delle JVM moderne non usa più il conteggio dei riferimenti, la maggior parte utilizzare un garbage collector sweep, che in realtà raccogliere questa memoria.

Successivamente potresti spiegare la creazione di un oggetto che ha una risorsa nativa sottostante, in questo modo:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Quindi puoi spiegare che questa è tecnicamente una perdita di memoria, ma in realtà la perdita è causata dal codice nativo nella JVM che alloca le risorse native sottostanti, che non sono state liberate dal tuo codice Java.

Alla fine della giornata, con una JVM moderna, è necessario scrivere un codice Java che alloca un nativo risorsa al di fuori del normale ambito di consapevolezza della JVM.

 16
Author: deltamind106, 2017-10-13 19:09:24

Tutti dimenticano sempre la route del codice nativo. Ecco una semplice formula per una perdita:

  1. Dichiarare il metodo nativo.
  2. Nel metodo nativo, chiamare malloc. Non chiamare free.
  3. Chiama il metodo nativo.

Ricorda, le allocazioni di memoria nel codice nativo provengono dall'heap JVM.

 15
Author: Paul Morie, 2011-07-21 20:52:40

Mi sono imbattuto in un tipo più sottile di perdita di risorse di recente. Apriamo le risorse tramite getResourceAsStream del caricatore di classi ed è successo che gli handle del flusso di input non sono stati chiusi.

Uhm, si potrebbe dire, che idiota.

Bene, ciò che rende questo interessante è: in questo modo, puoi perdere memoria heap del processo sottostante, piuttosto che dall'heap di JVM.

Tutto ciò che serve è un file jar con un file all'interno del quale verrà fatto riferimento dal codice Java. Più grande è il file jar, la memoria più veloce viene allocata.

Si può facilmente creare un tale vaso con la seguente classe:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Basta incollare in un file chiamato BigJarCreator.java, compilarlo ed eseguirlo dalla riga di comando:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: trovi un archivio jar nella tua attuale directory di lavoro con due file all'interno.

Creiamo una seconda classe:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Questa classe fondamentalmente non fa nulla, ma crea oggetti InputStream non referenziati. Quegli oggetti saranno raccolti nella spazzatura immediatamente e quindi, non contribuire alla dimensione dell'heap. È importante per il nostro esempio caricare una risorsa esistente da un file jar, e le dimensioni contano qui!

Se sei dubbioso, prova a compilare e avviare la classe sopra, ma assicurati di scegliere una dimensione heap decente (2 MB):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

Non si verificherà un errore OOM qui, poiché non vengono mantenuti riferimenti, l'applicazione continuerà a funzionare indipendentemente dalla dimensione delle ITERAZIONI scelte nell'esempio precedente. Il consumo di memoria del vostro il processo (visibile in alto (RES/RSS) o process explorer) cresce a meno che l'applicazione non arrivi al comando wait. Nella configurazione di cui sopra, allocherà circa 150 MB in memoria.

Se vuoi che l'applicazione giochi sul sicuro, chiudi il flusso di input proprio dove è stato creato:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

E il processo non supererà i 35 MB, indipendentemente dal numero di iterazioni.

Abbastanza semplice e sorprendente.

 15
Author: Jay, 2012-09-12 20:07:37

Non penso che qualcuno abbia ancora detto questo: puoi resuscitare un oggetto sovrascrivendo il metodo finalize() in modo tale che finalize() memorizzi un riferimento di questo da qualche parte. Il garbage collector verrà chiamato solo una volta sull'oggetto in modo che l'oggetto non venga mai distrutto.

 14
Author: Ben, 2011-07-03 17:36:58

Come molte persone hanno suggerito, le perdite di risorse sono abbastanza facili da causare, come gli esempi JDBC. Le perdite di memoria effettive sono un po ' più difficili, specialmente se non ti affidi ai bit rotti della JVM per farlo per te...

Le idee di creare oggetti che hanno un ingombro molto grande e quindi non essere in grado di accedervi non sono perdite di memoria reali. Se nulla può accedervi, verrà raccolto spazzatura, e se qualcosa può accedervi, allora non è un perdita...

Un modo in cui ha usato per funzionare - e non lo so se lo fa ancora - è avere una catena circolare a tre profondità. Come nell'Oggetto A ha un riferimento all'Oggetto B, l'Oggetto B ha un riferimento all'Oggetto C e l'Oggetto C ha un riferimento all'Oggetto A. Il GC è stato abbastanza intelligente da sapere che una catena a due profonde - come in A > B - può essere raccolta in modo sicuro se A e B non sono accessibili da nient'altro, ma non..

 14
Author: Graham, 2011-07-21 17:55:51

Ci sono molte situazioni diverse che la memoria perderà. Uno che ho incontrato, che espongono una mappa che non dovrebbe essere esposta e utilizzata in altro luogo.

public class ServiceFactory {

private Map<String, Service> services;

private static ServiceFactory singleton;

private ServiceFactory() {
    services = new HashMap<String, Service>();
}

public static synchronized ServiceFactory getDefault() {

    if (singleton == null) {
        singleton = new ServiceFactory();
    }
    return singleton;
}

public void addService(String name, Service serv) {
    services.put(name, serv);
}

public void removeService(String name) {
    services.remove(name);
}

public Service getService(String name, Service serv) {
    return services.get(name);
}

// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose 
public Map<String, Service> getAllServices() {
    return services;
}

}

// resource class is a heavy class
class Service {

}
 11
Author: Ben Xu, 2011-07-10 07:46:03

I thread non vengono raccolti finché non terminano. Servono come radici di garbage collection. Sono uno dei pochi oggetti che non saranno recuperati semplicemente dimenticandoli o cancellando i riferimenti a loro.

Considera: il modello di base per terminare un thread di lavoro consiste nell'impostare una variabile di condizione vista dal thread. Il thread può controllare periodicamente la variabile e utilizzarla come segnale per terminare. Se la variabile non è dichiarata volatile, la modifica al la variabile potrebbe non essere vista dal thread, quindi non saprà terminare. Oppure immagina se alcuni thread vogliono aggiornare un oggetto condiviso, ma deadlock durante il tentativo di bloccarlo.

Se hai solo una manciata di thread questi bug saranno probabilmente ovvi perché il tuo programma smetterà di funzionare correttamente. Se si dispone di un pool di thread che crea più thread secondo necessità, i thread obsoleti/bloccati potrebbero non essere notati e si accumuleranno indefinitamente, causando una perdita di memoria. I thread sono è probabile che utilizzi altri dati nella tua applicazione, quindi impedirà anche che tutto ciò a cui fanno riferimento direttamente venga mai raccolto.

Come esempio di giocattolo:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Chiama System.gc() tutto ciò che ti piace, ma l'oggetto passato a leakMe non morirà mai.

(*modificato*)

 11
Author: Boann, 2017-05-23 12:02:58

Penso che un esempio valido potrebbe essere l'utilizzo di variabili ThreadLocal in un ambiente in cui i thread sono raggruppati.

Ad esempio, utilizzando le variabili ThreadLocal nei Servlet per comunicare con altri componenti Web, avendo i thread creati dal contenitore e mantenendo quelli inattivi in un pool. Le variabili ThreadLocal, se non correttamente ripulite, vivranno lì fino a quando, possibilmente, lo stesso componente web sovrascriverà i loro valori.

Naturalmente, una volta identificato, il il problema può essere risolto facilmente.

 9
Author: mschonaker, 2011-07-03 06:49:51

L'intervistatore potrebbe essere alla ricerca di una soluzione di riferimento circolare:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Questo è un problema classico con riferimento conteggio garbage collector. Quindi spiegheresti educatamente che le JVM usano un algoritmo molto più sofisticato che non ha questa limitazione.

-Wes Tarle

 9
Author: Wesley Tarle, 2011-07-04 15:06:58