VT2018 GCeasy-synthese

From air
Revision as of 12:52, 10 December 2018 by Loris.Gentillon (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Auteur

  • Nom : GENTILLON Loris
  • Mail : gentillon.loris@gmail.com
  • Sujet : GCeasy - Java Garbage Collector

Résumé

Afin de détecter des problèmes de performances et de taille mémoire, une analyse des fichiers de log que génère une JVM lors de son exécution peut se révéler intéressant. GCeasy permet cette analyse, en local comme en SaaS, sur un fichier de log comme sur plusieurs via son API REST.

Abstract

In order of detecting perfomance and memory issues of a Java JVM, one can use a log analyzer on the java garbage collector log. GCeasy allows that in a fancy, shiny way. You can either use their webpage as a SaaS, or download your own instance of GCeasy (neither free nor libre). And, you can handle one file at a time, to get some graphics, or you can use their API to analyze hundreds of file and gather useful informaiton through json.

Key words

Java, JVM, Garbage Collector

Synthèse

GCeasy présente l'énorme avantage d'être très simple d'utilisation. Son interface web est claire et directe, et son API REST est très bien documentée. Cependant, l'utilisation de l'api nécessite de s'enregistrer au préalable sur leur site.

Objectifs

GCeasy vise à détecter différents problèmes que peuvent rencontrer l'exécution de programme Java :

  • fuites mémoires (même si à proprement parler Java ne peut pas subir de fuites mémoires, on parlera ici d'objets que le GC ne peut nettoyer)
  • lenteurs (dues au GC qui s'exécute trop souvent, suite à des tentatives d'allocation mémoire infructueuse)

Simulations

Simulation 1

https://gist.github.com/dpryden/b2bb29ee2d146901b4ae

L'objectif de cette simulation est de générer une fuite mémoire, en utilisant un ClassLoader défectueux.

Pour utiliser le code et constater la fuite :

  • Créer un fichier ClassLoaderLeakExample.java, n'importe ou sur le système et c/c le code du lien ci dessus
  • Compiler le fichier : javac ClassLoaderLeakExample.java
  • Executer le bytecode (en enregistrant les logs du GC) : java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:LOGFILE.log ClassLoaderLeakExample
  • (eventuellement rajouter un petit top/htop/gtop en parallèle pour constater les dégâts)
  1import java.io.IOException;
  2import java.net.URLClassLoader;
  3import java.nio.file.Files;
  4import java.nio.file.Paths;
  5import java.nio.file.Path;
  6
  7/**
  8 * Example demonstrating a ClassLoader leak.
  9 *
 10 * <p>To see it in action, copy this file to a temp directory somewhere,
 11 * and then run:
 12 * <pre>{@code
 13 *   javac ClassLoaderLeakExample.java
 14 *   java -cp . ClassLoaderLeakExample
 15 * }</pre>
 16 *
 17 * <p>And watch the memory grow! On my system, using JDK 1.8.0_25, I start
 18 * getting OutofMemoryErrors within just a few seconds.
 19 *
 20 * <p>This class is implemented using some Java 8 features, mainly for
 21 * convenience in doing I/O. The same basic mechanism works in any version
 22 * of Java since 1.2.
 23 */
 24public final class ClassLoaderLeakExample {
 25
 26  static volatile boolean running = true;
 27
 28  public static void main(String[] args) throws Exception {
 29    Thread thread = new LongRunningThread();
 30    try {
 31      thread.start();
 32      System.out.println("Running, press any key to stop.");
 33      System.in.read();
 34    } finally {
 35      running = false;
 36      thread.join();
 37    }
 38  }
 39
 40  /**
 41   * Implementation of the thread. It just calls {@link #loadAndDiscard()}
 42   * in a loop.
 43   */
 44  static final class LongRunningThread extends Thread {
 45    @Override public void run() {
 46      while(running) {
 47        try {
 48          loadAndDiscard();
 49        } catch (Throwable ex) {
 50          ex.printStackTrace();
 51        }
 52        try {
 53          Thread.sleep(100);
 54        } catch (InterruptedException ex) {
 55          System.out.println("Caught InterruptedException, shutting down.");
 56          running = false;
 57        }
 58      }
 59    }
 60  }
 61  
 62  /**
 63   * A simple ClassLoader implementation that is only able to load one
 64   * class, the LoadedInChildClassLoader class. We have to jump through
 65   * some hoops here because we explicitly want to ensure we get a new
 66   * class each time (instead of reusing the class loaded by the system
 67   * class loader). If this child class were in a JAR file that wasn't
 68   * part of the system classpath, we wouldn't need this mechanism.
 69   */
 70  static final class ChildOnlyClassLoader extends ClassLoader {
 71    ChildOnlyClassLoader() {
 72      super(ClassLoaderLeakExample.class.getClassLoader());
 73    }
 74    
 75    @Override protected Class<?> loadClass(String name, boolean resolve)
 76        throws ClassNotFoundException {
 77      if (!LoadedInChildClassLoader.class.getName().equals(name)) {
 78        return super.loadClass(name, resolve);
 79      }
 80      try {
 81        Path path = Paths.get(LoadedInChildClassLoader.class.getName()
 82            + ".class");
 83        byte[] classBytes = Files.readAllBytes(path);
 84        Class<?> c = defineClass(name, classBytes, 0, classBytes.length);
 85        if (resolve) {
 86          resolveClass(c);
 87        }
 88        return c;
 89      } catch (IOException ex) {
 90        throw new ClassNotFoundException("Could not load " + name, ex);
 91      }
 92    }
 93  }
 94  
 95  /**
 96   * Helper method that constructs a new ClassLoader, loads a single class,
 97   * and then discards any reference to them. Theoretically, there should
 98   * be no GC impact, since no references can escape this method! But in
 99   * practice this will leak memory like a sieve.
100   */
101  static void loadAndDiscard() throws Exception {
102    ClassLoader childClassLoader = new ChildOnlyClassLoader();
103    Class<?> childClass = Class.forName(
104        LoadedInChildClassLoader.class.getName(), true, childClassLoader);
105    childClass.newInstance();
106    // When this method returns, there will be no way to reference
107    // childClassLoader or childClass at all, but they will still be
108    // rooted for GC purposes!
109  }
110
111  /**
112   * An innocuous-looking class. Doesn't do anything interesting.
113   */
114  public static final class LoadedInChildClassLoader {
115    // Grab a bunch of bytes. This isn't necessary for the leak, it just
116    // makes the effect visible more quickly.
117    // Note that we're really leaking these bytes, since we're effectively
118    // creating a new instance of this static final field on each iteration!
119    static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 10];
120  
121    private static final ThreadLocal<LoadedInChildClassLoader> threadLocal
122        = new ThreadLocal<>();
123    
124    public LoadedInChildClassLoader() {
125      // Stash a reference to this class in the ThreadLocal
126      threadLocal.set(this);
127    }
128  }
129}

Simulation 2

https://github.com/Gazeka74/VT2018

Petit projet Eclipse, qui créé un certains nombre de threads, et chacun alloue un certain nombre de ressources qu'il ne libère pas.

Ne pas oublier de renseigner dans Eclipse les informations concernant la génération du fichier de log : (clic droit sur le projet - run as - run configuration - argument - jvm arguments)

  • -XX:+PrintGCDetails
  • -XX:+PrintGCDateStamps
  • -Xloggc:LOGFILE.log

Exemple d'un rapport généré par GCeasy

(Le rapport, enregistré en pdf ne rend pas super bien, la version live via le site web de GCeasy est bien plus interractive) https://air.imag.fr/images/6/66/GCeasy-report-3.pdf