Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4185368

Garbage collector pages in a lot of the class files

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P4 P4
    • None
    • 1.1.4
    • hotspot
    • x86
    • windows_95



      Name: ap32198 Date: 10/28/98


      We've built a large Java app, with over 2000
      classes. Whenever the Java garbage collector
      runs it pages in not only the entire Java heap
      (presumably due to the mark and sweep) but also
      many other pages, which seem to be part of the
      space allocated by the JVM for classes.

      This effect is very significant for our app.
      Our Java heap is around 5-6MB and we GC often,
      so we'd expect our working set to start at
      around 6MB, because of the GC paging in all
      the Java heap. But in fact it is around 12MB
      because the GC touches all the classes as
      well (probably the constant pools?). This is
      just too big and, together with the working
      set of our native code, means our app won't
      run on our target platform.

      If the portions of the classes needed by the
      GC were loaded in a contiguous area I suspect
      our working set would be several megabytes
      lower, which would really help. Java's high
      working set is a big problem for us, and I
      suspect it will also bite others who are
      writing large Java apps - particularly as
      the Java libraries grow bigger.

      I have test programs that generate many dummy
      classes and then load them to see their effect
      on working set. These tests confirm that loading
      more classes, even without creating more Java
      objects, increases working set substantially.
      If the dummy classes are larger than a page the
      effect is more marked, presumably because you
      then drag in at least a page per class during
      the garbage collection.

      I'm enclosing the programs, with instructions,
      though they're going to get horribly wrapped
      in this little text field

      Thank you for looking at this!
        Trevor Morris

      Here's a description of the test programs, followed by the
      programs themselves:

      - BogusClassGenerator.java. This generates a number of Java files,
      BogusClass0.java, BogusClass1.java etc. Each Java file contains
      a very simple class with just a constructor; the constructor
      contains "int i = 0;" and then an arbitrary number of "i += 1000;"
      assignments. BogusClassGenerator takes arguments which control
      the number of classes generated, plus the number of assignment
      lines (just a way to make the class file bigger by adding more
      bytecode). e.g:
       java BogusClassGenerator -l 500 1000
      generates 1000 bogus classes, each containing 500 assignments

      - SimpleThrasher.java. This loads some number of the BogusClasses
      generated by BogusClassGenerator, and stuffs them in an array. It
      also allocates some number of filler objects, just to get something
      on the Java heap (number of classes and number of filler objects
      can be controlled by arguments). It then goes into an infinite
      loop where it sleeps, does a GC, sleeps, flushes working set and
      then repeats (you'll need to add the flush working set call yourself,
      if you're doing this on Solaris - if not I can send you the NT API
      to do it). Watching the program with a working set monitor, you'll
      see a plain up and down function - working set drops when flushed
      then jumps on the GC, then drops and so on. Example of running:
       java -verbosegc SimpleThrasher 1000
      Loads 1000 bogus classes (and, by default, creates 1000 3k filler
      objects).

      I did some experiments on my NT box with these two programs and the
      results were pretty interesting:

      NO CLASSES:
      Running SimpleThrasher, but loading no bogus classes. 1000 3k filler objects.
      Java Heap: 303728/3554504 i.e. 3250776, about 90% utilization
      Working Set after GC: 4251648

      SMALL CLASS:
      Running SimpleThrasher, loading 1000 bogus classes, each with zero lines
      of assignments (class files ~250 bytes) + 1000 3k filler objects.
      Java Heap: 535352/3882184 i.e. 3346832, about 85% utilization
      Working Set after GC: 5038080 (786432 bytes greater than base case)

      BIG CLASS:
      Running SimpleThrasher, loading 1000 bogus classes, each with 50 lines
      of assignments (class files ~5260 bytes) + 1000 3k filler objects.
      Java Heap: 535352/3882184 i.e. 3346832, about 85% utilization (no change
         from small class case)
      Working Set after GC: 8560640 (4308992 bytes greater than base case,
         3522560 greater than small class case)

      Note, page size on NT is 4k so probably around 16 of the small classes
      could fit on a page (assuming the VM data structure is a similar size
      to the class file). The big classes are probably bigger than a page.

      So, bottom line, moving to bigger classes (but the same number of classes)
      increased the working set by around 3.3MB, presumably because the GC
      visits every class and now has to page in at least a page per bogus
      class, instead of (on average) around 1/16th of a page in the small
      class case.

      START FILE BogusClassGenerator.java:
      import java.io.FileOutputStream;
      import java.io.BufferedOutputStream;
      import java.io.PrintWriter;
      import java.io.IOException;


      public class BogusClassGenerator {

          /** Maximum number of bogus classes we generate */
          public static final int MAX_BOGUS_CLASSES = 5000;

          /** Base name for bogus classes */
          public static final String BOGUS_CLASS_BASE_NAME = "BogusClass";


          static void generateClass(int classNumber, int lineCount) {
              String className = BOGUS_CLASS_BASE_NAME + classNumber;
              try {
                  FileOutputStream fileStream =
                      new FileOutputStream(className + ".java");
                  PrintWriter outStream =
                      new PrintWriter(new BufferedOutputStream(fileStream));
                  outStream.println("public class " + className + " {");
                  outStream.println(" " + className + "() {");
                  outStream.println(" int i = 0;");
                  for (int i = 0; i < lineCount; i++) {
                      outStream.println(" i += 1000;");
                  }
                  outStream.println(" }");
                  outStream.println("}");
                  outStream.close();
                  fileStream.close();
              }
              catch (IOException e) {
                  System.out.println("Failed to generate class " + className +
                                     " due to IO exception: " + e);
                  System.exit(1);
              }
          }


          static private void usage() {
              System.out.println("Usage: java BogusClassGenerator -l lines count");
              System.out.println("-l lines : number of lines of code to put in BogusClass constructor");
              System.out.println("count : number of BogusClass<i> classes to generate");
              System.exit(1);
          }


          public static void main(String[] args) {

              int classCount = 0;
              int lineCount = 0;
              int i = 0;
              while (i < args.length) {
                  String arg = args[i++];
                  if (arg.startsWith("-")) {
                      if (arg.equals("-l")) {
                          if (i < args.length) {
                              String lineCountString = args[i++];
                              try {
                                  lineCount = Integer.parseInt(lineCountString, 10);
                              }
                              catch (NumberFormatException e) {
                                  System.out.println("Invalid line count: " +
                                                     lineCountString);
                                  System.exit(1);
                              }
                          }
                          else {
                              usage();
                          }
                      }
                      else {
                          usage();
                      }
                  }
                  else {
                      try {
                          classCount = Integer.parseInt(arg);
                          if (classCount > MAX_BOGUS_CLASSES) {
                              System.out.println("Too many classes - maximum is " +
                                                 MAX_BOGUS_CLASSES);
                              System.exit(1);
                          }
                      }
                      catch (NumberFormatException e) {
                          usage();
                      }
                  }
              }


              System.out.println("Generating " + classCount + " bogus classes" +
                                 " each with " + lineCount +
                                 " lines in their constructor");
              for (i = 0; i < classCount; i++) {
                  generateClass(i, lineCount);
              }
          }



      }
      END FILE BogusClassGenerator.java

      START FILE SimpleThrasher.java
      public class SimpleThrasher {

          /** Overhead for an object plus an array */
          public static final int PER_OBJECT_OVERHEAD = 24;

          /**
           * Instance variable - used to make each SimpleThrasher object take
           * up 3K.
           */
          private byte[] mySpace = new byte[(3 * 1024) - PER_OBJECT_OVERHEAD];

          /**
           * Array which is always allocated and then is filled with either
           * Objects or Class objects. This is an attempt to keep the Java
           * heap roughly the same size no matter how many classes are loaded
           */
          private static final Object[] TheClasses =
            new Object[BogusClassGenerator.MAX_BOGUS_CLASSES];


          SimpleThrasher() {
          }


          static void sleep(long millis) {
              try {
                  Thread.currentThread().sleep(millis);
              }
              catch (InterruptedException e) {
                  System.out.println("SLEEP INTERRUPTED");
              }
          }

          
          static private void usage() {
              System.out.println("Usage: java SimpleThrasher -i interval <count>");
              System.out.println("-f count : number of filler objects to create (each ~3k)");
              System.out.println("-i interval: interval between gcs, in seconds");
              System.out.println("count : number of BogusClass<i> classes to load");
              System.exit(1);
          }


          public static void main(String[] args) {

              int classCount = 0;
              int fillerCount = 1000;
              int interval = 3;
              int i = 0;
              while (i < args.length) {
                  String arg = args[i++];
                  if (arg.startsWith("-")) {
                      if (arg.equals("-f")) {
                          if (i < args.length) {
                              String fillerCountString = args[i++];
                              try {
                                  fillerCount = Integer.parseInt(fillerCountString, 10);
                              }
                              catch (NumberFormatException e) {
                                  System.out.println("Invalid filler count: " +
                                                     fillerCountString);
                                  System.exit(1);
                              }
                          }
                          else {
                              usage();
                          }
                      }
                      else if (arg.equals("-i")) {
                          if (i < args.length) {
                              String intervalString = args[i++];
                              try {
                                  interval = Integer.parseInt(intervalString, 10);
                              }
                              catch (NumberFormatException e) {
                                  System.out.println("Invalid interval count: " +
                                                     intervalString);
                                  System.exit(1);
                              }
                          }
                          else {
                              usage();
                          }
                      }
                      else {
                          usage();
                      }
                  }
                  else {
                      try {
                          classCount = Integer.parseInt(arg);
                          if (classCount > BogusClassGenerator.MAX_BOGUS_CLASSES) {
                              System.out.println("Too many classes - maximum is " +
                                                 BogusClassGenerator.MAX_BOGUS_CLASSES);
                              System.exit(1);
                          }
                      }
                      catch (NumberFormatException e) {
                          usage();
                      }
                  }
              }

              System.out.println("Loading " + classCount + " bogus classes");
              String bogusClassName = BogusClassGenerator.BOGUS_CLASS_BASE_NAME;
              for (i = 0; i < classCount; i++) {
                  try {
                      TheClasses[i] = Class.forName(bogusClassName + i);
                  }
                  catch (ClassNotFoundException e) {
                      System.out.println("Couldn't load " + bogusClassName + i +
                                         ": " + e);
                      System.exit(1);
                  }
              }
              System.out.println("Loaded " + classCount + " bogus classes");

              for (i = classCount; i < BogusClassGenerator.MAX_BOGUS_CLASSES; i++) {
                  TheClasses[i] = new Object();
              }

              System.out.println("Creating " + fillerCount + " filler objects");
              SimpleThrasher[] filler = new SimpleThrasher[fillerCount];
              for (i = 0; i < fillerCount; i++) {
                  filler[i] = new SimpleThrasher();
              }
              System.out.println("Created " + fillerCount + " filler objects");

              System.out.println("Starting thrasher; interval = " + interval);


              for (;;) {
                  sleep(interval * 1000);
                  System.gc();
                  if (filler == null) {
                      System.out.println("ERROR, should never happen!");
                  }
                  sleep(interval * 1000);
                  ec.util.Native.flushWorkingSet();
              }
          }
      }
      END FILE SimpleThrasher.java
      (Review ID: 26027)
      ======================================================================

            Unassigned Unassigned
            apalanissunw Anand Palaniswamy (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: