Reducing classes loaded by ClassFile API usage
Claes Redestad
claes.redestad at oracle.com
Thu May 2 11:34:38 UTC 2024
Hi,
Looking at replacing ASM with the ClassFile API (CFA) in various places we have observed both startup and footprint regressions. Startup times increase 4-5 ms on Hello World, 40 ms on a small GUI app and 250ms on a larger app. So there’s both a one-off cost and a scaling factor here.
We’ve been doing some analysis and picked a lot of low-hanging fruit. Bytecode executed has been reduced to about the same level and we’ve found improvements in dependencies such as the java.lang.constant API. All good. And the number of classes loaded on a Hello World style application has dropped by about 50. Great!
Still the overall picture persists: a Hello World style application that initializes a lambda takes a wee bit longer and the footprint is decidedly. The main culprit now that some low-hanging fruit has been plucked seem to be that the trivial use of CFA to spin up lambda proxies is loading in about 160 classes: An ASM-based baseline loads 691 classes. The best recent CFA version (a merge of https://github.com/openjdk/jdk/pull/19006 and https://github.com/openjdk/jdk/pull/17108) loads 834. A net 143 class difference.
Loading classes slows down startup, increases memory footprint, grows the default CDS archive. And involving more classes - and more code - is often costly even accompanied with some of the solutions being explored to ”fix” startup at large.
So why is this?
The CFA is mainly split up into two package stuctures, one public under java.lang.classfile and one internal under jdk.internal.classfile.impl. In the public side most of the types are sealed interfaces, which are then implemented by an assortment of abstract and concrete classes under jdk.internal.classfile.impl. Very neat. But I do fear this means we are at least doubling the number of loaded classes from this neat separation.
While it’s a bit late in the game I still feel I must propose striking up a conversation about what, if anything, we could consider that would reduce the number of loaded classes. Whether they are interfaces, abstract or concrete classes. I think any savings would be very welcome.
Here’s an idea:
There are a number of cases where the separation seem unnecessary:
public sealed interface ArrayLoadInstruction extends Instruction
permits AbstractInstruction.UnboundArrayLoadInstruction {
…
static ArrayLoadInstruction of(Opcode op) {
Util.checkKind(op, Opcode.Kind.ARRAY_LOAD);
return new AbstractInstruction.UnboundArrayLoadInstruction(op);
}
}
An interface in java.lang.classfile.instruction which only permits a single implementation class - and as it happens has a static factory method which is the only place where that concrete instruction is called.
Making single-use interfaces such as this one a final class is doable[1], but now we’d have some instructions modeled as an interface, others as classes. Cats and dogs, living together. And it gets messy quick for instructions that can be bound or unbound, since those inherit from abstract BoundInstruction or UnboundInstruction respectively. But perhaps internal implementation details like whether an instruction is bound or unbound ought to be modeled with composition rather than inheritance (and optional CodeImpl + pos tuple) in a shared base class? Then it might follow that each of the interfaces in java.lang.classfile.instruction can really be a single final class. If all concrete instructions were folded into their corresponding interface that could reduce the total number of implementation classes by 46 (though only 6 of those seem to be on a Hello World)
Yikes, that’s a deep cut for a small, incremental gain. From an API consumer point of view I can’t say there’s much difference, and the factories can still (be evolved to) produce different concrete types when necessary.
Maybe someone can think of other, simpler ways to reduce the number of types floating around in the ClassFile API?
Thank you for your consideration.
Claes
[1] https://github.com/openjdk/jdk/compare/master...cl4es:jdk:fold_instruction_example?expand=1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20240502/7b4b875d/attachment.htm>
More information about the classfile-api-dev
mailing list