Possibile perdita di memoria nel numero di classi caricate nell'applicazione Java

StackOverflow https://stackoverflow.com/questions/167740

  •  03-07-2019
  •  | 
  •  

Domanda

Di recente ho iniziato a profilare un'applicazione java osgi che sto scrivendo usando VisualVM. Una cosa che ho notato è che quando l'applicazione inizia a inviare dati a un client (tramite JMS), il numero di classi caricate inizia ad aumentare a una velocità costante. Tuttavia, la dimensione dell'heap e la dimensione PermGen rimangono costanti. Il numero di classi non diminuisce mai, anche dopo che smette di inviare dati. È una perdita di memoria? Penso di sì, perché le classi caricate devono essere archiviate da qualche parte, tuttavia l'heap e il permgen non aumentano mai anche dopo aver eseguito l'applicazione per diverse ore.

Per lo screenshot della mia applicazione di profilazione vai qui

È stato utile?

Soluzione

  

In qualche modo stai creando dinamicamente nuove classi al volo?

Grazie per l'aiuto. Ho capito qual è il problema. In una delle mie lezioni, stavo usando Jaxb per creare una stringa XML. Nel fare ciò, JAXB utilizza la riflessione per creare una nuova classe.

JAXBContext context = JAXBContext.newInstance(this.getClass());

Quindi, sebbene JAXBContext non stesse dicendo nulla nell'heap, le classi erano state caricate.

Ho eseguito di nuovo il mio programma e vedo un altopiano normale come mi aspetterei.

Altri suggerimenti

Sono disposto a scommettere che il tuo problema è legato alla generazione del bytecode.

Molte librerie usano CGLib, BCEL, Javasist o Janino per generare bytecode per nuove classi in fase di esecuzione e quindi caricarle dal programma di caricamento classi controllato. L'unico modo per rilasciare queste classi è rilasciare tutti i riferimenti al classloader.

Poiché il classloader è gestito da ciascuna classe, ciò significa anche che non è necessario rilasciare i riferimenti anche a tutte le classi [1]. Puoi prenderli con un profiler decente (io uso Yourkit - cerco più istanze di classloader con le stesse dimensioni conservate)

Un problema è che JVM non scarica le classi per impostazione predefinita (il motivo è la compatibilità con le versioni precedenti - che le persone presumono (erroneamente) che gli inizializzatori statici vengano eseguiti solo una volta. La verità è che vengono eseguiti ogni volta che viene caricata una classe .) Per abilitare lo scarico, dovresti passare alcuni usando le seguenti opzioni:

-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled

(testato con JDK 1.5)

Anche in questo caso, la generazione eccessiva di bytecode non è una buona idea, quindi ti consiglio di cercare nel tuo codice per trovare il colpevole e memorizzare nella cache le classi generate. I trasgressori frequenti sono i linguaggi di scripting, i proxy dinamici (compresi quelli generati dai server delle applicazioni) o l'enorme modello Hibernate (in questo caso puoi semplicemente aumentare il tuo permesso).

Vedi anche:

  1. http://blogs.oracle.com/watt/resource /jvm-options-list.html
  2. http://blogs.oracle.com/jonthecollector/entry/presenting_the_permanent_generation
  3. http://forums.sun.com/thread.jspa?messageID=2833028

Potresti trovare alcuni flag hotspot utili per comprendere questo comportamento come:

  • -XX: + TraceClassLoading
  • -XX: + TraceClassUnloading

Questo è un buon riferimento:

http://java.sun.com/javase/technologies/hotspot /vmoptions.jsp

A meno che non fraintenda, stiamo esaminando le classi caricate, non le istanze.

Quando il tuo codice fa riferimento per la prima volta a una classe, JVM fa uscire ClassLoader e recupera le informazioni sulla classe da un file .class o simili.

Non sono sicuro in quali condizioni scaricherà una classe. Certamente non dovrebbe mai scaricare alcuna classe con informazioni statiche.

Quindi mi aspetterei un modello approssimativamente come il tuo, in cui la tua applicazione si sposta in aree e fa riferimento a nuove classi, in modo che il numero di classi caricate salga su e su.

Tuttavia, due cose mi sembrano strane:

  1. Perché è così lineare?
  2. Perché non è altopiano?

Mi aspetterei che tenda al rialzo, ma in una linea traballante, per poi ridursi all'aumento poiché la JVM ha già caricato la maggior parte delle classi a cui fa riferimento il tuo programma. Voglio dire, ci sono un numero finito di classi a cui fa riferimento la maggior parte delle applicazioni.

In qualche modo stai creando dinamicamente nuove classi al volo?

Suggerirei di eseguire un'app di test più semplice attraverso lo stesso debugger per ottenere un caso di base. Quindi potresti prendere in considerazione l'implementazione del tuo ClassLoader che sputa alcune informazioni di debug, o forse c'è uno strumento per farlo segnalare.

Devi capire quali sono queste classi che vengono caricate.

Sì, di solito è una perdita di memoria (poiché non trattiamo direttamente la memoria direttamente, è più una perdita di istanza di classe). Ho già seguito questo processo e di solito è un ascoltatore aggiunto a un vecchio toolkit che non lo ha rimosso da solo.

Nel codice precedente, una relazione di listener provoca il "listener" oggetto di rimanere in giro. Guarderei i toolkit più vecchi o quelli che non sono stati sottoposti a molti regimi. Qualsiasi libreria esistente da tempo in esecuzione su un JDK successivo sarebbe a conoscenza di oggetti di riferimento che rimuovono il requisito per " Remove Listener " ;.

Inoltre, chiama dispose sulle tue finestre se le ricrea ogni volta. Non penso che se ne andranno mai se non lo fai (in realtà c'è anche uno smaltimento a distanza ravvicinata).

Non preoccuparti degli ascoltatori Swing o JDK, dovrebbero usare tutti i riferimenti, quindi dovresti stare bene.

Utilizza Eclipse Memory Analyzer per verificare la presenza di classi duplicate e perdite di memoria. Potrebbe accadere che la stessa classe venga caricata più di una volta.

Saluti, Markus

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top