Java Performance

dho's Patented One Step Solution

  • Don't create objects.

WTF?

  • Re-use objects
  • Boxing / unboxing
  • Strings / string functions
  • Function / method hints
  • API knowledge

Object re-use

Overview

  • Many ways to re-use objects
  • Singleton model
  • Object pools
  • Make APIs compatible

The Singleton Model

  • Only one instance of an object
  • Useful for WORM-like objects

Singleton Example

 1 public final class Singleton {
 2   private static Singleton inst = null;
 3   private static int property;
 4 
 5   public Singleton() {}
 6 
 7   public static getInstance() {
 8     if (inst == null) {
 9       inst = new Singleton();
10     }
11 
12     return inst;
13   }
14 
15   public static getProperty() { return property; }
16   public static setProperty(int val) { property = val; }
17 }

Singleton Model Drawbacks

  • Needs special care for concurrent access in R/W models
  • Not particularly "standard" OOP access pattern
  • Most of the time, you need more than one instance

Object Pooling

  • Delegates object creation by maintaining a "pool" of objects
  • Re-uses objects internally
  • Generally, anywhere you're creating a moderate number of relatively short-lived objects with fairly beefy constructors in a hot path, it makes sense to pool objects.
  • Systems with very good malloc implementations may see performance degradation with object pooling, especially for small objects (like Integer, String, etc.)

Object Pool Example

 1 public static class ObjectPool {
 2   private static final Queue<Object> inuse = new Queue<Object>();
 3   private static final Queue<Object> free = new Queue<Object>();
 4 
 5   public ObjectPool() {}
 6 
 7   public Object getObject() {
 8     Object o = free.poll();
 9     if (o == null) {
10       o = new Object();
11     }
12 
13     inuse.add(o);
14 
15     return o;
16   }
17 
18   public void releaseObject(Object o) {
19     inuse.remove(o);
20     free.add(o);
21   }
22 }

Object Pooling Drawbacks

  • Potential unnecessary creation in multithreaded cases
  • Peak object use is constant memory overhead
  • These issues can be addressed, but require much more complex code

Making Objects Re-usable in APIs

  • If people can't re-use your objects, performance problems are your fault.
  • Any properties initialized in the constructor must be able to be overwritten

Under the Hood

Overview

  • Many "simple" tasks create objects under the hood
  • Not always straight-forward
  • Easy to mis-use in tight loops

Boxing and Unboxing

  • Boxing: converting native types to their respective object type
  • Unboxing: converting an object to its respective native type

Boxing and Unboxing: an Example

 1 public void boxMe(Integer in) {
 2   System.out.println(in);
 3 }
 4 
 5 public void unboxMe(int in) {
 6   System.out.println(in);
 7 }
 8 
 9 Integer unbox = new Integer(1);
10 int box = 1;
11 
12 boxMe(box);
13 unboxMe(unbox);

What Happened?

The call to boxMe effectively compiles to this:

1 int box = 1;
2 Integer _box = new Integer(box);
3 boxMe(_box);

The call to unboxMe effectively compiles to this:

1 Integer unbox = new Integer(1);
2 int _unbox = unbox.intValue();
3 unboxMe(_unbox);

In both cases, additional overhead is incurred; boxing is way more expensive

Strings

  • Strings are immutable in Java
  • All string manipulation creates a new object
  • Methods that change and return strings are constantly creating new objects

String Methods to Avoid with Prejudice

  • split: creates N new strings, several regex objects, and can't be made to not use regular expressions.

  • concat: this is the same as "a" + "b". Compiles to a new StringBuilder object with tons of append calls. This is partially optimized but brutal in tight loops.

Threads

  • Threads are expensive

  • Use thread pools instead -- you can just object pool those

  • You can't use actual Thread objects with this

  • Use FutureTask instead

Compiler Hints

  • final and static final variables can be and are inlined by the compiler and are well worth using

  • final methods can be inlined by the JIT at runtime, but probably aren't worth using unless there's an identified bottleneck

Profiling

Prerequisites

  • Use jdk 1.6
  • Comes with VisualVM that has CPU and memory profiling
  • Profiling is sampled, so low-overhead
  • If you want to spend money, JProfiler is amazing

Garbage Collection

  • Java's a garbage collected language
  • Variables are reference counted
  • "Garbage" is only collected when the GC runs and no references are held
  • Use VisualVM or JProfiler to find references -- a lot of the time, there will be only one

Fixing Performance Problems

  • Run the CPU profiler
  • Find the class trees with the biggest overhead
  • Eliminate the worst offender
  • Rinse
  • Repeat

Profiling Example: Part 1

50% took less than 11 seconds; max 36 seconds (1000 reqs, 32 concurrency)

Pre-profile

Profiling Example: Part 2

66% took less than 6 seconds; max 12 seconds (1000 reqs, 32 concurrency)

Pre-profile

Questions?