2014-06-11 10 views
9

Ieri ho distribuito la mia prima app Grails (2.3.6) su un server di sviluppo e ho iniziato a monitorarlo. Ho appena ricevuto un monitor automatico che indica che la CPU è stata bloccata su questa macchina, quindi ho inserito SSHed. Ho eseguito top e ho scoperto che era il PID della mia app Java a bloccare il server. Ho anche notato che la memoria era al 40%. Dopo alcuni secondi, la CPU ha smesso di bloccare, è scesa a un livello normale e la memoria è tornata giù nell'intervallo ~ 20%. GC principale classico.Comprensione perdita del classloader Groovy/Grails

Mentre stava raccogliendo, ho fatto una discarica di heap. Dopo il GC, ho quindi aperto il dump in JVisualVM e ho visto che la maggior parte della memoria era allocata per una classe org.codehaus.groovy.runtime.metaclass.MetaMethodIndex.Entry. Ci sono stati quasi 250.000 esempi di questi in totale, consumando circa 25 MB di memoria.

Ho cercato su Google questa classe e ho dato un'occhiata al numero ultra helpful Javadocs. Quindi non ho ancora idea di cosa faccia questa classe.

Ma google ha anche pubblicato circa una dozzina di articoli correlati (alcuni dei quali domande) che coinvolgono questa classe e una fuga PermGen/classloader con le applicazioni Grails/Groovy. E anche se sembra che la mia app abbia effettivamente ripulito questa istanza di 250K con un GC, è comunque preoccupante che ci siano state così tante istanze e che il GC abbia bloccato la CPU per oltre 5 minuti.

Le mie domande:

  • Che cosa è questa classe e ciò che è Groovy facendo con esso?
  • Qualcuno può spiegare this answer a me? Perché -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled può aiutare questo particolare problema?
  • Perché questa classe è particolarmente problematica per il PermGen?

risposta

12

Groovy è un linguaggio dinamico, ogni chiamata al metodo viene inviata dinamicamente. Per ottimizzare quel Groovy crea un MetaClass per ogni java.lang.Class nel MetaClassRegistry. Queste istanze MetaClass vengono create su richiesta e archiviate utilizzando riferimenti deboli.

La ragione per cui si vede molto org.codehaus.groovy.runtime.metaclass.MetaMethodIndex.Entry è perché Groovy sta memorizzando una mappa di classi e metodi in memoria in modo che possano essere rapidamente inviati dal runtime. A seconda delle dimensioni dell'applicazione, questo può essere come hai scoperto migliaia di classi dato che ogni classe può avere dozzine a volte centinaia di metodi.

Tuttavia, non vi è alcuna "perdita di memoria" in Groovy e Grails, ciò che viene visualizzato è un comportamento normale. L'applicazione sta esaurendo la memoria, probabilmente perché non è stata allocata memoria sufficiente, ciò a sua volta causa la garbage collection delle istanze MetaClass. Ora diciamo per esempio si dispone di un ciclo:

for(str in strings) { 
    println str.toUpperCase() 
} 

In questo caso stiamo chiamando un metodo sulla classe String. Se si sta esaurendo la memoria, ciò che accadrà è che per ogni iterazione del ciclo lo MetaClass verrà raccolto e quindi ricreato nuovamente per l'iterazione successiva. Ciò può rallentare drasticamente un'applicazione e portare alla CPU bloccata come hai visto. Questo stato è comunemente indicato come "metaclass churn" ed è un segno che l'applicazione sta per esaurirsi nella memoria heap.

Se Groovy non era spazzatura raccolta di questi casi metaclasse allora sì che significherebbe v'è una perdita di memoria in Groovy, ma il fatto che Si tratta spazzatura raccolta di queste classi è un segno che va tutto bene, tranne che per il fatto che non hai allocato abbastanza memoria heap in primo luogo. Questo non vuol dire che ci possa essere una perdita di memoria in un'altra parte dell'applicazione che sta divorando tutta la memoria disponibile e non lascia abbastanza per far funzionare correttamente Groovy.

Per quanto riguarda l'altra risposta si fa riferimento a, l'aggiunta di scarico di classe e tweaks PermGen non in realtà fare nulla per risolvere la memoria emette a meno che non si analisi dinamicamente le classi a runtime. Lo spazio PermGen viene utilizzato da JVM per memorizzare classi create dinamicamente. Groovy consente di compilare le classi in fase di esecuzione utilizzando GroovyClassLoader.parseClass o GroovyShell.evaluate. Se si stanno continuamente analizzando le classi, allora si può aggiungere un flag di scarico di classe. Vedi anche questo post:

Locating code that is filling PermGen with dead Groovy code

Tuttavia, una tipica applicazione Grails non compilare dinamicamente le classi a tempo di esecuzione e quindi modificando le impostazioni di classe scarico PermGen e non sarà effettivamente ottenere nulla.

È necessario verificare se è stata allocata memoria heap sufficiente utilizzando il flag -Xmx e se non allocare più.