1

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 ]

  • 1
    The JIT might. But worrying about such a tiny optimization is almost certainly pointless in a modern system. You realize computers have gigabytes of memory now, right? When Java was written you were lucky to have 32 megabytes. I think my computer had 5 megabytes at the time. And it would still have been pointless to worry about the space of a single `int`. Maybe in the days of the Atari 2600 it was a problem. – Elliott Frisch Jun 29 '21 at 00:06
  • 1
    Actually, I think that the JIT cannot do this in most cases. The problem is that is that if it does optimize them away, and then they turn out to be needed (for debug, in a new subclass, etc), the JVM cannot find / update the object node to add space to store the field. – Stephen C Jun 29 '21 at 00:13
  • @ElliottFrisch yes but what if you were dealing with an array that was 65536 bytes long, that feels like a lot of memory to waste. – slit bodmod Jun 29 '21 at 00:18
  • @StephenC ah yes, in my specific case there are times the superclass or other subclasses are used it is purely in this specific subclass (although implicitly all subclasses of that too) that it might cause a problem – slit bodmod Jun 29 '21 at 00:19
  • 1
    *"but what if you were dealing with an array that was 65536 bytes long"*. In Java, arrays are separate heap nodes, so the field only needs the space for the reference to the array; i.e. 4 or 8 bytes. If the array is not needed, then field should not have been initialized ... by the class that the field belongs to. (In theory, a redundant allocation *could* be optimized away, but I doubt it would be implemented ... for similar reasons to my previous comment.) – Stephen C Jun 29 '21 at 00:35
  • 1
    So, in your examples, the memory used by the `C` field is either 4 or 8 bytes, not 1024. – Stephen C Jun 29 '21 at 00:37

1 Answers1

2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Ah that makes sense, I kinda forgot that in java large data structures are not bundled in with the objects they are part of. So essentially if I never construct the long[512], it never get's created and that memory isn't sitting around unused. – slit bodmod Jun 29 '21 at 22:46