Memory consumption of anonymous classes vs. concrete subclass

Jesper Steen Møller jesper at selskabet.org
Fri Mar 26 15:44:55 UTC 2021


Hi Nir and list

(I'm sorry if I'm over-explaining this, but it's an interesting issue -- maybe the details would interest others, too)

Solutions for right now:
In your example, you're building an anonymous class, but it still holds a reference (to ThisClass.this) whereas SimpleSomeObjectProperty needs to store two, one for each field. With the current 16 byte object header in OpenJDK, the effective difference is a jump from 20 and 24 bytes (or 24 and 32 on heaps without compressed OOPS). Perhaps you're better off making a flyweight cache or something similar, since you know best what the intended call pattern is.

Another approach would be to change the abstract class into a functional interface and then harness the power of lambdas and let hotspots' magic powers do the heavy lifting:

@FunctionalInterface
public interface SomeObjectProperty {
    Pair<String, Object> getBeanInfo();
    default String getName() {
        return getBeanInfo().getKey();
    };
    default Object getBean() {
        return getBeanInfo().getValue();
    };
}

Then your anonymous class usage would become a bit more elegant:

   SomeObjectProperty prop = () -> new Pair<>("prop ", ThisClass.this);

To guess the weight of the underlying lambda proxy, inspect the generated lambda implementation method in the enclosing class. In this case, it's:
  private javafx.util.Pair lambda$0();
Lambda method is not static: This means that this is captured, which is clear from the lambda expression. The lambda proxy object would simply capture the this-reference and call the lambda getBeanInfo into the generated lambda class.
No parameters on the lambda implementation method: This means no further values are stored in the lambda proxy object.

Overhead of the pair workaround: I'm assuming that in the worst case, the Pairs would get collected easily from the eden space, and in the best case, hotspot would conclude that the Pair doesn't escape, and avoid allocating it altogether, and inline and optimize out the unneeded assignments.

See below for possible other solutions:

> On 26 Mar 2021, at 14.41, Nir Lisker <nlisker at gmail.com <mailto:nlisker at gmail.com>> wrote:
> 
> Hi David,
> 
> Thanks for the reply.
> 
> What role does ThisClass play here?
>> 
> 
> It's the enclosing class of `SomeObjectProperty prop`. It should have been
> a placeholder for some Object.
> 
> But the code is not effectively the same. The concrete class is a
>> general class that can be instantiated multiple times for different
>> beans and names - hence the fields are needed to store them. The
>> anonymous class is for a singleton instance hard-wired to return one
>> bean and one name. You could define a named class, specific to a given
>> property, with no fields and which was also hard-wired the same way.
>> 
> 
> When I instantiate either of these classes, at the end of the day, I want
> to return 2 constant values - the bean and the name.

Nitpicking: Actually, ThisClass.this is not constant across all instances of the anonymous class (so it tracks the ThisClass it was instantiated in), so there still needs to be that one reference.
If each ThisClass instance needs to give out many such identical SomeObjectProperty instances, it really should cache it instead.

> The concrete class is
> immutable (no setters, and I should have put the fields as private final),
> so once I create an instance of it, it is also hard-wired to return 2
> specific values.
> Another way to phrase my question is: is there a mechanism in which I can
> create these anonymous classes by specifying the only important
> information - the value of those 2 fields - without taking readability
> punishment.
> Maybe this thought experiment would help me: *what if* when I instantiate
> the concrete class, it is converted in the background into the memory model
> of the abstract class. What breaks in this case considering that they both
> need to hold 2 constant values?

I guess theoretically, a future JVM could do this for immutable instances (records), by recording instantiation counts by each field (or combination of fields) as const-candidates, and slicing out new class variations for hot candidates. But that seems very impractical and far fetched, and perhaps not what you meant.

It's perhaps be a better idea to allow the runtime to produce a flyweight pattern for classes frequently moved from eden space to older generations, and de-dup'ing those instances. You'd have to abandon object identity, which is guaranteed in your outlined solution, but I can't tell from context if it important.

More to convention, a future Java *could* have constant value parameters (like C++ and Rust):

class SimpleSomeObjectProperty<String S> extends SomeObjectProperty {

    Object bean;

    SimpleSomeObjectProperty(Object bean) {
        this.bean = bean;
    }

    @Override
    abstract String getName() { return S; }

    @Override
    abstract Object getBean() { return bean; }
}

Your ThisClass could then do "SomeObjectProperty prop = make SimpleSomeObjectProperty<"prop ">(this)"
Each such specialisation site would carry a constant space+time cost of creating a new version of the methods affected, and likely an extra layer of dispatch for each 'getName' call. And it would require some person-years of investment into the JVM and javac ;-)

-Jesper


More information about the valhalla-dev mailing list