Class init progression discussion
Dan Heidinga
heidinga at redhat.com
Wed Dec 7 15:17:53 UTC 2022
Previously, we at Red Hat had done some investigation into static final
fields and how existing classes could be adapted to enable their statics to
be initialized at build time.
I'd like to replay some of that investigation here, both to share our
findings and also to gather different perspectives on the problem space.
Our work had been very focused on moving work from runtime to build time
and with legitimizing build time initialization - recognizing two phases:
build & run time. With Leyden's approach to shifting constraints there may
be different outcomes from these kinds of investigations, especially in
light of the recognition that there may be many phases beyond just build
and run times.
I'd like to break the document up into a series of threads on each topic as
that often leads to more focused discussion. I've also posted the full
document is available at [0].
Let's start off with some background on class initialization and a key
question: why don't users group their static fields together in ways to
give them different lifetimes? We have tools to do this today, like the
IODH pattern, why aren't they used more broadly?
Key question:
As we look at shifting work from runtime to build time, we should
understand what drives user behaviour today. What tools are available for
controlling lifecycles of static fields and how popular are those tools?
Broadly used or narrowly? Why?
Background:
Class initialization is a bit of a magic process from the perspective of
users. Users typically believe it happens when the class is loaded rather
than understanding the actual triggers specified in JVMS { new,
get/putstatic, invokestatic, and caused by subclass init, reflection }.
Class init is also difficult to reason about because it takes all the
static field initializations (apart from those done using the ConstantValue
attribute) and puts them together into a single “soupy” method. This method
is “soupy” in that it mixes all the interactions and operations - even
those expressed separately in the source - into a single method that allows
unbounded side effects.
To make matters even less clear, the language and VM have slightly
different rules for how static final fields are written: At the language
level, the compiler enforces that static finals are only written to once
but the VM allows them to be written as many times as needed if done within
the <clinit> method (this used to be if done from within the defining
class).
All this results in the life cycle of every static field in a class being
conjoined, even if they would be better handled in different life cycles.
There is:
* One initialization point
* One method that is called to write the fields
* One duration for all of them (the life of the class or, equivalently, its
classloader)
* One thread that executes the method (good for exactly once semantics)
There is a common case where users recognize these conditions and use them
to produce obviously correct code: the initialization-on-demand holder
idiom. This is used to create singletons that ensure there is only a single
instance of the class, and occasionally used to defer expensive operations
until first use (lazy init).
--Dan
[0] http://cr.openjdk.java.net/~heidinga/leyden/ClassInitPlan.pdf
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20221207/4748f1ab/attachment.htm>
More information about the leyden-dev
mailing list