Idea: guaranteed object-level immutability, like in JavaScript's Object.freeze()

Piotr Tarsa piotr.tarsa at gmail.com
Sat Oct 3 14:58:35 UTC 2020


Thanks for your response.

> The biggest problem is building a user model that answers
questions like, “when is it useful?” and “who has privileges
to freeze an object?” and “what happens when there’s a mix
of frozen and unfrozen objects in a workload”?  And of course,
“are frozen objects the same type as their ordinary siblings,
or somehow a different type?”  There are no easy answers
here, simply because any eventual design has to work well
with millions of existing APIs and programs.  I *do* think
there are probably “hard” answers out there.

These are interesting questions. I'll try to answer them.

> “who has privileges to freeze an object?”

That could probably be determined by the class of an object. Today we have
class and class members access scopes (public, protected, package private
and private) and the same could apply to freezing, i.e. there could be an
class level annotation e.g. @AllowFreezing.PackagePrivate (also since Java
15 got Sealed Classes we could implement Sealed Freezing Permits). Access
scopes are used to determine who can change object state so it seems
sensible that it could extend also to freezing.

There are special kinds of objects though - arrays. They don't have state
management (encapsulation) built-in, i.e. one cannot subclass an array to
add some checks on e.g. mutation or achieve that in other ways without
wrapping that array in a wrapper object. Since they don't have
encapsulation then at least creating a frozen copy of them should always be
safe. Today we can clone any array we have a reference to, so making a
frozen copy shouldn't introduce any additional risk. Making a frozen copy
of an array should also be safe after introducing inline types from Project
Valhalla. Inline types are immutable by design, so freezing doesn't affect
them at all.

> “are frozen objects the same type as their ordinary siblings, or somehow
a different type?”

In the case of arrays I'm not sure. Sharing mutable arrays is risky as they
don't have any encapsulation (mentioned previously) and also (because they
are covariant) mutating then is unsafe in general case (mixing mutability
with covariance can result in array store exceptions). Therefore they need
to be handled with care anyway. OTOH there was a proposal (long time ago)
to develop Arrays 2.0 for Java so if that introduces new syntax (or
hierarchy of types) for arrays then there could be something new to
describe frozen arrays.

For other types of objects (i.e. the ones defined through classes) the
answer is: definitely the same type. If the privileges for freezing an
object are set on class level then freezing is an explicitly allowed (and
thus managed) operation on a given type (assuming the access scopes allow
freezing).

> “what happens when there’s a mix of frozen and unfrozen objects in a
workload”

If we have objects that are not arrays then freezing is controlled by
objects' class so we need to look up the class contract to see when we can
expect that object will be frozen or not. Even today we can emulate
freezing easily (e.g. we can add a method MyClass.freeze() that would cause
all setters or other mutators to throw an exception).

If we have arrays then probably we should look up the contract of the
source we've got the array from. If there's none then not only we're unsure
whether the array is frozen but also we're unsure whether someone
overwrites our changes to that array. In any case, without a contract we're
in a bad situation.

> “when is it useful?”

I've mentioned potential internal (JVM-level) performance improvements in
the previous post, but we can leave them aside for now and focus on more
mainstream things.

Freezing arrays would reduce defensive copying and would make them more
safe and thus (especially if frozen arrays have a separate type) more high
level in perception. Right now several array-wrapping immutable collections
or collection-like types do defensive copying when accepting or returning
contents in arrays. For example code `new String(array)` always copies the
array. There's no method
`String.takeMyArrayWithoutCopyingIPromiseItsImmutable(array)`. Similarly
when doing `myString.toCharArray` a new (copied) array is returned. There's
no method `myString.asImmutableCharArray()` that would expose the array
directly without defensive copying. Same thing goes for immutable
collections libraries. They tend to always defensively copy the arrays they
wrap since there's no guarantee of immutability. With frozen arrays we
could have following code:
```java
// builds new frozen array and wraps it
var immutableArrayWrapperFromLibraryA =
  libraryA.ArrayWrapper.build(1, 2, 3);
// O(1) operation, no defensive copy, still safe
var exposedFrozenArray =
  immutableArrayWrapperFromLibraryA.exposeInnerArray();
// O(1) operation, no defensive copy, still safe
var immutableArrayWrapperFromLibraryB =
  libraryB.ArrayWrapper.from(exposedFrozenArray);
```
We could go further and (when frozen arrays have a separate type) treat
frozen arrays like any other immutable collection and safely pass them
around unwrapped.


Regards,
Piotr

wt., 29 wrz 2020 o 21:58 John Rose <john.r.rose at oracle.com> napisał(a):

> On Sep 29, 2020, at 11:30 AM, Piotr Tarsa <piotr.tarsa at gmail.com> wrote:
> >
> > ...Do you think freezing objects could make sense for Java?
>
> Short answer: Maybe, eventually.  But not like JavaScript.
>
> You mention race conditions as a potential problem.  One
> way to manage that would be to tie the new object states to
> the existing state transitions involving the synchronization
> monitor.  That opens up a large design conversation about
> how to extend Java’s (rather antiquated) locking intrinsics
> to support more kinds of anti-race design patterns.
>
> The biggest problem is building a user model that answers
> questions like, “when is it useful?” and “who has privileges
> to freeze an object?” and “what happens when there’s a mix
> of frozen and unfrozen objects in a workload”?  And of course,
> “are frozen objects the same type as their ordinary siblings,
> or somehow a different type?”  There are no easy answers
> here, simply because any eventual design has to work well
> with millions of existing APIs and programs.  I *do* think
> there are probably “hard” answers out there.
>
> Currently, Project Panama is experimenting with confinement
> models for native (C-heap) data structures.  We are learning a lot in
> this exercise.  When things settle down, I think we might
> transfer some of our learnings across to Java heap data
> structures.  Note that confinement is a useful generalization
> of freezing, because both confinement and freezing make
> useful statements about who can and cannot modify an
> object field.  A confined field is mutable only by one (or
> a limited set) of threads.  A frozen field is mutable by
> no threads at all.  When passing mutable data between
> threads, it is (arguably) useful to be able “park” the data
> in the confined-to-no-thread state while it is queued for
> processing, having been originally confined-to-sender-thread
> and eventually (after dequeueing) confined-to-receiver-thread
> for further processing.  Arguably.  My point here is that,
> not only does freezing all by itself raise difficult design
> questions (as well as implementation questions which
> are frankly easy by comparison), but freezing also raises
> the question of “what is the correct set of primitives
> for race control”, since freezing is not a lone primitive,
> but rather one of a set of possible ones.
>
> Another variation on the “freeze” theme is lazy fields.
> The JDK uses lazy evaluation internally in many places,
> but the language does not provide all the ways we might
> wish to express lazily evaluated fields.  (So we’ve had to
> invent off-label mechanisms like @Stable.)  The point
> of a lazy field is it starts out with no value and then
> acquires its value at some use point, and freezes that
> value at the same time.
>
> The generalization which covers all of these cases
> (as well as today’s existing regular and final fields) is
> being able to write stories about the mutability state of
> a field, saying when it can be modified and by who,
> and how the mutability state can be changed (if at all).
> There are many possible such stories; I’ve identified
> the ones which seems promising to me above.
>
> HTH
>
> — John


More information about the discuss mailing list