Frozen objects?
Brian Goetz
brian.goetz at oracle.com
Sat Dec 16 20:38:22 UTC 2023
These are subtle issues. Here are some considerations to think about.
Even final fields are mutable at certain points in their lifecycle, such
as during construction. There are verifier rules that will let a final
field be mutated by the constructor declaring the field (even multiple
times in the same constructor invocation, which the language prohibits
but the VM allows.) There are also some off-label channels for mutating
final fields, such as during deserialization, and reflection also offers
the ability to bust finality and access control on some fields through
setAccessible, but the set of limitations on that is growing (good).
Ignoring whether "final means final" (grr), classes already offer some
ability to provide freezing through the use of final fields. And value
objects will take this further, giving the VM permission to freely
scalarize and reassemble value objects as needed. This provides much of
the benefit you hope to get from freezing, in that it tells the VM at
the aggregate level that the object need not be copied (and also, can be
copied freely.)
Where we have a real gap is with arrays; we cannot at present make
arrays unmodifiable. This is not an irremediable, in the sense that we
already have error paths on `aastore` (dynamic type checks and
ArrayStoreException.) But what is missing is the programming model,
because arrays lack constructors -- there's no body of code in which we
can draw the circle of mutability for arrays as we can with objects.
We've discussed two ways to do this:
- a primitive for allocating an array and running a function to
initialize every element, which is guaranteed to run successfully for
each element before the reference is dispensed;
- a "freeze" operation on arrays, which acts like a copy, but if the
array is already frozen just returns its own reference.
Both of these have their uses.
On 12/16/2023 12:32 PM, Archie Cobbs wrote:
> Caveat: I'm just trying to educate myself on what's been discussed in
> the past, not actually suggest a new language feature. I'm sure this
> kind of idea has been discussed before so feel free to point me at
> some previous thread, etc.
>
> In C we have 'const' which essentially means "the memory allocated to
> this thing is immutable". The nice thing about 'const' is that it can
> apply to an individual variable or field in a structure, or it can
> apply to an entire C structure or C array. In effect it applies to any
> contiguous memory region that can be named/identified at the language
> level.
>
> On the other hand, it's just a language fiction, i.e., it can always
> be defeated at runtime by casting (except for static constants).
>
> In Java we have 'final' which (in part) is like 'const' for fields and
> variables, but unlike C 'final' can't be applied to larger memory
> regions like entire objects or entire arrays.
>
> In C, 'const' can be applied "dynamically" in the sense I can cast foo
> to const foo. Of course, this is only enforced at the language level.
>
> Summary of differences between C 'const' and Java 'final':
>
> * Granularity:
> o C: Any contiguous memory region that has a language
> name/identification
> o Java: At most 64 bits at a time (*) and arrays are not included
> o Advantage: C
> * Enforcement:
> o C: Enforced only by the compiler (mostly)
> o Java: Enforced by the compiler and at runtime
> o Advantage: Java
> * Dynamic Application:
> o C: Yes
> o Java: No
> o Advantage: C
>
> (*) With records and value objects we are gradually moving towards the
> ability for larger things than an individual field to be 'const'. More
> generally, Java has slowly been glomming on some of the goodness from
> functional programming, including making it easier to declare and work
> with immutable data.
>
> This all begs the question: why not take this idea to its logical
> conclusion? And while we're at it, make the capability fully dynamic,
> instead of limiting when you can 'freeze' something construction time?
>
> In other words, add the ability to "freeze" an object or array. If 'x'
> is frozen, whatever 'x' directly references becomes no longer mutable.
>
> A rough sketch...
>
> Add new Freezable interface:
>
> public interface Freezable {
> boolean isFrozen();
> static boolean freeze(Freezable obj); // returns false if
> already frozen
> }
>
> Arrays automatically implement Freezable (just like they do Cloneable)
>
> What about the memory model? Ideally it would work as if written like
> this:
>
> public class Foo implements Freezable {
> private volatile frozen; // set to true by Freezable.freeze()
> void mutateFooContent(Runnable mutation) {
> if (this.frozen)
> throw new FrozenObjectException();
> else
> mutation.run();
> }
> }
>
> But there could be a better trade-off of performance vs. semantics.
>
> Other trade-offs...
>
> * (-) All mutations to a Freezable would require a new 'frozen'
> check (* see below)
> * (-) There would have to be a new bit allocated in the object header
> * (+) Eliminate zillions of JDK defensive array copies (things like
> String.toCharArray())
> * (+) JIT optimizations for constant-folding, etc.
> * (+) GC optimizations
> o (*) Put frozen objects into a read-only region of memory to
> eliminate mutation checks
> o Optimize scanning of frozen references (since they never change)
>
> I'm curious how other people think this idea would or wouldn't make
> sense for Java & what's been decided in the past.
>
> Thanks,
> -Archie
>
> --
> Archie L. Cobbs
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20231216/1162a48f/attachment-0001.htm>
More information about the amber-dev
mailing list