GCPerformance.java - GC Performance Test Program

This section describes a GC performance test program - GCPerformance.java.

In previous tutorials of the book, we learned how different garbage collectors work. Now in order to learn how to tune garbage collectors to improve application performance, I wrote the following test program:

/* GCPerformance.java
 * Copyright (c) HerongYang.com. All Rights Reserved.
 */
class GCPerformance {
   static MyList objList = null;
   static int objSize = 1024; // in KB, default = 1 MB
   static int baseSize = 32;  // # of objects in the base
   static int chunkSize = 32; // # of objects in chunk
   static int wait = 1000;    // in milliseconds: 1 second
   static int warmup = 256;   // warmup loops: 256*32 = 8GB
   public static void main(String[] arg) {
      if (arg.length>0) objSize = Integer.parseInt(arg[0]);
      if (arg.length>1) baseSize = Integer.parseInt(arg[1]);
      if (arg.length>2) chunkSize = Integer.parseInt(arg[2]);
      if (arg.length>3) wait = Integer.parseInt(arg[3]);
      if (arg.length>4) warmup = Integer.parseInt(arg[4]);
      System.out.println("Parameters: "+"Size="+objSize+"KB"
         +", Base="+baseSize +", Chunk="+chunkSize
         +", Wait="+wait+"ms" +", Warmup="+warmup);
      objList = new MyList();
      myTest();
   }
   public static void myTest() {
      for (int m=0; m<baseSize; m++) {
         objList.add(new MyObject());
      }
      for (int k=0; k<warmup; k++) {
         for (int m=0; m<chunkSize; m++) {
            objList.add(new MyObject());
         }
         for (int m=0; m<chunkSize; m++) {
            objList.removeTail();
         }
      }

      Runtime rt = Runtime.getRuntime();
      System.out.println("Real:Exec  Lat.     Throughput      "
         +"Total:Free  Proc.");
      System.out.println("Time:Time  ms/o  Ave:Min:Max:Chunk  "
         +" Mem.:Mem.   Obj.");
      long dt0 = System.currentTimeMillis();
      long dt1 = System.currentTimeMillis();
      long minPerf = 999999;
      long maxPerf = 0;
      long count = 0;
      while (true) {
         for (int m=0; m<chunkSize; m++) {
            objList.add(new MyObject());
         }
         for (int m=0; m<chunkSize; m++) {
            objList.removeTail();
         }
         count++;

         mySleep(wait);
         long tm = rt.totalMemory()/1024;
         long fm = rt.freeMemory()/1024;
         long dt2 = System.currentTimeMillis();
         long dt = dt2 - dt0;
         long de = dt - (count*wait);
         long perf = (1000*chunkSize)/(dt2-dt1-wait); // per second
         if (perf<minPerf) minPerf = perf;
         if (perf>maxPerf) maxPerf = perf;

         System.out.println((dt)      // Real time in millis
            +":"+(de)                 // Execution time in millis
            +"  "+(1000000/minPerf)   // Latency (ms/1000 objects)
            +"  "+((1000*count*chunkSize)/de) // Average throughput
            +":"+minPerf              // Best throughput
            +":"+maxPerf              // Worst throughput
            +":"+perf                 // Chunk throughput
            +"  "+tm+":"+fm           // Total and free memory in KB
            +"  "+(count*chunkSize)); // Objects processed
         dt1 = dt2;
      }
   }
   static void mySleep(int t) {
      try {
         Thread.sleep(t);
      } catch (InterruptedException e) {
         System.out.println("Interreupted...");
      }
   }
   static class MyObject {
      private long[] obj = null;
      public MyObject next = null;
      public MyObject prev = null;
      public MyObject() {
         obj = new long[objSize*128]; // 128*8=1024 bytes
         for (int i=0; i<objSize*128; i++) {
            obj[i] = i/2+i/3+i/4+i/5; // some work load
         }
      }
   }
   static class MyList {
      MyObject head = null;
      MyObject tail = null;
      void add(MyObject o) {
         if (head==null) {
            head = o;
            tail = o;
         } else {
            o.prev = head;
            head.next = o;
            head = o;
         }
      }
      void removeTail() {
         if (tail!=null) {
            if (tail.next==null) {
               tail = null;
               head = null;
            } else {
               tail = tail.next;
               tail.prev = null;
            }
         }
      }
   }
}

Some notes on the test program:

The output of the test program focuses on two performance metrics:

Throughput - Defined as "the number of objects processed per second". This is calculated as:

Latency - Defined as "the maximum time required to process a single object". This is calculated as:

Table of Contents

 About This Book

 Heap Memory Area and Size Control

 JVM Garbage Collection Logging

 Introduction of Garbage Collectors

 Serial Collector - "+XX:+UseSerialGC"

 Parallel Collector - "+XX:+UseParallelGC"

 Concurrent Mark-Sweep (CMS) Collector - "+XX:+UseConcMarkSweepGC"

 Garbage First (G1) Collector - "+XX:+UseG1GC"

 The Z Garbage Collector (ZGC) - "+XX:+UseZGC"

 Object References and Garbage Collection

Garbage Collection Performance Test Program

GCPerformance.java - GC Performance Test Program

 GCPerformance.java - Program Output

 Performance Impact of Wait Time

 Performance Impact of Object Size

 Performance Impact of Chunk Size

 Performance Jumps Not Related to GC

 Performance Test and System Interruptions

 "START /REALTIME" - Run JVM with Highest Priority

 GCPerfP99.java - 99th Percentile Performance

 GCPerfP99.java - Output Verification

 GCPerfP99V2.java - Percentile Performance with Load

 GCPerfP99V2.java - Work Load Level

 GCPerfP99V2.java - Object Number and Size

 Performance Tests on Serial Collector

 Performance Tests on Parallel collector

 Performance Tests on Concurrent collector

 Performance Tests on G1 collector

 Garbage Collection Performance Test Summary

 References

 Full Version in PDF/EPUB