slit bodmod
slit bodmod

Reputation: 101

Will the JVM optimise out unused fields

I'm trying to get to learn more about the JVM when it comes to optimising my code and was curious whether (or more specifically in which ways) it optimises out unused fields?

I assume that if you have a field within a class that is never written too or read from, when the code is run this field will not exist within the class. Say you had a class that looked like this:

public class Foo {
    public final int A;
    public final float B;
    private final long[] C = new long[512];
}

and you only used variables A and B, then you can probably see how initiating, maintaining and freeing variable C is a waste of time for what is essentially garbage data. Firstly would I be correct in assuming the JVM would spot this?

Now my second and more important example is whether the JVM takes inheritance into consideration here? Say for example Foo looked more like this:

public class Foo {
    public final int A;
    public final float B;
    private final long[] C = new long[512];

    public long get(int i) {
        return C[i];
    }
}

then I assume that this class would be stored somewhere in memory kinda like:

[ A:4 | B:4 | C:1024 ]

so if I had a second class that looked like this:

public class Bar extends Foo {
    public final long D;
    
    @Override public long get(int i) {
        return i * D;
    }
}

then suddenly this means that field C is never used, so would an instance of Bar in memory look like this:

[ A:4 | B:4 | C:1024 | D:8 ] or [ A:4 | B:4 | D:8 ]

Upvotes: 1

Views: 604

Answers (1)

Holger
Holger

Reputation: 298459

To prove that a field is entirely unused, i.e. not only unused til now but also unused in the future, it is not enough to be private and unused with the declaring class. Fields may also get accessed via Reflection or similar. Frameworks building upon this may even be in a different module, e.g. Serialization is implemented inside the java.base module.

Further, in cases where the garbage collection of objects would be observable, e.g. for classes with nontrivial finalize() methods or weak references pointing to the objects, additional restrictions apply:

JLS §12.6.1., Implementing Finalization

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.

Another example of this occurs if the values in an object’s fields are stored in registers. The program may then access the registers instead of the object, and never access the object again. This would imply that the object is garbage. Note that this sort of optimization is only allowed if references are on the stack, not stored in the heap.

This section also gives an example where such optimization would be forbidden:

class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
}

The specification emphasizes that even being otherwise entirely unused, the inner object must not get finalized before the outer object became unreachable.

This wouldn’t apply to long[] arrays which can’t have a finalizer, but it makes more checks necessary while reducing the versatility of such hypothetical optimization.

Since typical execution environments for Java allow to add new code dynamically, it is impossible to prove that such an optimization will stay unobservable. So the answer is, there is no such optimization that would eliminate an unused field from a class in practice.


There is, however, a special case. The JVM may optimize a particular use case of the class when the object’s entire lifetime is covered by the code the optimizer is looking at. This is checked by Escape Analysis.

When the preconditions are met, Scalar Replacement may be performed which will eliminate the heap allocation and turn the fields into the equivalent of local variables. Once your object has been decomposed into the three variables A, B, and C they are subject to the same optimizations as local variables. So they may end up in CPU registers instead of RAM or get eliminated entirely if they are never read or contain a predictable value.

Not that in this case, you don’t have to worry about the inheritance relation. Since this optimization only applies for a code path spanning the object’s entire lifetime, it includes its allocation, hence, its exact type is known. And all methods operating on the object must have been inlined already.

Since by this point, the outer object doesn’t exist anymore, eliminating the unused inner object also wouldn’t contradict the specification cited above.

So there’s no optimization removing an unused field in general, but for a particular Foo or Bar instance, it may happen. For those cases, even the existence of methods potentially using the field wouldn’t impose a problem, as the optimizer knows at this point, whether they are actually invoked during the object’s lifetime.

Upvotes: 2

Related Questions