2016-03-10 10 views
15

Per anni, abbiamo eseguito servizi Java con dimensioni heap ridotte utilizzando +UseParallelOldGC. Ora stiamo iniziando a implementare un nuovo servizio utilizzando un heap più grande e il collector G1. Questo sta andando abbastanza bene.Java G1: monitoraggio perdite di memoria in produzione

Per i nostri servizi che utilizzano +UseParallelOldGC, monitoriamo le perdite di memoria osservando le vecchie dimensioni di generazione dopo la raccolta e l'avviso su una soglia. Funziona abbastanza bene e in effetti ha salvato la nostra bacon solo due settimane fa.

In particolare, per +UseParallelOldGC, facciamo la seguente:

  • ManagementFactory.getMemoryPoolMXBeans()
  • Cercare il MemoryPoolMXBean risultato con il nome che termina in "Old Gen"
  • Confronta getCollectionUsage().getUsed() (se disponibile) con getMax()

Sfortunatamente, sembra che G1 non abbia più il concetto di getCollectionUsage().

Fondamentalmente, tuttavia, ci piacerebbe monitorare la dimensione dell'heap G1 dopo l'ultima raccolta mista che sceglie di fare in un ciclo misto o qualcosa di simile.

Per esempio, al di fuori della VM sarei felice con uno script awk che semplicemente trovato l'ultimo '(mixed)' stato che è seguito da un '(young)' e guardare ciò che la dimensione finale heap è stato (ad esempio, '1540.0M' 'Heap: 3694.5M(9216.0M)->1540.0M(9216.0M)')

C'è qualche modo di farlo all'interno della Java VM?

+0

questo dovrebbe fornire informazioni simili: http://stackoverflow.com/a/ 32509813/1362755 – the8472

risposta

2

Sì, JVM fornisce strumenti sufficienti per recuperare tali informazioni per G1. Per esempio, si potrebbe usare qualcosa di simile classe che stampa tutti i dettagli circa garbage collection (basta chiamare MemoryUtil.startGCMonitor()):

public class MemoryUtil { 

    private static final Set<String> heapRegions; 

    static { 
     heapRegions = ManagementFactory.getMemoryPoolMXBeans().stream() 
       .filter(b -> b.getType() == MemoryType.HEAP) 
       .map(MemoryPoolMXBean::getName) 
       .collect(Collectors.toSet()); 
    } 

    private static NotificationListener gcHandler = (notification, handback) -> { 
     if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { 
      GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); 
      Map<String, MemoryUsage> memBefore = gcInfo.getGcInfo().getMemoryUsageBeforeGc(); 
      Map<String, MemoryUsage> memAfter = gcInfo.getGcInfo().getMemoryUsageAfterGc(); 
      StringBuilder sb = new StringBuilder(250); 
      sb.append("[").append(gcInfo.getGcAction()).append("/").append(gcInfo.getGcCause()) 
        .append("/").append(gcInfo.getGcName()).append("/("); 
      appendMemUsage(sb, memBefore); 
      sb.append(") -> ("); 
      appendMemUsage(sb, memAfter); 
      sb.append("), ").append(gcInfo.getGcInfo().getDuration()).append(" ms]"); 
      System.out.println(sb.toString()); 
     } 
    }; 

    public static void startGCMonitor() { 
     for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) { 
      ((NotificationEmitter) mBean).addNotificationListener(gcHandler, null, null); 
     } 
    } 

    public static void stopGCMonitor() { 
     for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) { 
      try { 
       ((NotificationEmitter) mBean).removeNotificationListener(gcHandler); 
      } catch(ListenerNotFoundException e) { 
       // Do nothing 
      } 
     } 
    } 

    private static void appendMemUsage(StringBuilder sb, Map<String, MemoryUsage> memUsage) { 
     memUsage.entrySet().forEach((entry) -> { 
      if (heapRegions.contains(entry.getKey())) { 
       sb.append(entry.getKey()).append(" used=").append(entry.getValue().getUsed() >> 10).append("K; "); 
      } 
     }); 
    } 
} 

In questo codice, gcInfo.getGcAction() ti dà abbastanza informazioni per separare le collezioni minori da quelli principali/mista.

Ma c'è un avvertimento importante per usare il tuo approccio (con una soglia) a G1. Una singola raccolta mista in G1 di solito interessa solo diverse vecchie regioni di gen, molte abbastanza per liberare una quantità sufficiente di memoria ma non troppe per mantenere bassa la pausa del GC. Quindi, dopo una raccolta mista in G1 non puoi essere sicuro che tutta la tua spazzatura sia andata via. Di conseguenza, è necessario trovare una strategia più sofisticata per rilevare perdite di memoria (magari in base alla frequenza di raccolta, raccolta di statistiche da varie raccolte, ecc.)