RFR: 8142334: Improve lazy initialization of java.lang.invoke
Claes Redestad
claes.redestad at oracle.com
Mon Nov 16 16:17:53 UTC 2015
Thanks for the explanation, and patience, Vladimir!
Reworked:
http://cr.openjdk.java.net/~redestad/8142334/webrev.09/
/Claes
On 2015-11-16 13:49, Vladimir Ivanov wrote:
> The trick here is @Stable annotation.
>
> If the @Stable field is written to non-default value in constructor,
> it should be equivalent to final field initialization.
>
> If the field is written to later, the initialization should be either
> idempotent or properly synchronized. I'd like to note that though
> multiple writes to @Stable fields are allowed now, the behavior is
> undefined [1], so it would be better to ensure the field is written
> only once. Also, though we haven't seen any issues with NamedFunction
> initialization before (probably, because it is performed during
> bootstrap), we saw problems in other parts of the framework when
> multiple non-default values are observed (e.g. JDK-8005873 [2]).
>
> There are 2 ways to delay initialization:
> (1) lazily instantiate NamedFunctions;
> (2) lazily initialize NamedFucntion.
>
> Both approaches should give startup savings.
>
> In the first case, there should be some exceptions to let the system
> bootstrap (some instances should be lazily resolved).
>
> Second case looks cleaner, but requires proper publication as you noted.
>
> I'm fine with (2): make NF.resolve() synchronized and access the field
> only through accessor (make the field private to guarantee that).
>
> I don't see any need in NamedFunction caches then - it should be cheap
> to instantiate NFs.
>
> Best regards,
> Vladimir Ivanov
>
> [1] @Stable javadoc:
> "It is (currently) undefined what happens if a field annotated as
> stable is given a third value. In practice, if the JVM relies on this
> annotation to promote a field reference to a constant, it may be that
> the Java memory model would appear to be broken, if such a constant
> (the second value of the field) is used as the value of the field even
> after the field value has changed."
>
> [2] https://jbs.oracle.com/browse/JDK-8005873
>
> On 11/15/15 12:29 PM, Peter Levart wrote:
>> Hi Vladimir,
>>
>> On 11/14/2015 12:59 AM, Vladimir Ivanov wrote:
>>> NamedFunction.resolvedHandle should be usually pre-resolved, but for
>>> bootstrapping purposes it is done lazily in some cases. I don't see
>>> any reason why NamedFunction.resolve() should be called on a freshly
>>> created instance. Use proper constructor instead.
>>
>> I agree, but I think that's just a style issue. Even if you use a proper
>> constructor, the fact that resolvedHandle is not a final field means
>> that such instance has to be published safely to other threads or you
>> have to guarantee that resolvedHandle of such instance is accessed only
>> through accessor method that checks for null and (re)initializes the
>> field if it finds it still being null.
>>
>> In the later case (when resolvedHandle is pre-populated and
>> NamedFunction is published unsafely), it can happen that some threads
>> get the pre-populated instance of resolvedHandle and other threads that
>> don't yet see the pre-populated instance (because of reorderings) get
>> the lazily resolved instance computed by one of them (if double-checked
>> locking is used).
>>
>> So if always returning a single instance from resolvedHandle() accessor
>> is important, resolvedHandle field should not be pre-populated if unsafe
>> publication of NamedFunction instance is exercised. What role does
>> pre-resolving play? I only see it as a means to throw exceptions early.
>> So perhaps an assert in selected constructors that resolves the handle
>> but does not assign it to resolvedHandle field could catch any coding
>> mistakes in tests for the price of double-resolving when assertions are
>> enabled.
>>
>>> NamedFunction(MethodHandle resolvedHandle)
>>> NamedFunction(MemberName member, MethodHandle resolvedHandle)
>>> NamedFunction(MethodType basicInvokerType)
>>>
>>> // The next 3 constructors are used to break circular
>>> dependencies on MH.invokeStatic, etc.
>>> // Any LambdaForm containing such a member is not
>>> interpretable.
>>> // This is OK, since all such LFs are prepared with special
>>> primitive vmentry points.
>>> // And even without the resolvedHandle, the name can still be
>>> compiled and optimized.
>>> NamedFunction(Method method) {
>>> this(new MemberName(method));
>>> }
>>> NamedFunction(Field field) {
>>> this(new MemberName(field));
>>> }
>>> NamedFunction(MemberName member) {
>>> this.member = member;
>>> this.resolvedHandle = null;
>>> }
>>>
>>> There are 2 non-final fields in NamedFunction:
>>> static class NamedFunction {
>>> final MemberName member;
>>> @Stable MethodHandle resolvedHandle;
>>> @Stable MethodHandle invoker;
>>>
>>> Repeated initialization of NamedFunction.invoker is benign since
>>> NamedFunction.computeInvoker uses synchronized
>>> MethodTypeForm.setCachedMethodHandle to populate the cache.
>>>
>>> Regarding resolvedHandle, NamedFunction are usually constructed
>>> pre-resolved, but for the limited number of lazily initialized
>>> instances, I think we should force resolution right after
>>> bootstrapping is over.
>>
>> But then they are not lazily initialized any more. Or do you refer to
>> eagerly initialized instances that are not pre-resolved?
>>
>>> Or, if you want to save on resolvedHandle resolution, replace all
>>> direct field accesses with accessor calls and synchronize when setting
>>> the value.
>>
>> I think there can only be two kinds of "safe" NamedFunctions:
>> - pre-resolved and published safely (these are the ones that are
>> initialized in <clinit> of a class and published via a static field of
>> the same class)
>> - lazily initialized, not-yet-resolved and which may be published
>> unsafely
>>
>> The refactoring that Claes is making is changind the 1st kind of
>> NamedFunction(s) into the 2nd kind.
>>
>> Only the 1st kind of NamedFunction(s) can be used so that their
>> resolvedHandle field is directly accessed in code.
>>
>> Because it is difficult to track through code which ones are which and
>> it's easy to make a mistake that is hard to track down, it would be best
>> to always access resolvedHandle via it's accessor anyway, to make code
>> more robust to changes such as Claes'.
>>
>> What do you think?
>>
>> Regards, Peter
>>
>>>
>>>
>>> Best regards,
>>> Vladimir Ivanov
>>>
>>> On 11/12/15 10:11 PM, Claes Redestad wrote:
>>>>
>>>> On 2015-11-12 19:53, Peter Levart wrote:
>>>>>>
>>>>>> Do you think this would work correctly w.r.t visibility:
>>>>>>
>>>>>> FUNCTIONS[idx] = function;
>>>>>> function.resolve();
>>>>>> UNSAFE.storeFence();
>>>>>
>>>>> This does not do anything useful.
>>>>>
>>>>> What happens if you simply omit function.resolve() call. If lazy
>>>>> resolving works and tests pass, then perhaps it's all ok.
>>>>>
>>>>> Peter
>>>>
>>>> Actually, I simply tried backing out the DirectMethodHandle changes
>>>> entirely, and measured how much we'd lose by leaving that particular
>>>> case alone: almost nothing, as it turns out (same number of LFs are
>>>> generated etc). I think the safest option is to leave
>>>> DirectMethodHandle
>>>> alone:
>>>>
>>>> http://cr.openjdk.java.net/~redestad/8142334/webrev.05/
>>>>
>>>> /Claes
>>
More information about the core-libs-dev
mailing list