javaperformancegarbage-collectionreal-timeobject-pooling

How to Implement an object pool in Java that does not create garbage as it grows?


My Java application has excessive garbage collection due to its frequent object creation and disposal, so I want to implement an object pool to reuse objects and reduce GC overhead. How can I design a garbage-free object pool that dynamically adjusts its size (without creating garbage) based on application load? Can you provide an example implementation or guidance for pooling strategies, especially for pool growth?


Solution

  • The best data structure to use is a native array due to its better cache locality. The key challenge is to allow it to grow without generating garbage. This can be achieved by storing the previous array in a SoftReference:

    int newSize = int (growthFactor * this.array.length);
    E[] oldArray = this.array;
    this.array = (E[]) new Object[newSize];
    // copy from oldArray to this.array (if necessary)
    // nullify any object left on oldArray (if necessary)
    oldArrays.add(new SoftReference<E[]>(oldArray));
    

    You can try implementing this approach yourself as an exercise, or you can use CoralPool, which is an open-source garbage-free solution optimized for performance. For a deeper dive into writing garbage-free Java code, check out this video.

    On a side note, using java.util.LinkedList is not ideal because, like many other Java collections, it generates a significant amount of garbage. This creates a contradiction: you’re trying to minimize garbage using an object pool, but the pool itself contributes to garbage creation. Clearly, that's not an efficient approach. Run the code below to observe how much garbage it generates:

    final LinkedList<Object> list = new LinkedList<>();
        
    final Object object = new Object();
    final Random random = new Random();
        
    long start = System.currentTimeMillis();
        
    for(int i = 0; i < 50_000; i++) {
        int toAdd = random.nextInt(1_000) + 1;
        int toRemove = random.nextInt(1_000) + 1;
        for(int x = 0; x < toAdd; x++) list.add(object);
        for(int x = 0; x < toRemove; x++) if (!list.isEmpty()) list.removeLast();
    }
    
    long time = System.currentTimeMillis() - start;
    System.out.println("List size: " + list.size() + " after " + time + " millis");

    
    
$ java -verbose:gc -cp . TestLinkedList 
    
    [0.003s][info][gc] Using G1
    [0.034s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->1M(388M) 0.796ms
    [0.043s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 29M->1M(388M) 1.587ms
    [0.060s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 61M->2M(388M) 2.989ms
    [0.082s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 78M->3M(388M) 4.149ms
    [0.107s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 111M->1M(820M) 1.293ms
    [0.149s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 137M->2M(820M) 2.541ms
    List size: 38609 after 166 millis