You’re sick of Java loggers being initialized at class initialization, which messes up your whole run? John Rose to the rescue!
This is what it might look like :
lazyprivate finalstaticLogger LOGGER = Logger.getLogger("com.foo.Bar");
This document extends the behavior of final variables to allow lazyexecution to be supported at will, both in the language itself and in the JVM.The behavior of existing lazy computation mechanisms is proposed to be improved by changing the granularity: now it will not be with the precision of a class, but with the precision of a particular variable.
Lazy computation is deeply embedded in Java.Almost every linking operation can pull lazy code. For example, executing a method
.. <clinit> (class initializer bytecode) or using the bootstrap method (for
Class initializers are something very crude in terms of granularity when compared to mechanisms that use bootstrap methods, since their contract is to run everything initializing code for the class entire , instead of limiting the initialization to a specific field of the class. The effects of such crude initialization are difficult to predict. It is difficult to isolate the side effects of using one static class field, since computing one field results in computing all static fields of this class.
If you affect one field, you affect them all. In AOT compilers, this makes it particularly difficult to optimize static field references, even for fields with easily analyzed constant values. If you have at least one overcomplicated static field, and absolutely all fields of this class become impossible to analyze. A similar problem appears with the previously proposed mechanisms for implementing constant convolution (at runtime javac ) for constant fields with complex initializers.
An example of an overcomplicated field initialization that occurs at every step, in every file, in different projects is the logger initialization.
private final static Logger LOGGER = Logger.getLogger("com.foo.Bar");
This innocuous-looking initialization starts a huge amount of work under the hood that will be done during class initialization – and yet it is highly unlikely that a logger is really needed at the time of class initialization, and may not be needed at all. The possibility to postpone its creation until the first real use will simplify initialization and in some cases allow you to avoid this initialization altogether.
Final variables are very useful, they are the main mechanism of the Java API for pointing to constant values. Lazy variables are also well established. Since Java 7, they have begun to play an increasingly important role in the internals of the JDK, being tagged with the annotation
@Stable JIT can optimize both final and "stable"-variables much better than just some variables. Adding lazy final variables will allow this useful usage pattern to become more common, allowing it to be used in more places. Finally, using lazy final variables will allow libraries such as JDK to reduce dependency on code
<clinit> which, in turn, should reduce startup time and improve the quality of AOT optimizations.
The field can be declared with the new modifier
lazy which is a context keyword, taken solely as a modifier. Such a field is called a lazy ( lazy field ), and is also obliged to have the modifiers
A lazy field must have an initializer. The compiler and runtime agree to run the initializer exactly when the variable is first used, not when the class to which the field belongs is initialized.
lazy static final field is linked at compile time with a constant pool element that represents its value. Since constant pool elements are themselves computationally lazy, it is sufficient to simply assign a properly matched value to each static lazy final variable associated with that element. (You can assign more than one lazy variable to an element, but this is hardly a useful or meaningful feature.) The attribute name is.
LazyValue , and it must refer to a constant gender element that can be ldc-shaped into a value that is convertible to a lazy field type. Only those conversions that are already used in
So a lazy static field can be thought of as a named alias to a constant pool element within the class that declared the field. Tools like compilers can somehow try to use this field in their own way.
The lazy field is never a constant variable (in the sense of JLS 4.12.4) and is explicitly excluded from participating in constant expressions (in the sense of JLS 15.28). Therefore, it never captures the attribute
ConstantValue , even if its initializer is a constant expression. Instead, the lazy field captures a new kind of classfile attribute called
LazyValue which the JVM checks against when linking to that particular field. The format of this new attribute is similar to the previous one in that it also points to a constant pool element, in this case the one that resolves to the field value.
When a lazy static field is linked, the usual process of executing class initializers is not should disappear. Instead, any method
<clinit> of the declaring class is initialized according to the rules defined in JVMS 5.5. In other words, the bytecode
getstatic for a lazy static field performs all the same linking as for of any static field. After initialization (or during the already-launched initialization of the current thread), the JVM resolves the constant pool elements associated with the field, and saves the values obtained from the constant pool into that very field.
Since lazy static final cannot be empty, no values can be assigned to them – even in the small number of contexts where this works for empty final variables.
At compile time, all lazy static fields are initialized independently of non-lazy static fields, regardless of their location in the source code. So, the constraints on the location of static fields do not apply to lazy static fields. The initializer of a lazy static field can use any static field of the same class, regardless of the order in which they occur in the source code. Any non-static field initializer or class initializer can refer to a lazy field, regardless of the order in which they occur relative to each other in the source. Usually doing this is not the smartest idea, because it loses all meaning of lazy values, but maybe it can be used somehow in conditional expressions or on the control flow. So lazy static fields can be treated more like fields of another class – in the sense that they can be referenced in any order from any part of the class in which they are declared.
Lazy fields can be detected with the reflection API using two new API methods in
java.lang.reflect.Field. The new method
true if and only if the field has the modifier
lazy New method
false if and only if the field is lazy and still not initialized at the time of running
isAssigned (It may return trueon almost the next call in the same thread, depending on the availability of races.) There is no way to know if a field is initialized other than with
isAssigned is only needed to help with the rare problems related to resolving cyclic dependencies. We can probably do without implementing this method. Nevertheless, people who write code with lazy variables occasionally want to find out carefully whether a value is set to such a variable or not yet, in much the same way that mutex users sometimes want to know if a mutex is locked or not, but don’t want to really get locked)
There is one unusual limitation on lazy final fields: they must never initialize to their default values. That is, the lazy reference field must not initialize to
null , and numeric types must not have a null value. A lazy boolean value can be initialized with just one value –
false is its default value. If the initializer of a lazy static field returns its default value, linking that field will crash with a corresponding error.
This constraint is introduced to allow JVM implementations to reserve default values as an internal watchdog value marking the state of an uninitialized field. The default value is already set in the initial value of any field, set at the time of preparation (this is described in JLS 5.4.2). So this value naturally already exists at the beginning of any field’s lifecycle, and is therefore a logical choice to use as a watchdog value tracking the state of that field. Using these rules, you can never get the original default value out of a lazy static field. To do this, the JVM can, for example, implement the lazy field as an immutable reference to the corresponding constant pool element.
Restrictions on default values can be circumvented by wrapping values (which may be equal to the default) in boxes or containers of some convenient kind. A zero number can be wrapped in a non-zero reference to an Integer. Non-primitive types can be wrapped in Optional, which becomes empty if it hits null.
To support the freedom in the way the fic is implemented, the requirements on the method
isAssigned are purposely understated. If the JVM can prove that a lazy static variable can be initialized without observable external effects, it can do this initialization at any time. In this case,
isAssigned will return
true even if
getfield has never been called. At
isAssigned is only subject to the requirement that if it has returned
false then none of the side effects from initializing the variable should be observed in the current thread. And if it returned
true then the current thread can observe side effects of initialization in the future. Such a contract allows the compiler to substitute
getstatic for its own fields, which allows the JVM not to deal with tracking the detailed states of final variables that have common or degenerate elements in the constant pool.
Several tracks can come to a state of race to initialize a lazy final field. As is already the case with
CONSTANT_Dynamic. , the JVM chooses an arbitrary winner of that race and provides that winner’s value to all participating tracks, and records it for all subsequent attempts to get the value. To bypass races, specific JVM implementations can try using CAS operations if the platform supports them – the race winner will see the previous default value, and the losers will see the non-defaulted value that won the race.
Thus, the existing single assignment rules for final variables continue to work and now capture all the complexities of lazy computation.
The same logic applies to secure publishing with final fields – it’s the same for both lazy and non-lazy fields.
Note that a class can convert a static field to a lazy static field without breaking binary compatibility.Client Instruction
getstatic is identical in both cases. When the variable declaration is changed to lazy,
getstatic is linked in a different way.
You can use nested classes as containers for lazy variables.
You can define some sort of library API for managing lazy values or (more generally)any monotone data.
Refactor what were going to make lazy static variables so that they turn into nullary static methods and their bodies are published with ldc CONSTANT_Dynamic constants, some way.
(Note. The above workarounds do not provide a binary-compatible way to evolutionarily decouple existing static constants from their entanglement with
When it comes to providing more functionality, lazy fields can be allowed to be non-static or non-final, preserving the current correspondences and analogies between the behavior of static and non-static fields. A constant pool won’t be able to be a repository for non-static fields, but it can still hold bootstrap methods (depending on the current instance). Frozen arrays (if implemented) could get a lazy option. Such explorations are a good basis for future projects built from this document. And by the way, such features make our decision to disallow default values even more meaningful.
Lazy variables must be initialized with their own initializing expressions. Sometimes this seems like a very unpleasant restriction that throws us back to the days of inventing empty final variables. Remember that these empty final variables can be initialized by arbitrary blocks of code, including try-finally logic, and they can be initialized in groups rather than simultaneously. In the future, you could try to apply the same features to lazy final variables as well. Perhaps one or more lazy variables could be associated with a private block of initializing code whose job it is to assign each variable exactly once, as with the class initializer or object constructor. The architecture of such a feature may become clearer once deconstructors appear, since the tasks they solve overlap in some sense.
A moment of publicity. The Joker 2018 conference is coming up very soon, with a host of prominent Java and JVM experts. To see the full list of speakers and papers, you can at the official website
John Rose – JVM engineer and architect at Oracle. Lead engineer for Da Vinci Machine Project (part of OpenJDK). Lead engineer for JSR 292 (Supporting Dynamically Typed Languages on the Java Platform), working on the specification of dynamic calls and related issues such as type profiling and improved compiler optimizations. Formerly worked on inner classes, did the original HotSpot port to SPARC, Unsafe API, and developed many dynamic, parallel and hybrid languages, including Common Lisp, Scheme ("esh"), dynamic bindings for C++.
Oleg Chirukhin – At the time of writing this text, works as a community manager at JUG.ru Group, popularizing the Java platform. Before joining JRG, he was involved in the development of banking and government information systems, an ecosystem of self-written programming languages, and online games. Current research interests include virtual machines, compilers, and programming languages.