Lazy statics (was: Feedback / query on jextract for Windows 10)

Brian Goetz brian.goetz at oracle.com
Wed Feb 3 18:35:11 UTC 2021


Obviously this is ground that has been at least partially trodden, and 
past attempts to traverse this territory has not yet yielded a clear 
roadmap, hence it's still on the shelf, despite its obvious 
attractiveness.  Let me try and enumerate some of the loose ends.

We seek laziness because we want to defer a calculation into the future, 
where it might not need to happen.  This applies both to statics and 
cached properties of objects.  Computing a static final field now must 
happen in <clinit>, which happens before any other useful work does; 
computing an instance field in the constructor happens before anyone can 
do anything useful with the object.  Laziness gives us a chance to avoid 
the computation until we need it.  It always comes with a tradeoff, 
which might be complexity in the programming model, or coordination 
overhead (CASes and such.)

Programmers want laziness built into their model because it lets them 
express what they want without having to imperatively implement it.  
Platform creators like laziness because the author gives us more 
information about program invariants, which the platform can then 
usefully exploit.  The key new information is "write arity."  Today, 
Java offers us two write-arity options; exactly once (final) or 
"anything goes".  Laziness expresses a zero-or-one write arity, along 
with tacit permission to delay side-effects until the value is needed.

 From a programmer perspective, both the instance and static paths are 
attractive; we have all done more work than we'd like (and made more 
mistakes than we should) implementing lazy initialization in both 
instance and static state.  But from a platform perspective, the static 
case is more compelling, because there's more that the runtime can do to 
optimize lazy statics -- it can treat them as trustable statics that are 
grist for the inlining mill.  There are fewer options here for instances.

Without painting the bikeshed, let me point out that there are two 
possible user models for capturing laziness: lazy fields and cached 
methods.  Lazy fields are the more obvious:

     __lazy static final String s = computeABigString();

but we could frame this equivalently as

     __cached static String computeABigString() { ... }

There are pros and cons to each, but this is largely a user-model 
question about the story we tell.  Saying this is a field, vs a cached 
method, sends a slightly different message.

Separately, there's a hidden possible requirement we should try to be 
explicit about: whether we care about binary compatible migration for 
_public_ lazy static fields.  If we have today:

     public static final String s = ...

and some *other* class accesses the field, there will be `getstatic` 
instructions in the wild that expect the field to be there.  But, this 
would constrain the ability to translate a read of a lazy static as 
invoking a method whose body does an LDC, which might be a preferable 
translation.   I am not expressing an opinion on whether this is a 
requirement or not, as much as calling it out as something we could 
_choose_ to take on, or not, with full awareness of the tradeoffs.

Reframing as caching makes it more obvious that we have to be clear 
about what guarantees we are offering, especially in the instance case; 
are we guaranteeing to execute the method no more than once, or is it OK 
for multiple threads to race to initialize the cached value?  What 
visibility guarantees do we make?

Finally, there's exceptions.  One of the pain points we have today is 
initializing method handles in static finals; we can't say

     static final MethodHandle mh = ... lookup ...

because of the exceptions.  THis is a mere annoyance with finals, where 
we'd catch and rethrow the exception in the <clinit> and the client 
would get an ExceptionInInitializerError, but worse with lazies, since 
the timing of the exception can be any time and any place.

I'm not writing all this so that we can post our pet solutions now; I'm 
trying to capture everything in one place.  I'm sure I forgot something; 
let's try to get a complete picture of the problem space.




On 2/3/2021 7:46 AM, Maurizio Cimadamore wrote:
> On Mon, 2021-02-01 at 09:31 +0100, Remi Forax wrote:
>> Another intermediary solution is to implement the lazy keyword in the
>> compiler (javac) but only enable it under a flag. So that flag can be
>> used when compiling java.base and jextract generated classes.
> I'd avoid such hacky solutions :-)
> In my experience, people look at those "hidden" flag, and then before
> you know it, you start seeing code in the wild that depends on them
> (few years ago I saw code still using -jsr14).
>
>>
>>
>> For me the non-static lazy is a non problem, Java already considers
>> separately final field and static final field, so lazy can be only
>> allowed on static final fields, but neither on static fields nor on
>> non-static fields.
> I tend to agree - allowing dynamic constants in the constant value
> attribute seems to be a straightforward generalization of what we have
> now.
>
> Exploring the space of non-static lazy final is a possibility, but I
> don't think that exploration should block what would seem like a
> generally useful strategy (being forced to use classfile generation
> just because there's no language support for lazy static constants
> seems like a suboptimal situation).
>
> My intuition also tells me that non-static constant will likely not be
> as interesting; at least in the method handle/var handle case you want
> these things to be static - so that C2 can see their constant-ness -
> so, if a "Constable" instance is likely what we'd like to use the lazy
> support for, then I'd say that tying that to "static" seems a
> relatively forced move (at least given the VM we have today).
>
> Maurizio
>



More information about the panama-dev mailing list