Category Archives: Java

Testing Clean Cleaner Cleanup

In Replacing Finalizers with Cleaners, the cleanup of resources encapsulated by heap objects is arranged using two complementary mechanisms, try-with-resources, and the Cleaner. Resources needing some kind of cleanup include security sensitive data and off-heap resources such as file descriptors or native handles. The cleanup should always occur and should occur as soon as possible when the resource is no longer active.

One of the questions that arises as Cleaners replace finalizers is how to test that the cleanup is working. For try-with-resources the test is pretty straight-forward, the state we need to verify is in an object that is or can be made accessible to the test and it is clear when to check that’s its done (after the close).

  1. In a try-with-resources statement, create a reference to the object
  2. Extract a reference to the encapsulated state data that is to be cleaned
  3. Exit the try-with-resources statement, implicitly calling close
  4. Check that the cleanup has occurred
  public void testAutoClose() {
    char[] origChars = "myPrivateData".toCharArray();
    char[] implChars;
    try (SensitiveData data = new SensitiveData(origChars)) {
      // Save the sensitiveData char array
      implChars = (char[]) getField(SensitiveData.class,
                       "sensitiveData", data);
    }
    // After close, was it cleared?
    char[] zeroChars = new char[implChars.length];
    assertEquals(implChars, zeroChars,
                 "SensitiveData chars not zero: ");
  }

Fairly straight-forward, the getField utility method below is used to get the private char array to be cleared from SensitiveData.sensitiveData.

  import java.lang.reflect.Field;

  /**
   * Get an object from a named field.
   */
  static Object getField(Class<?> clazz, 
    String fieldName, Object instance) {
    try {
      Field field = clazz.getDeclaredField(fieldName);
      field.setAccessible(true);
      return field.get(instance);
    } catch (NoSuchFieldException | IllegalAccessException ex) {
      throw new RuntimeException("field unknown or not accessible");
    }
  }

Testing the Cleaner of SensitiveData

Verifying the cleanup in the un-reachable case is a little more interesting. The cleanup function won’t be run until sometime after the garbage collector determines the object is un-reachable. The setup is the same, as is checking that the array has been cleared. The try-with-resources is replaced with clearing the reference to the SensitiveData so it can be garbage collected.

  1. Create and hold a reference to the object being cleaned
  2. Extract a reference to the encapsulated data that is to be cleaned
  3. Drop the reference to the object
  4. Request the garbage collector to run
  5. Poll the array, checking for the cleanup to be complete
  public void testCharArray() {
    final char[] origChars = "myPrivateData".toCharArray();
    SensitiveData data = new SensitiveData(origChars);

    // A reference to sensitiveData char array
    char[] implChars = (char[]) getField(SensitiveData.class, 
                         "sensitiveData", data);

    data = null;  // Remove reference

    char[] zeroChars = new char[implChars.length];
    for (int i = 10; i > 0; i--) {
      System.gc();
      try {
        Thread.sleep(10L);
      } catch (InterruptedException ie) { }

      if (Arrays.equals(implChars, zeroChars))
        break;    // break as soon as cleared
    }
    // Check and report any errors
    assertEquals(implChars, zeroChars, 
                 "After GC, chars not zero");
  }

As in the AutoCloseable case above, the internal sensitiveData char array is saved by the test. After the reference to the SensitiveData object is set to null, System.gc() is used to start the garbage collector before checking the array for zeros. The garbage collector runs in parallel, and it may take some time after invoking System.gc() before the garbage collector determines the object is no longer referenced and the Cleaner is notified to call Cleanable.clean() invoking the cleanup function.

This test code directly confirms the array has been cleared. As long as the state that is to be cleaned is visible to the test it works well, but for other cases and classes being tested the state may not always be visible or accessible.

An Alternative Test for Off-heap Resources

For some kinds of resource cleanup, it is not possible for the test to directly observe that the cleanup has or has not occurred. For example, if the resource is a native memory address or a handle, the cleanup function may deal directly with the resource, and it may not be possible for the test to observe that the resource is released or cleared.

The next best thing is to observe that the cleanup function has been called and is complete. To see how that works, we’ll need to understand a bit about how the Cleaner determines when to call the cleanup function.

A Cleaner is a thread that waits for notification that a registered object is no longer reachable and then calls the corresponding cleanup function. When a cleanup function is registered with the Cleaner, the object and its cleanup function are linked by creating and returning a Cleanable. Typically, the Cleanable object is saved in the object, as is done in the SensitiveData example, so that the close method can call Cleanable.clean to invoke the cleaning function and remove the registration.

The Cleanable is implemented as a PhantomReference to the object. The PhantomReference will not keep the object alive and can be queried to know if the object is still alive. During the normal garbage collection process, when the object becomes unreachable, the Cleanable is queued for processing by the Cleaner thread. Until it has been cleaned, the Cleanable is referenced by the Cleaner and won’t be garbage collected. After its cleaning function has been called, the Cleanable itself is freed and garbage collected.

Using the same reference based techniques used to trigger the cleanup, the test can monitor the Cleanable and know it is complete when the Cleanable becomes un-referenced. The test retrieves the Cleanable from the SensitiveData.cleanable field and creates its own PhantomReference to monitor it using its own ReferenceQueue polling utility.

  1. Create and hold a reference to the object being cleaned
  2. Extract a reference to the Cleanable that holds the cleanup function.
  3. Check that the cleanup does not occur before dropping the reference to the object
  4. Drop the reference to the object
  5. Wait for the Cleanable to be no longer referenced
  import java.lang.ref.PhantomReference;
  import java.lang.ref.Reference;
  import java.lang.ref.ReferenceQueue;
  
  public void testCleanable() {
      final char[] origChars = "myPrivateData".toCharArray();
      SensitiveData data = new SensitiveData(origChars);

      // Extract a reference to the Cleanable
      Cleanable cleanable = (Cleaner.Cleanable)
               getField(SensitiveData.class, "cleanable", data);

      ReferenceQueue<Object> queue = new ReferenceQueue<>();
      PhantomReference<Object> ref = 
               new PhantomReference<>(cleanable, queue);
      cleanable = null;   
      // Only the Cleaner will have a strong 
      // reference to the Cleanable

      // Check that the cleanup does not happen 
      // before the reference is cleared.
      assertNull(waitForReference(queue), 
                 "SensitiveData cleaned prematurely");

      data = null;    // Remove the reference 

      assertEquals(waitForReference(queue), ref,
                   "After GC, not cleaned");
  }

This test uses a utility method waitForReference to invoke garbage collection and wait for a reference to be queued. The caller checks if it is the expected PhantomReference to the object.

  /**
   * Wait for a Reference to be enqueued.
   * Returns null if no reference is queued within 0.1 seconds
   */
  static Reference<?> waitForReference(ReferenceQueue<Object> queue) {
    Objects.requireNonNull(queue);
    for (int i = 10; i > 0; i--) {
      System.gc();
      try {
        var r = queue.remove(10L);
        if (r != null) {
          return r;
        }
      } catch (InterruptedException ie) {
        // ignore, the loop will try again
      }
    };
    return null;
  }

This technique for testing cleanup functions relies on knowledge of the implementation of the SensitiveData class and its use of Cleanable objects managed by the Cleaner.

Testing the initial setup of the Cleanable, before the object reference is set to null, verifies that the cleanup is not called prematurely, if so, that’s likely a bug in the test or the implementation.

This technique, waiting for the Cleanable to be called and become un-reachable, is effective independent of whether the cleanup function is chosen to be a simple lambda or an explicit record class, nested class, or top level class. Though it does not directly verify the cleanup, it does verify that the cleanup function has been called and completed.

These are only a few possible ways to write the test, there are many more that take advantage of cooperation with the class being tested. By refactoring the state or allowing the test to break the encapsulation of the class both the cleanup function and the test can have the visibility needed to confirm the cleanup occurs and occurs when expected.


The SensitiveData example code and the SensistiveDataTest test code are available in a SensitiveData Gist. The tests use TestNG for test assertions.

Replacing Finalizers with Cleaners

Cleaners were introduced in JDK 9 as a mechanism to call cleanup functions for heap objects that have security sensitive content or encapsulate non-heap resources. JEP 421: Deprecate Finalization for Removal describes the rationale for replacing finalizers and alternatives for developers. As JEP 421 moves forward, the adoption of Cleaner as a convenient alternative to finalization is getting more interest from developers. These examples show several ways to use cleanup functions to replace finalizers.

Previously, the cleanup would be done in the finalize method of the class. Even though the object is un-reachable, when the finalize method is called it has access to all of fields of the object and can change them.

Cleanup functions, like finalizers, are run when an object is found to be unreachable from any class or thread. Unlike a finalizer, a cleanup function holds the state needed for cleanup separately from the object because we want the object to be reclaimed as soon as it is unreachable. The cleanup function must be able to work independently from the object. If there are any references to the object from the cleanup function it would still be reachable and could not be reclaimed. Any state needed for the cleanup must be encapsulated in the cleanup function.

I’ll use italics to refer to instances including object O, cleanup function F, Cleaner C, and Cleanable A to help keep track of what’s what.

An object O and corresponding cleanup function F is registered with a Cleaner. The registration is usually done in the constructor of object O. The Cleaner returns a Cleanable holding the reference to the object O and its cleanup function F. Calling Cleanable.clean() runs the cleanup function at most once. The Cleaner has a thread that waits for registered objects to become unreachable and runs the corresponding cleanup function F. A Cleaner and its thread is fairly heavyweight and should be shared within an application, package, or class where possible.

Due of the close relationship between the object O, the cleanup function F, and the Cleaner, there are several potential coding pitfalls to watch out for. A concrete example illustrates how to write a cleanup function to take advantage of the garbage collector as it reclaims objects.

The class SensitiveData holds data that should be erased when it is no longer in active use. The data is erased in a cleanup function (initially using a lambda) by clearing the internal char array. It is called either by the close method or by the Cleaner when its instances become unreachable. SensitiveData implements AutoCloseable to encourage its use within try-with-resources promoting the idea that cleanup should occur as early as possible.

In un-structured contexts, where try-with-resources is not suitable, the close method should be called directly. The Cleaner serves as a fallback when a SensitiveData object is no longer referenced and the cleanup has not been called by the close method.

In the example, we’ll show the cleanup function is implemented using a lambda or a class SensitiveCleanable. All the state needed for the cleanup is encapsulated in the cleanup function.

import java.lang.ref.Cleaner;
import java.util.Arrays;
import java.util.Optional;

public class SensitiveData implements AutoCloseable {
  // A cleaner
  private static final Cleaner cleaner = Cleaner.create();

  // The sensitive data
  private char[] sensitiveData;
    
  // The result of registering with the cleaner
  private final Cleaner.Cleanable cleanable;

  /**
   * Construct an object to hold sensitive data.
   */
  public SensitiveData(char[] sensitiveData) {
    final char[] chars = sensitiveData.clone();
    final Runnable F   // Pick one 
      = () -> Arrays.fill(chars, (char) 0);// A lambda      
      // = new SensitiveCleanable(chars);  // A record 
      // = clearChars(chars);              // A static lambda
    this.sensitiveData = chars;
    this.cleanable = cleaner.register(this, F);
  }

  /**
   * Return an Optional of a copy of the char array.
   */
  public Optional<char[]> sensitiveData() {
    return Optional.ofNullable(sensitiveData == null
          ? null : sensitiveData.clone());
  }

  /**
   * Close and cleanup the sensitive data storage.
   */
  public void close() {
    sensitiveData = null;   // Data not available after close
    cleanable.clean();
  }

  /*
   * Return a lambda to do the clearing.
   */
  private static Runnable clearChars(char[] chars) {
    return () -> Arrays.fill(chars, (char)0);
  }

  /*
   * Nested record class to perform the cleanup of an array.
   */
  private record SensitiveCleanable(char[] sensitiveData) 
                 implements Runnable {
    public void run() {
      Arrays.fill(sensitiveData, (char)0);
    }
  }
}

A brief example showing SensitiveData used with try-with-resources and the clearing of temporary arrays to minimize the time the sensitive data is visible in process memory.

public class Main {
  // Encapsulate a string for printing
  public static void main(String[] args) {
    for (String s : args) {
      char[] chars = s.toCharArray();
      try (var sd = new SensitiveData(chars)) {
        Arrays.fill(chars, (char) 0);
        print(sd);
      }
    }
  }

  // Print the sensitive data and clear
  private static void print(SensitiveData sd) {
    char[] chars = sd.sensitiveData().get();
    System.out.println(chars);
    Arrays.fill(chars, (char) 0);
  }
}

Coding the Cleanup Function

Let’s take a closer look as the pros and cons of various cleanup function coding choices. Each of these expose a FunctionalInterface method with no arguments that performs the cleanup using only values it holds itself.

A lambda cleanup function

class Foo {
  private final char[] data;
    
  Foo(char[] chars) {
    final char[] array = chars.clone();
    cleaner.register(this, 
          () -> Arrays.fill(array, (char)0));
    this.data = array;
  }
}

For simple cleanup, a lambda is the concise and can be coded in-line in a constructor. It is easy to code but it may be hard to spot mistakes and verify that it is working as expected. For example, if chars is a field, the lambda could refer to this.chars inadvertently capturing this. Unless there were tests written to check that it was cleared, you might not notice that the the object is not collected and the cleanup does not occur.

One way to ensure that this is not captured is to create the lambda in a static method. Its scope does not have this so it cannot accidentally be captured.

private static Runnable clearChars(char[] chars) {
  return () -> Arrays.fill(chars, (char)0);
}

A cleanup function as record, nested, or top level class

class Foo {
  // Record class to clear an array.
  private record Cleanup(char[] array)
          implements Runnable {
    public void run() {
      Arrays.fill(array, (char) 0);
    }
  }

  private char[] data;

  // Copy the array and register the cleanup.
  Foo(char[] chars) {
    final char[] array = chars.clone();
    cleaner.register(this, new Cleanup(array));
    this.data = array;
  }
}

A record class, nested static class, or top level class is the most robust way to write a successful cleanup function. It provides good visibility into the separation of the state that is independent of the object and provides a place to document the cleanup.

Do not be tempted to make the cleanup function an inner class, it implicitly refers to this and the cleanup will not be run.

Cleanup Using Mutable State

Though in most cases the record or nested class fields are immutable, there are use cases where the state needed for cleanup is changed during the normal use of the object O and therefore needs to be mutable. Holding the mutable state in an object accessible to both object O and the cleanup function F is a straight-forward way to handle this case. If multiple threads are modifying the state, they will need to have some kind of synchronization. When a Cleaner is shared among different uses, and synchronization is needed for the state, the cleanup function might block the Cleaner thread. As with any synchronization, check carefully that the synchronization does not result in deadlock or delay in the cleanup. For example, if the cleanup involved closing a network connection, it would be prudent to fork off a task independent of the Cleaner thread.

Wrap up

The most lightweight cleanup function can, with careful attention to detail, be a lambda. The lambda must not refer to this implicitly or explicitly. The lambda body cannot refer to fields in the constructor. This is more restrictive than the usual allowance to use final or effectively final values.

Converting to cleanup functions from finalizers may require a bit of refactoring to separate out the state. The refactoring can improve the structure and reliability of the class.


The complete example with the various options is available from SensitiveData.java.