Jeffrey Bosboom's Blog

[blog] [projects] [about]

How many bytes could you save if Java had constructorless classes?

Not enough to be worthwhile.

All Java methods are members of some class, so it’s common to simulate free functions with “utility classes” containing only static methods. These classes are not designed to be instantiable, but the Java compiler will add a public default constructor if they do not declare one, so they typically include a private (and thus inaccessible) constructor to prevent instantiation. Usually the body is empty, but some instead throw an exception to prevent reflective invocation, like java.util.Objects:

private Objects() {
    throw new AssertionError("No java.util.Objects instances for you!");
}

These constructors serve a compile-time purpose only, but take up space in the compiled class files. java.util.Objects is a particularly egregious example, with an exception message string that will be displayed only to developers trying hard to do something they shouldn’t be doing.

While the Java programming language specifies that a default constructor will be generated for any class not declaring other constructors, the Java virtual machine specification does not require a class to contain any constructors at all. Classes lacking instance initialization methods (the JVM term for the methods named <init> that constructors are compiled to) cannot be instantiated by bytecode and reflective attempts to instantiate such a class will fail with NoSuchMethodException, but their static methods can still be called. Omitting constructors has the same effect as private constructors, at no space cost.

To investigate how much space we could save if Java natively supported constructorless classes (perhaps through a @Noninstantiable class annotation), I implemented an ASM-based class file transformer (available as a Gist) that removes unused private constructors. Private constructors can only be accessed by bytecode from within their class (e.g., static factory methods) or enclosing classes, so the transformer don’t need to perform a global analysis. The transformer will remove constructors only used by reflective or native code, so the result is an overestimate of the actual space savings.

The transformer removes 27804 bytes in total from the class files in Oracle JDK 8u40’s rt.jar, 170 of them coming out of java.lang.Objects. A few bytes of these savings are due to better constant pool ordering; without managing the ordering, much of the gain is lost to unnecessary wide indices. rt.jar is 60.2 MiB in size, so saving 27.2 KiB is hardly worth complicating the compiler. (The annotation may still be worthwhile for documentation purposes.)